From 46311055de18a112fcdde3ee7c8e4aedc3d9dbb0 Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 15:41:55 +0100 Subject: [PATCH 1/8] chore: untrack csharp build artifacts --- .../bin/Debug/net8.0/Pachca.deps.json | 23 - .../generated/bin/Debug/net8.0/Pachca.dll | Bin 236032 -> 0 bytes .../generated/bin/Debug/net8.0/Pachca.pdb | Bin 70732 -> 0 bytes .../generated/bin/Debug/net8.0/Pachca.xml | 599 ------------------ ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 - .../obj/Debug/net8.0/Pachca.AssemblyInfo.cs | 24 - .../net8.0/Pachca.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 17 - .../obj/Debug/net8.0/Pachca.assets.cache | Bin 150 -> 0 bytes .../Pachca.csproj.CoreCompileInputs.cache | 1 - .../net8.0/Pachca.csproj.FileListAbsolute.txt | 14 - .../generated/obj/Debug/net8.0/Pachca.dll | Bin 236032 -> 0 bytes .../generated/obj/Debug/net8.0/Pachca.pdb | Bin 70732 -> 0 bytes .../obj/Debug/net8.0/Pachca.sourcelink.json | 1 - .../generated/obj/Debug/net8.0/Pachca.xml | 599 ------------------ .../generated/obj/Debug/net8.0/ref/Pachca.dll | Bin 97792 -> 0 bytes .../obj/Debug/net8.0/refint/Pachca.dll | Bin 97792 -> 0 bytes .../obj/Pachca.csproj.nuget.dgspec.json | 77 --- .../generated/obj/Pachca.csproj.nuget.g.props | 15 - .../obj/Pachca.csproj.nuget.g.targets | 2 - sdk/csharp/generated/obj/project.assets.json | 82 --- sdk/csharp/generated/obj/project.nuget.cache | 11 - 22 files changed, 1470 deletions(-) delete mode 100644 sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json delete mode 100644 sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll delete mode 100644 sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb delete mode 100644 sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll delete mode 100644 sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll delete mode 100644 sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json delete mode 100644 sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props delete mode 100644 sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets delete mode 100644 sdk/csharp/generated/obj/project.assets.json delete mode 100644 sdk/csharp/generated/obj/project.nuget.cache diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json deleted file mode 100644 index 2961953f..00000000 --- a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v8.0", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v8.0": { - "Pachca/0.0.0": { - "runtime": { - "Pachca.dll": {} - } - } - } - }, - "libraries": { - "Pachca/0.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll deleted file mode 100644 index a747aca637ad2142f5f31a85002b06fb69c220b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236032 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 MKY;$be@_D604k^OJ^%m! diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb deleted file mode 100644 index 54cd6a6fc71f30e8bd26c05b3a2be0a628fb71ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70732 zcmd>{g?kj&`}Sv-H~~U%SqKm$5F@xp2*HB{FA#@7j0g~PH-fu6!JXnRp{00Rpg<`a ziWF&qmLjDsy!So3n>;M(_xA_9T-QB0pL0LYIcLtyoSEJ12LJBu{WXe4;Xg;HOKX3Z z;dMQ|)e6N%8H2(|s9KlEaMt|^T^tS`rzj^!C`w6s-BXfc;s+(uzm7U9*7fMF+(J2< z<)9wzTI1&!G<0z(*grObjX4$>>b@f2r}le_m8;g^H~)D{E@YoTwbHBd>caMw6m?X+ z!Oj?7e_KVlWvwWW3So?>TZOt|&?F~CIRMUp{sy0fzXIi95Kuvi5)7eQDTxX#e)1oF z(#F3R#vjT_HI{vpgFH~{05SI}BAXyQ5Lt=%IR-!1==ARlR(al^g=%+^MSi9i*`vum^SdDVNj^u*M8fCS6LG`juqv*N&82>@_ z=citMPopuQxhA)9*aNvk}qr&ZkQ8_VJKwMw?Bdlkd%EcQQ>)SWdDjBBo5tQGX%8eUql|D_3F{7Gl zmEBE^*-pp7Y&`hf=G?g!+Y~S2i@84XjOfi)&qg=R!vA%T+tuotGzJqd=md5(X zmRe=Csho@QH?55In|-y)V_%ld|FEBaENc}{KdsUbY6-Q0Izv670Z=p)4-JRLK@*@w zP&Tv?+5sJaa-cKNMd&(|3q6KjLhm5OU#r+bPEc9M1@eFzKrNs^s1wu!>JLRhaZoCh z4o!jPKue(2&}L{CbO`ziItN{bZa_anPoY1c_mE=%u4Jep0(}8R zLa|T^GzQ9qHbc9hL(o^yIp{KU1Ns?y3jG1ShqPP@)qh&0QftgHQjZR0*mB z)q|Qr0Z<31JJb(~fQCZJ&}b+FnguO_vZ0O84(I@s1D$~`Lf4^O=rQyf`WLcpqg9GP zr63(t9rA!0KrNs^s1wu|3WtV3Nzf=L9hw5oftEn4q0P`P=s0v5`X2fbx(odRy@1|A zA0gWytx^ms1Gz%(P<^O5)Eeptg+O0Gkx(p@0*!$(p=m++$89lq6|@Q32_1w^Kxd&# z&`;1k=n3>Y^cSRQi)$7t4wZwdKyFZ7s0ri;wS&4seV{OC0ki^I4{e9`LC2s|(09-^ z=nnJ327$_5(2F-((L7SkR&_U<~bQZb<{RG{E zoQ_y$NHRulX2zm~^fj&SsU2)w(rJ+ht4agsA4|Rk3LgCO5C9xLC>$CBB|(#++0bHW6|@Q3 z2_1yaLq9+_q5IG?=oR!gRH!?iDWDQi1*jTS2WkxYLT#ZgP;V#{8Vn^uBcUv4Iy4_z z4y}W>L3^R2&^hQbbOZVsdJ6pky@#|R*jLCIDi2kKYC}FyQ^+4`4|Rk3LgCO5C#4_tSU7t{!91qDIDP%mg8Gzdz7MnL1C z1<(p;J+vL#2OWb>LEk~wpgYhb=sENT`T*JV#Q6f1hAKfdpn6aDnh!09Hbc9hL(o^yIp{KU1Ns?y3jG1Sha7w3d5bUYa7|Z4 z+0|5bN4Y-K9BK`9ghHS%phzed8V5~)WDn39fH1s&Ow)<8_>_t zQ|J%qJ*4f2>j!d%%0pG5+K>;_6!M3HpL!+S#Xeu-pS_-X! zwm`d~!_e2zdFTh|CUhTq2EBs*h6)WpUr-6C0#psE1J#3?K><()s5{gTihzbf$LnDP8-j&f_LBNPIC0YyTw zPzp2#%7mst^PpwWT4*b@2RZ_sgf2i=pj*%b=vU}3NE43dBB(f24yppVL3N=fP#dT- z)Ds#2MMLq>aA+Jf0a^~NgSJ6?p`*|_=rVKz`Wbo(y@ftPwh_2KpfXTps3znEHG*0} zK~ON%3mOOwf)bz+(0FJfG!t3~t%No}Uqbt#n<99fM9m-$B=)JJ2KOIrIkl0NF(0 zz6X_tDnT_MPpBc(5^4i=hI&E+plB!_8V-$vCO|Ww1<(p;J+vL#2OWb>LEk~wpgYhb z=sENzisusl6;g&r7gEMSS56&+;$K;piiE`4A{QBBM^UF6y zc}{G8y=z>4d7UUC_B z)co>YQ4YboyvBbTrOk-^@;gzUJTkw&Sz3PivM48w%CE06I={S3l*jRHX7!&&`9+j} z9GhQ1Yg~T0=J@>b0a5Ot&#&Jj%9{=O^=|3;<&C23%eRo#e;VbQD9_Byum4q)*Jb6` zr%lK&`%KI)KN97%N%{4)C+C-Mi*mx0{CdsQ{PH4Eel5yvrsv135#>K;+ z7Msh3l)a)nWC`mv_-%vv_ZMZrxR2y94^Sb&JejNnGu~QoC$Np+Heg%9^z8)Gw-?Oz z90c=XDl9k->?oM|MJ%|eVD?u`FyoypxVT{U?<|<{B`mn41(y=c{z?nx8^dJ;v;DGy z`-95~?g1_@nC(@t;EIA7uM^DpN`g85%7W>;2yV7BKfnDNyGGropk z#@7@~zm{P7Zh|?U+JZShbp*3Ncfri}uwYLM_7cqgy#+JBt_AxD=6LD}W`2Fa^cx7~ z_!H zf_Xeb1T(*fVCMI<;9eHoTQKwcSa4s#Y_FdMe<7IT>o1t`10eHyZj16j3l6nlzBrKa z;T9ZW!I2gmWx>%F%m+Z(J|6-l^Wjf29{?ru!A~+D{v^j)aJ-n0ff_|IGv}f=H~~I? z%gleJphVoE%-qg~zxTGqJZSr|N3vF2#GL;X;3_8O{AYu!iu_gJY9fCP*ww^* z5L^qcZeqR&SP!mYVjiCj;F>1pd~E{P61*AgW@66YR&Z?-56Ahp4P3{>RWN^Fg55>_ z4zP#F-vjnEG18TN7Q7#IW}nx<0l_$2%0Urd417p1+dC}cnSVqu^N$Lqe@rm_Hi?wD-FIPnC)K`@yx#_nE5}7c#iM7VCMfM;+cO# zF!OH;rhiK?{oA5Fj_;0OwtrW|Ge1`_^Y4jxj_+r|%)c+)OA2OwDG|^7(t_E484=I?vVxgk-hwNLe709nFx%4!re8@g{mO#ry9lOV zMKJxUg6UTiOy5;7{py10*APsq#HM z^!o~Cem}wVzYt8nzZfsaKR__Y&(~Du{+J&snE7FX>4yuZA0e22q+t3{g6T&Krawq9 z{lS9i$B6NA{6hqD{6j@N^J4`wKTa_Hc)|1&1k+CxOn;bQ`bmQ6Ckv*ZBA9-vVEV%a z(;p%Bhx<25F!yh?h-dy7!OR~knEp7y^v4UPuNO?;Aeer-VEP$?>1PV2pCy?71i|zt z3Z_3vF#XAb=}!?%f2v^m(*)C>E|~rd!SrVerawzC{n>))&k;<2t~egu!Se+3_{2DHDf3slvTLjbJDwzH@!SuHarvIg2`a1;E-zoOT2fRx# z_iwj|XZ{|+%-<`R{yxF<_Y07Nly|Ew5~8~B`Hj_Py}{qF?R z|6VZti-PH25={THVER7@rhi2+{i}lMUlUCKN3lPRz}JP(_@4wb{)S-sHwDwbC7Awg z!SwG4rhiv3{anHH?+K>=vtau71=D{ZnEpe-^dAYP|BGPyj|J0zBAEVDF<-nMp9!Dw zzY1pjZ-VJR7fk4ea!baG6a8tzm%9y zJ|C18KI6*>W_(!@&-1aI@EKoTFyku-re9GoeVu5J&ySUa&-luM8Sf(E*+1WtHt#Rv zs|sd(HH&yx;WNIvV8++5h_5Mp#@7W`tuY%1!StI7rr%63{pNz{w-8LfrC|E41k?8w zOy5s1eSg990|e7=E#`Y5I8gYE#}|w8&L_s>i#~ZwA794FWBT~wOdiwcms`x|BYk`U zC6DRjOCfnoA79|eWBT}lMIO@+7USjljxQzT`HaVx2=bWm-7Mnq!TUU)@%TV|9y1;v zTF+zp{4lv3KR#fc$MpGuXX*E~=no$f&hr_M5AfzO`@@H9^O!z9Ae+bZ@qyJmrXMPp zewbkT_|RouJpBkU9#=3voS5e`9v>RaW5!1drawq9{lS9i#|WlBMD)k|(NN(tK2|W} z;{?->7fe4vF#SZq^oI$ipCp)mvS9itg6XG<@zw+n7e3=h2xk09!SvGv(;p?6{%FDU z#|WlBRxtf>g6WSJOkXdUzCkekbiwp91k=wH`^)#!vxLw134$3vQ84{Ug6U5dOn-`C z`cnndpC*|8biwpz2&O+%F#TDA>CYBSe~w`Ka|P3%Cz$?x!SojhroT|kR|oJS;WK`* zV8$;IOn<3h`pX2VO3Z|benEook^j8a}zeX_qwSwud6HI@-*k8VW+aP?# zZxqbBKjFynWKc&`6Dh0pk1f*HTtB7TqX8NXLB zg3Z{QfF#YqQy|Um7!e{)qf*JpvVEW$+rhic|{Y!%B zUlvUN2f_5O2&R8kF#T(S>HjF0{&m6he-cdphG6W_($}^vemR zUtTc%3WDiZ6ii1kXGVEWAk({CY|eoKq-v=Tn!eFZb#PcVId!Sn+J({C-9exP9bZ3NQ~ z5=_6XVEXOEc=^26UiggfAeix;1k>*Gu*$ zzqi<*s^C7tXMA75jPECy{uhGj_ZRK)d>tTs#t#(C_)v@ZFyS*kTrlG!1k;ZcOg~Dr z$MZW{_>3PUnDK)J(~l8Me~4iELj}{16-+-)F#UMJ^b-WrPqY~CFyS*kNigG+MLg#> zRrrh_E|~En1k)cWn0}gI`lAHXA1#>v7{T<%3Z_3!F#Yj@>FWj4HwdPmE|`9XVEUPY z>1PS1KS414iGt}*5=?)xVER)8)1NAs{xre#r;Fpk=dl^WXZ%dTjGrZ#{%pbY=ZN-r z{>~LX4Xh2&TVMF#T-7^j8U{ zzgjT;HDW$^zONNN&qM!}5VB$)nY!SuI?_INzD3ZL=Y1T%iSVESJQ zroTfl{hfm8?-ERZw_y5v1k>LunEpP&^!E#yK z{yV{p|6as%|1Juj@s|WM{<2{DKM1COMKJxVg6UrqO#es0^sfu1|C3<)Hw4qaDVY8( z!Srtnrhi8;{kwwc=L)8OPcZ$T#e4;U?+c&t4+Jy*p3<9q+t4`1k*1q zn0^_-^vepSUrsRn@`CAC5KOAMQ1UtKW$ z8iMK96!XpXx0di3?wFT3!BbdIsMSC8?XS}Ci#(P=BdkdfObp6 zOuvC(`V9rsZzPz0W5M*B2&UgujF+!xn+c!s%>^^Qg<$$E1=DXO+T;1|D}2WL31+;% zVEO@q>9-b4KTt6JHiGE~38vpxF#UFd>9-e5zk^`<9R<_xB$$3@!SsU#)9)gfepkWt zy9uV>T`>I+!Ss6wrr%32{oaD<_Yuc~*F#_7Grpf-#(yE0et*IA2MDG=P%!;a!Surf z(+?L+KSD75NQ?1C37_%Nf*C(ZF#W-T>Bk7BKSVJ7p@Qkh3Z@??#>4YDUigep5X|^Q z!Ssg-rk^C3ezIWtDT3*z3Z_5YVmu>+&-jso8J{MY{wTrpM+>GuMlk)cg6WSFOn z4}V{(7e3<+f*GGKn0|&}`kA6V&UcpZ89zZV<0lHHKS?nC$rkNR5kBLm3TFH?!StsK zrawcpR{}g!_>7+=nDMg()1M=l{#=Xp<_Vwi^93`0fnfRz1=C+-(cWU=Gk%F+#xJ#q zUnYFUFBi=C6@uxn6ih$cqPlLruwcd?5lsK6VEV@_+B+_M#^(rT{0YJIzY&{ckPW`%d_b|6VZTFN%2HZ!Za-@s|ZN{s)WrE5c{| zRl$tEW)c6R@ELzyFynu+h`%9x#@`go_*)k7w}sF6JAxU1*CIYw_>8|NnDIYb#NQV_ z;~xlS{6mZQN5W_PFM=8W*dqRkl|$a|i`&3|D!4KDncynmUj;jYe-q5}^SR&(;NPvR z^4jNjqy7}kbN#L0k@#Kqd%;5x|F7VtcAX z1oN{oy#%xWzJl3*f5GfOR515HLNMQ3j~2}Nim~8W!R$XlFy}u>Fh4t!Dwz8-QZV;- zwBYvOaf0a^1oN|8nS!}L69p%MrwHa}EvF0S`87*0{keiUe+vZjdRr{`9nQC9g6Xdm z%(ZB>V18F}onStTY!uA?wg~2D7q<(hzf&-;=RJaXJ?|IH>+6tU<{uTz_HzXD_n zaYx0`x-eF22dvOeSe1)nC3eQ@TN*2F39Pauv9gxJYFYs+XnCxfm9SEl!|GTTD`I7= zf;y$Sbw#XpHL$|f!m3scD_M1{UanZNs$!L@f|aQzRwECrK;Bq&+_BQs!Rk^QD~cyp zk-AE(wHH>4dRQSEVO3~|m7oEx{>HfCo8l^Ofh)T?uI2z-!M?a^{cxqW!qw@IE3yr) z!a!VkZE>{);RbLI79p3tn807@u}5K>nB$H3yt$NH5F@om!>9WcCwW(&cGwJeDQb7uG9)e z`8Kw8%A;D@DBHMYqg=@?2j!aB+9`G2a#3!Gt)0>yf7;4c>5i?PGRVyry^7luKi4r_^?jP+dK0>bdJt)4@F(i<Z3UL%SFuMroNJ-uB})_8=XJit2?<>B6Xl()I*QQqg4jq+h^*;)?D)v{7T?FC!(qL#DO za*kS7N~*nJi(b^SUM=US#N?vpG|CQ2?qJ`Q zw#vj9-<9^tKx`e9_?T?e^c|9onzGnBD0PP@*;qG*D%tkREtDOU*F!>4b9QJbYGz{V zpllqPgPQTNIj9N5)#_=eAu`TomAW!C6Xvp7zVnqk;FDEhcu)NG5-Ma@ub z9hA&?-_=;>6MR?OD^0Ow%h{-ToRE#0@3Cd?${JkniOL##B^+A^WprXFYJ!J_qNWtK zcoG_xgPIaaIjFgWvV-z;n9EvQT6<*{whqdsBt2?QCF@a>iLHaOJUJINqf&BF z(kI1tovqR$)pwn}@;Ay3O3Bo0)O?Gry|M>ej!;?8{ZiK3EAGRU^$tq2;i0Ih zJt7n}k5R_o;T(~Jnn&2$D_gO3P`(-AvcXmvG16s&y)pn>*62~wB2|x?zeeg&P%R~nD<-RPin9hHq5=h4}yxsI}f^7|-dlRBPF_DafVWs`%F zF*+1AQDZ_;Qw>`OrTLf~)ZE0@Ub%oRV_Y`l?mX6Iv%NA3TL)$CSUqaSjMJmWA6o~d z-?&`VlpLRnn!iz2$Fl`@!|}db?3L-*s^dXTBYie%Dq*XRXREEUQLk*ZR~BQd&JSv4 z7(!9g5nBf(%8-MavqN)G$&NZIrpE%9nV4$Wp$vSCX;iY=ojFYC=JXe)nw#xG<$}W55Dz=OXMNQV!P}Ia? z%X!X0&EmKm)I6G+gPP~jF1zs;P^Y`>wpXrAbJ^{nyqKm(%|^W*HS?zHQS&@H7d5A5 zYJ%qEs4*`4aQ@73*=Mh8!QNJnEmx9U)I3he zMa{Fhxu|(P#CJdY^4)K*B+T>O&%Uxz^Zo2>)I6V;jhgMVl>@fQg!#$=dnFEAo(Z9- zaf%B?&4>A+sKNZ8=JLcG)Qnw_gBo042f3#%2kn&y3tSHJxa(1KW}zN66R>qqRxZp% z4aS3-&WmzUgYg`~-^E?*d&pk7yvX;EgYs-qHfq)`&PL5tY`JbIhi#QUOO(U*iW{~L zO5-J=s9Co(6g73Qbx>lL;>81g=@S;48l?;li;s+rN>Y+jlGIwI*hRNV2a1>QpEz|y z(X54AOTP>6+Uk_;55;G7S8{^aXjdIc@tnKq=E&C5+Z6NJaA@}rJ*|shn4R6Y)5>3~ zZ&}iy{1EG3KkU6YEu;N{8*Rcf4|lk37u0E7y!{s)x8FGLbZAIQk&WF3rhYT~Sm2%m zF7t;y>~?hUw2A=RIh_gz-|ui!FHN$)DH-xPQHT}sNVpM!sVoagr$e}h7$%Dg^672^?!3|4^ zinI@ijfsj+@r+0=?4@XIia0x$uTP-=Q`AtL}I`dRIci!yY9IN31D6wet$c+a71e4;u1E z-KnkYdZfgtS&sRg>6g32-?4uYG5_(kGQa)3c-X8lXS#G->5v>}(>d1mWw+@^gH{$w z2p?kIIx0MMkWGA4N@GuNPtVSQApr?VQGSVu?meTDl4BC$TMVb+mz*3G7ap7DmxBL3 zIXpEbieq#ZygSHG8krE08i%P)uG(~TRWBYSFXKV-;z9B<9i+UUwW~I*IyfaIF}bOiS5nvr z&p|OMgHyv(lcSO%65>qbR4X%rnD8QsXcZhi0ib(=JZ z@`>`P7ZqLCJEDGMbi>9zUbU-^9h=^NxN+tbA0;9oE-?n@xre!`0p`-%!^hL7LBqN} zo^`z&)Tv*;flmW(pZX1&G-y=cCn`M3yNS0?y|D18M&3;#qZ`zXjB41x+q+3P{_mjp zL8)PbqLhGYN^(MKQbd$TG}`uvNJx!O!QaMDNKHvhP4O5S6CbIxPKytVi-`z}jZNzq z6F)R6vSUnmQdm-&l8~4Z6BjcoECokaiR7X8NQ_NQRuW^wQlb-*;uOF5w1C7^1yhK? zsJwC4#jrgacwm}_$3)=&&-V0bShqnv&$@N%)(OL&MEHbZUm8Y7)$?iCpkCufVNvy> zqZ>susUH;{R=0t7gNU%`CV4xM->47%@9y~2*x0b}*eE41Ui}dn6&({FrG#|z3kY;? z9q8X9$Q>so)p(#ce*^?~3-s&Kg~bK~y$1$&8yJL&kic%NY4}-9qt9y4jzyNK$-4h- z)Gau}=f5(12L5M;Pr*&q`>&?zebSV&CazOVL{dU>LUf8}KzFV>o{_P!c#G!6lsW$> zdVGrN;<3aXo4U3=cs}=5{qTj4^TT__B&UYO`iCXQM0oNlHzjUwF!nV6wx~8?mPg9* ziUI2n{=MhLAE6BkPBeDr9cFiioq3(Od%45}ZJUf=bB49f+!vgm(<>&vo{wjjB%E$3 zF~hN57!#iz)h%M;x#2JRf05HP=wOK3xsl~|ZnRA77BwgaH-|J++T;@zCgSghE6p}_ zKL7ai+_rlo6N)_hC+B`#azsK>Y)rT*>`h-V0lYU z!7V2&Dbf`A*B`CU;16yoHJU8@sp0Pxf)}2-o9@%y_j32NG`@z>a;Z8k6WtumnE4`og9{;AQ?xav*8W17DB$lx1wo_yG~qj%7pBkPv! zn>v5KDL5b@Ha04PmtZoELwrPPQW6&);{=`W(SIBFv`sOWcf)?}9;*$tCCe+ojaBf z-X5$!J^t>CDxI7PNbQ^uAH})@hvt+o;+I+G#hHc4V+?u8afu1>m=pDO=;;?9pOB(1 zS>_4u+xX6LB?rF|eD%z!SFe9`6s`~6ro`9qrWLtv(7!ldWRAJ!)@l+Yi4=ieG@ci>>@i1!=(ryUIJbLK&hh`bi!qhki8 z;xPs%s3~RN?Ruv;X73MQIsS7X(|6CnZwq(0{`zcwN^(NpSyest<_wPT@bQ*AMm=2F zZbz?_>rMXb)GaSKZ{_iQc5DM9i`dwP92y!NJn!P=yCD;s=9nUR+;D-!#pR77>ie&s z@y{mAxKhfnWJCAX$L8#M{k*Hsewo0m?ola$BXKw91C05cxi@#4x<0P#n{D-Q@9Iu_ zmOtEhVM3oRGS_&N!{Sff;;{a$JN#9lp85`xJmfK>It%QU~?o$D=U+WRivkyCe9Z=Z43 zF-fzp-;#pT@_O&CJ_MLrho!9L?GVG@7s0#(O&<0NpriVNA z>C3p&+Qa7`a|rvJX8+XWRlC+(?8kQ5d2*dw0S$CXN{EO`Ht%x7?|rb6;?rN}TYap* z=Y`+mui7q3p7rn10usBYghdPu!F=T{KoR@rai%MEZ5P$Bf(&tZQu<$rzrKAnR<+i=tQF(Z9~XS5kQ zrvJ6?%ooKceIl7pO~zg;Z0Y+G6Kg6>C;grJbDQl6PjBw=JDzLm)fn{T#f+c1to!_P z`;YUB>-a7GEje|h^$#iLq2b{*zs2qP4ZFD?K^r>+jIGnQUAlEOhmAJ_y)1%1TTBvn zI4$F`^Uu6|xkE%~@RAmeY5uDp|6tBDK1ONPh9CHR&U|)uHmUlE5eX5kSGCg_ zI<0PNp6fPvj10pC;g=j=*OYOhZuT+maNNV$7n;8JXumu0r?A|=DsMJ*W6lT(Gat@@ z5B<-vi`P9Lj9u3<+V{)j%Uxdw>K0ww0jWPao5#SwmVC$8d6}&XDiF5j-Ioa zeIdx@i&J~X`_KIC_vEo|KH;Y9cEMc45|ePra7Fb`N=S)~G0n{Rqg$q^9VZ`~cJ+$} z?UrsnuGzY9&h3Izje~XHA25eG&$@p&Up;(8@Q&PH#vk5xaB{&pyxL8UG5x!tG0T{~ z;OKXK#>|)(wDjtwS+-MOIv1SYHztuSn1*|KX81bh)*RgG){TS1{4?Gi`Euxn_qFnK zjeV^9((xhVcJ1^3azNP3;N8oX9X-)uTFd-6F)Y6bt|olkN=k?|b>tLMSUtxcJlWE4 zh5ww^%gwrH~OEo&Hk9^yZP?N%MOXx2NjsoItkCDV)Z*w z?_2eGw#{G5YBc`sQ0Mih&W4=)(4lmJ$)8m6^)Jxthmv3!1NJKS3NmO}TGge1KDGfmLT zag+b#CH3u}lN;r>uIj(OOk!5OYQJC1&+8Nwmynd!DJ(HDDw4BpzC5h_LHTRvJet46m^&U?_BKf3UbJalu@XU*N{ zw5_e7;>t0J@s-RkkIWrd+=ag{<2;R7KVD0Jc`;;vhof&((#t+wZ+`k=R;RES^K2eV zzPyw(liDJ*;q0xR0SCtREV0n~6{phO53*UqY7fpFCr(cUIeN z$whAWD*1Gs%!^9lyFa|Zc{9p?khNV^W;OfFwtYD9`pO1v*I%pCtwh&ZaU!=%{%!S7 ztBdb>3Z^9nUTR$Kg5Q*g0X9E0N$oGAEnnb1vTr$`Jruj}ylts}qJp-@drh3<`*D}Z z>6)7NDr#kYr&pXY_XBIAM&GU#px@Nfwa?IpuSImXs9~v5$@x#1!^38={mtu3I8=D* z+FAd{7jIe|?XMFV-J|f5GA70R+WF^d2cPk*T6Vw2>`nW-`A>D~-KEOeH48q;;QKmJ zT@qsQUWd;A{Z2XedV7XG{ z$@B?LTC|?w`=fuxsPFEGZhA$9o8A{SE)ZVTKCb27TX+5Tiu3!$feYGrc5at#%$S`! z_V!BuRKKIoYIoak@A;RXW|;TtlaYEi>GKy;lb$9$UK8TjX8Ze=HU4o~`KaJj+8wXP#!Qn#?ZhRGO9MlW9 zO6xf&x8&Fg?>i`g8;-omimmds`2y$`6>WNJ!n1Wuaw2b4at0@kUU{5@Y~8Enw0$SX zc3SV<;NzlW+q#>&6WPYzUcV19Fuudu!OfQ42=-gM+iByQ7wyasu_8VYZxfjgZ1zH* zrQE0G#pjIJ^JH(((M^l`j6HV4{CI4c)UKoY#)Q1era zBW$PnC5dGV9b*O!P8ku!t!W5xjT)+t17$9pI?>|prGOR7wZC1tw`S<)`}x_!V7>O2 z)JMseyQ^Mo-?&QfswsCv+Hart_vfjdqEf;l`4ZPSSLKc;{)s;UtSng)k#J~2Xuzhi zakYOra@@Swh(Uk${89ed>H|Ee<7YXSoHu84+dWHu?X#6F6y;Rru^aPqya`+4y#IvS&B-N;HkvZ$$PUHS3?z^I}|H`EG6KzyDj0 zDIE*QGUj(uZ}sL!{3q>SY-IXx4Y#e|++j%PC1biZyV>)&`EiY_KHuX`_B6ieGj(e7 z?TJigm8ekqR^hdC{U-(v9A($@<}h=orWY!F-o_(OQg`+JYBbU#J~=Gf{OsiHvh*q& z`SX}_>$%~!fh$}7)V`nDOcZl4Rp0= z#dvVvXLk5T$#qor&0l=Uv+!R5`z|YeOYFKb+#>k1LsPcz?I#>+#ZbS65wrXG&Rl$} z$R9)BUbo0=i}yD3pYS{jTYt%is?L0WLErJ{ww3OMI{3%`AIa^*hKF^JNQy}`UqT;VpIO0kvbuXjY^lk| z{8mQhuCt%xXnvM4zofJrgsJ}XJNMLP?QU?VMw~r2yvwE@?Oz-#7qG0#?}fU#)*EM$h51(B43!I( z?Y$87waf3rf85%3pVn_~#URh#AIlW`pQ*-|%{YeUTgZ$f{nWL+O6cLt)N8jot?16%OrtL(kR!(US$ap24pJb~<%PEZHKusA(ti zo^1+xdE)Xxe}_k4PM;#b2OX>!yvgCQ)vQy0EVs-Jhz*O0Gtb(I(=9WZG+^}HK?}Av zXutM?cl7R$73@DtN=i#iNf?wAmN+=g(=T>V0v@pj=d}`;yJ9`}dixip>-mqn*I~^b z*CqY_={)^^wi1x{mQcjXy7!r0vBI$_d$PZ4d+be(+bz6jo%rAB$>t|~C%@SXm|ni; zo0m6IyR_e5srTs`zYaB@dU=x;Aosy|)>ge*;TQhp@jk1!W!-b06EOYdiVE7Bj&uHJ z&w=p~36c5F$txRbv)O&E-dT3@Q+u~hk2!q8xBtn6|Jg<`etq6O*?ia^l)Q$^6yN#Q z7k$(8Tt)xgZ}zTPebDRh|7_y3b8pzvi-*}tnGxA9n(F(tU0rD86X$)-_x@)qy&HHp z3BViVG12&qDSxwVoR|`uE~N)ls2F3v3d$b;RSa7Q9IURfckLe+Hd>G zCjSfxnlj@`>b5~u%n#6}`-1V|5l`->X~`NqPMsDv`-yW8N7oA4&~43!IUUZn&l|Au zmBc4+5x-vdLVZWBlIG#8XW_+sr=-0q6*#f#4pR=_+V#vkr2_B~h%R_fz`Tbk=f)o4 z4Egv@_~LQjXRTK+>vN#5@|Afp{xsQmFydR^eZ-WfU!EDHXpJG4fNV8f1jox5B=GpnF%Jf7suOqHD}7kMZy|kFS<`ZTokZHd|)vx;Kpf&`yTqx1g!9VM(9AdGY@7#ywoL z_S*Ma_$Vo}!;EOxtgL2siaB{O-bhg|&;WeWCY~QDG0sf6tDYaYZ?hIfUa48;PMeI) zOIq*L4YK(x&lrB~=7aO>=9t#H@{f_TTTf2kJfqh13VlBd55{kpV%4{rOdSobxNH+U zat+*YYt*jEL0|T<`FiHCuzH_mhJ+;#P4?t3ZK7inMwqhys=44U`@LZ8H{)QD!TxiP z`aJq7azv;9$Tkl4?=5rHSFFK1_8qG}<5AGTz=;7)A5MPzA4#7b_olt(9pI3=7VWsl zcI5mH`sKa7mUjC3%zxxtyf-wd=z$Z=PPf4zqJZt0`je=hAi|NqG9g5O=3GUNS*s4vC-UGI_;@n)j`+B4r*e!QjG zyU#Q6IS%uKO;EK^^}-k#TfSq9H$k1()hS!G-4BuGx5n|C6+9=Kelu;H)K}Atmu}15 zqnclH{jTk{y+3d9PQA6s+>OlWn1D~5n1+}0Ao4wTY-rIdUBCOLV#k9!T3mYi;hA}v z=*drkg`_6ty|Y|s$EaJJzU@)R`;E)Y=(J+omYVh4Zp}3HlYf;J^M0^b*wT-=?uD;(YZ(_eOp*#CRQ=cb`B zP`pby?$G0}*le*~yDry#Q>)XgzH@#GEcVX4tT5X6`s$MxJR{@JO~qdpQnHQ`s9@9 zh|N0VFo)M%n__35d#4AsojLx|jm`_3n-@(rB`($?soN$W^)f%buK1ed5{`ixf1Zra zZh6!EZkH*^Ji@&5F!xa!{&Qq}PQ(72!9zUmby)D%ew)!N|d2Z_XI0$toYbtjg-YS~qq$A||1?8g98yja*`{a2oVW{tZr>(#`j9)W^3~2epYd z9dQ0`-qgj?vd6z;q;|=Uoeown+8H8u&8fcq(L{c4y;(_o`9h<`cuQ#U#RGhmjF@Ix zG|lgb<-b9d>}mR_g!ye4yjf;`lsCU~YW`)inLUl~awVG|JKF`DU%m4N=VaPb^=8L= zNM40`p|7CXSVFV0yk=tq%|>6%#x9zTgEbqIG#kfhHqI=gDW=J6sma`{$^4+n8m`N- z*JX`$&AM)fuX<(KX|u-IA)u8eSvSrKUxXXWU2YUv;4ZTzLs5-klE!dJlaAl}-O{CZ zaZTUCmJBzw>B060@YAH~^m;Zmz68)$W6)>}V|4~cogvfJa952jtTklN?4Z#v)EJ8C z^s9A-(yoSKH1(^rhEn`Ka%26m{~SfeD@|sECUdJM%T<@TUzb(KHERNsGxuq;YIA>@ zXySAuIfxPL$4{f5qA_T7`uRG8y{jQo?Ps3WU`MmLM*l%$7zIaXFt{3SsUx)08uZ3Y zgH~f0r!y4M8M0grxvJ@?HDuCkr_s;R7#wu^@uyvi6-`Ko(6lZU#T;c za5coM<_fLBnfunn5c;3{reCWuxM&P98pA!EAw_3+?3x~+cAKm<{KA%7YYa9TLpu7@ z878|L9;l|1)-b6wOdm}~eNDzqO=d4$#v@(k0N2ce^fMl6Gy5~SrACj#r|9%^bp~5k zL%2GuIa-4ahtTw#^s4H}x@irkjI(7ZtT9Z)k=7YzxEh|QW+|;SXXKM^) zb^0wjLuFUPD0Q@(wT4PGgEacN8be{7eud89>}rTp&E;A{ahhmwg$7;eH|Y#ISHnov z+^98Fzd)jGXISWJ_(ScflGd<5J=z(66_{u~LKwzRn)I$Z z!vkGBTSzc?H!)*OEjiI;3uvU{^QD^v4 zmtM^^eViJwU7KE&UAN5a|3AB4pfTLlr1#Ppp5mHvO;;~f!xL?KekK7b4GF5bQfnw-Y<1^SGueox~#LV6I_{`6{5{LqXvX% zGQZPgmCc`dtmEqRpDmfQ&yinL{<1do)?@y3FIcta`3lGZ>J0Oq=Dy z?i*+Q@}J%Fwl_{=IIT%E7D(wQLZ}>n8`*SZ64rF-+EBg*41^ zH9S*CR7Pu8WGhv)lk#aIJKm1 z@EE7n6yqP~(o7tLW9O{3bL9W?s6OE7ZwI5k52`EUxvvJk^IoV%j8Yy)IIB1QI3>Pg zSyqd$IhWJod%zX7_yTP?Z5lVo)@dDB8o`k9+~5n_6}66b_yrHXv|3J^V9hvd{1=X; ztvvd5G!kju$0OTLe!0pu8vqiJC-4i+yK>0SvG(^tnpt)mbAwI6R9?3ZlJQ( z#v1SN^(l_;_1Ca!lz{PDDVok{l`J1BJRZ!}<|oQ=nq5EGXz<4oN^A6Yx?o21 zS=JiNhFzf=5&T#IZVUL+qLm8c|NGS9YyaFhYi$bSzxy1`jZ&D8%`BxcWIQ+Kx-v#1 z*%Vh!Te2|z7tn;lc*aI6CAd*TiZd6_-Ri~;&EtFJ&YV*x?N~efzn=JNZE2#qDWV-_ z$3E~ybFH%-j}e1YO&O)M_>QxaHqa@5HnfVaGoo=W6vA#c?#f*}W^Fv`);#Lg#-m;l ze{s~&3a#P~%>`(aIiJ>6X{PyfOtVIEX-#8whR#}RkZD)Q+Bidn@wZrU65DaKPTC&q z+mwmE9gTgr=*E8UoBFk3zc$8x-SC%F@uzi4X|>$o;Q1Llx54+2j0dx5S_`$?7uFgi z+7+@fc3V=hwJOVQF$1{x*>@lIZAwJnMU8z2;m^I}!QRxj9s9O3_8q{#x@v_}6a&C@ zVa4;58~1YJdH!xPNM>kL20X^Rydw%R`~xkKRX8M>>K9)26+aZvZ$l#Ts%GVOOyjslmLy5f9#?iI1Z#24l4H`AtCo_ogqo@Ke;HdeSY zY~XhckA*qYHrf&FF|6Pod$Y&$ z1@>5i=UQnkrrZC&j_oze9<8lPbAHvUx>tccw&=qiUz&RS^vbHpv#Bi4rqVpNt&KbS z*_CDZ$MVW@w5nw~rPRafq*eR=?8-7!?u*XThnYIB#}lib@x+=~3fDDmBBgo%z{+Kd zl?AtxQrfY$xYQhNd8x64`-fU8!&$}6h8vtN>IQcgmT-Ve(nV8T@UW@KHL;{N!Bm&O zI+o>3nKE#PKn8CyMR|9@T?2P8-j7hKtaV^%gf&`8HpQV8mar+wyAT#6Ym6BUV9cfx zR$DfO+f!v4rL}RUjB>mSwa|v~%&ljoGhH=<(+rh)*`#l_)?jYz3e_`SHv03prIh7u z!OC=RQ12^6v}1W&a43ucs5c1QP1I5`;|&7A=DP%MByiOmiKBLesVOJ6SBiHXN9{1# zyn3H1P8T;9y5@{hTF1iJ4b)-HG2N5Oas(w9;;2nkTe0Jf&a`plWsZBH9qxrV5*V|o zPD43^uI^{Ww47v-i_Ap%kwd98sRQ1+Rtae}8_x zl+U|fx)*r8H2wlTW|S!K3N6J|NWBRK{KuM*QOUAL8!Pl^TKClZ&u6P|8uvvtg0(*^Z(k%NXs6r zxzgJFua!0k|0S~~Bi*!*pWb8DwcUxw5$7xW#uEn4jJ&1(vwLg?mikZcvFcK9&lAak zqp;PEHr;JaEC1*B*iepVOM!Dv|V^x&r70&qoTKPMMu}3W6 z>fOzrOQF4SDeQuk5Ie6v;gr;lHZ!a!QB=QJocOcJ1&iCNu3bWvsXW!4W@Q*jg>TBWB%c2EYsRFvQO{+MfvQ7=T<9R z@witM)u!ehme20~na-AXe_Ja&^xJSKMfmt{YrHe6_u|j){w*Rn3QyB0KD|$s;3%9q zin1JquQrC4!e{rX%%+z2DO=ti^9SIhRj>Ka?o;~vk?ee-sq>mVI5mw2Cu=O8dvS}Z zz?V1`cstVZj)eyz(<`1zyjfM`ZL7-vtL<9Aq^PcRP08iKD#SmLtiB5_?z2=OC`M$sh( z-Cg8!U6#*v_doai)icAR)6MF9U)8N!b?-g@dE9gEy>+Wcraf`tHX(*kE>{&YMu zrcu-|bEFEptSSE6xta0wh+RyZ8NbwhUyLU8O4B0n=QQcTSn~*m$%&TeH}L+=Ws=cS zu3E}fGo7@+-M5s`Q!0+XwZ;_`F%hPX%ynS73@N1=To5Wu>@s1irObrRmq2o!)NLV? zk(iuHZ7An!(pAqBDVcdqOL+iOlc0eCYb%i}eJOyh^he;CPjl`utmbJ~T>o(ZIVrO8aTOE4nhh5H@c)-p~Q~-KrQY&56y`mPWIM zF&(w!7_)rgcw-D@+q;U-zsHuoXO<%z?|Xf;{zN{X)LH9KGRqN;H>sCCzmCtRcG2fk z6Pn)0{EdkcgKTPV0fl}cVwlNFv*KEKmRaj?yjg}V+ndYhH-BB9-%M}|*ysW_dNbBc zoETm_Nu#&1(qdLxY$`e4VzaPfd&~KJ1re?=_Z@FVf307`=PA}tk*H73eO-w00_KBq zm3hj5fCI>~w~{U0$CCH4=KWFHUD9C1`4rwv56k;31|R-Rz~3FbEu)h42?>!7IXsD#bm z)cod3Su}IJjZmHu0iv&5EszuJQJ(!HX}(M=e3@4Gvf-q}+r#It@%d|f{+fB-!h3_y zf6wQ?=kwp2=gqx0>m7TP*iB%e`&N#k@avQmlU_kMGe2-ZM53^WLX~-=~ECHA*Sz zzmVs@u-;#p|10w!G5-5-BH*m`Ur8dByg!)b z2a`zudLku0y}5rmiCj)1SFq9*Br;NaoIesi6DyMbmFg#aE)DzNCed$`=yzEEJ0yB} zJ<;Yq9kf5Stu{85#BU^#8%bn(lp1#!^MAxfe?(AKBw9tHH>q>-Z_=*g&!I%-n7|S9 z=kobnKBq7D>C62E+Qs|@04q%1Li)Fm{;hm|Yft%yY>qz_c_#+q^l;PVDki6;a_Wq= z!dR7DSvZxz;o-kN`AewQWdyg3;O?YE?^GY>-$kL^MUA-3yUf>+nHn-9K3_nm*q%?Y zeoy0x{bewC6M~)CiU?EeZn-izfmm3&hv-%k%}S!Vk5upD^ZSYBe%^n8XdWOM#tolw z!+(%y9_$%6bXJ#zrWrD_n5zdgf@`SMgrYPpbQy4 zLx#VJV%bEo)DcKsPZJ{i^#g_I9Go1i?l`elqBJ9nQvVmk@JrVDCGFxF+TSz8_8e(E zM;b4%{0lwhU4`TS5@CsPZw(o>0)gm?Wi@S9kujy3q_J{q3TSRsPxk% zEZ2(dv{H$rOH~_bM!<9#-{zGmvUD3hZ=;e)w`0Cth4E(T7_{ibx{Cc92XU)pV{%%a zy@F8cU79e{S;dp?Y)rsOcjl|cgu3>0LQN%Ip(;*S^fRN6bOKB{G1W!77aQrtMtZT4 z{yLmX_cuCn()}-230A7Tr7MlSIqAwldVdIs4IyX4m>(g4xW^HhCvKjrHb})A?q$6 z)G?*&=ngjX{$?V1niQWV#Vstqh2^(0zm@rCS^wD!jI&K|GCVpWC1W!sa)rJx+-{?K zx6zesXItCZ)()z32f2KSQhAAL-AVJ>$#xjA(z|&7l_=%&-E4n1+uuX1doH-h82&am z0w?w(IYC;q`Mc%H3?fXAfTf?$V3FQSJbQ`fcf|QSYGWVGW*=p^pN?lg9g5E3V(B+o z{{TDY0d~xTw3mYe)$I1d)zV$wE>~nre*9R(`7YTx9Hms@2Q;D&5^5ChLM$9o(kPCy z#!*wlNgrj)#|ZeC0qmrY4OL2vW@$#V^a<8KL5e3Ci%#Nwws;g1tthp?QmF)%N+qyW zYJsg%3u4+x5L4*}#k^nK+cXwfkd_mJ9f9YdtA-_d-TjyZxd5^JLfgEkjv zBV}d`;Ar8FD!`y43w2gK1f5k6L02}?m5AeNM?qZeC`hP)gM{|Jpn~}d=DV|ecb4y| zjy&k84nOF{=e_v64<*ot66mMyGU%u32>SDWf3=%nfcDK`0Lu+hyAKAb8iPvGt0cX_ z#5I`rFJrmOv>yfxT)~jOCUgfKjlrO@6Pqrl6>@^?xc0GBuIgta)XWGnHG)iyqHsr% zxzQ9Rcl`uo+N(0gXr~Xxvi?}sXH*QvU0^yxFaqEk`V@pEF^CD|ae@Z-U?O!lk>n;( zH zTo!W4X$>V(Ly4p)krXB3lS7{p2q=Mo_g9h0Rfb6?SVg9OOilclOs{3XUdw*{VV_UG9;(MBnJk5T;g^h3F z{jKC|Yaio4gF4uPI?xgfi$sRMV#~i`i#sT*9h4>KQ~@JYz&TaGIaRQm2zRrcJ!F4R zTq*7{3NwA|CZ)KSHTJRwCsBb;qMTqa?e$H{;7y}lC*Y(hI7E{U*E7mVn31&(!Rj+x{c+FOkmvUCfm7E%^1ekksHWR*mwsr(LoowGVM&GCCXt}l}M(mN+iR0l!FJpU`k(t2_nHjF? z%nYYG88y?Ut3^=$73GIs-oHjR8RFl1&B#|+|_Nwb>S#|)4&*Xu|`79!-0S`07u zqR~yJnzE=ib(~DK4oET!wZCN+YJbZtqG>OpY2QZK+(z1qDZ9nwX$fVzgzYS2y=AV#N*#(qcK%61zgv4<#^Y=2pin3otU9Kjd zt106(yuXGzX6VT<^kmjjK5JQT9d)*j@_v|nKTP>@wJ;-5MO@2z_SW?pA~PFEX9I0% zV|y*f)x*pa%s)Z8n`i@EHO$n}#nkDbC9|3QZYIA^Q$9~uU^fmro{#%{#dWt_S#nw~ zCwKJd|_ZBiktWwa=QaPN-WuB+7pJ%Uq))1!)+D=Jt zrv!Eo=?-f0C02fk<#tlSJ1K!(EWeB8U!g``p(b}zV!L^N59{w?{nsd|*C?siiQ{!@ zVlR93Ub-w!9W(o>$^AOT%5W-{(W#h|+0Wj7h!%Rt;B+#FXc>pe*J1L-2$^Ao%p764 zBeb9o+0KXL_b4@ew9*9l%wgbBg*bPs8jev7$5`_?DITX9jx|)n3Bou*_D_<`Ns5)R zGsD=KiDKp#D#p;#@*!hr$T?ZaIawG}HH0x$Ls+b82)W1@I;w`yQ8k1u6i3)X)ex4b z8p0BFu3?#qKP*${8kVWJ!w$-9*nxQXnk?+7{DqxW2w`XD8398^z%WjFaWkfH!Vap2 zushrB&USmUot|u`7y0Q$dGt|-5cbj6?qPSd6Ri=41K8LAHa3Wj4Ps+_GZu2%6%J-& zgW1@ChK&u=aacHvg@&`y;i|cC1UVX^qoi;Y5svC(ra|E_bQHW+kdx`+%Z>GfoIQn{ zJ%!__z;Wef1QyDgW3(m|PT<1{)Gp^r;lyrQcmgnR(?KkpLSR#&V|a zK4)wRAI8!ge(f$?*QX_f+eX1~S`%_w6K*t(#lnp&yx}4#3~dW?F4={A=M-*c;ms^u z*RW--&xKnFL%g^&@+{$QCHvb5cN?E?k5ZP~#&UdX6!NW6$Thcc7t8I`5E<^FJ?nl4 zC)}eBG2BbL-)rpN3HMUm`)P~&jT)SAKkfe@JJ~_g$(-;Y&5w&-;k$;86TV9!93gIX z#7=lb9d7tB>3&SQACvBJ@_pRYbHd}~^90MEVEGf>+eM$sM4wytGe28_&#m#%b@q#E zer{c~)2jNSOHrGmc}2-dUGU*ta(WTI2&>)LCAGav>Ybma-YIHRY+3FXc5+&*W_5Ax zp%(Z=H}!T&QS0LCimsC{wvrvOealLIz1eJh60&wua}>AwOfgC2R7qm7vZmjy#$9N% zg;g{G#jyuYHY(#z5kBv+lKALm5`JxMWbwJQ*gL_RNfs4EA4}*2dV* zMc5#x#V(a6v+bp0u_e-idj!TZ>vr4fi+>kva*j3Cwt7ge+!i*0x2rX@xb!@$f6OYe z`pL-xzo@9^p^)Dk{IgRdgI)M!?l6wjr_=F2QIj}QpY{}??25@bciLBkWe>J&cblCY zU?*R)YbxxT)ppH)+qJVTyY@iBg9;MZUuk;>teNxB9MIuIBv-!2=qsF!Y{XP=%R(U_ zjhLF>kf~aZA5mP| zE-#~aAJl9h_beCZ4dyD2dzSpC<+G*d3wD?UB%Vm7U>AS3teFc@TJ1P_DRB3;ooi0% zoUmgU4@g9I+z4{scKj*`9l_x>nTfVi0=Y>Xc}xD;^x4w$Is>r#QvrM)0MEm}9fvVT zfU$oY4%6vmU3t2obEE!NzJlHkYJbAv6<-U)cL?g@IP#YKOYF0yWybE4;x3X&z=oa7 zl>eMYITryo{Qt;PLi?}&8@BTy(H3uSWA0KzCB0@lNKLw&LjXgpVTcgJo z<`I%;a03pn#CU{zAT0TlR3eg?kVd2>Fy8T&`^s&&37Nnlxc>qKCM2Ctk5DCn2Ol`W zSeqbZAaemD3NnLaH@OKf)sl&V)nv(>J@8B~%%ya-2IuQiYGDkI=TEA62!#ddew$*IuINxSA@yS58L&dNmUMQF_HjgaG8GjCUMA><^-LR<(rUv*b4 z%XZZ=?TJ4V13Did;mhYNuE*0OGU_M%b(jp>UV_8x5J%qnXF7*7rBPpO=+tJS zM?~IX2QcHK@=o|}xZw~fw?7-0!%`bTz8{3P;_y241Kg_xa;G@*R+5mhEK-Y7xtX4^V-c$XV{-ov|3LGpY--1UB(pD|aammMeeTq%7r zTuvhSAwu>@81ORXK|0jE;Gz53LkR*8$VCD0+y-(IN)F)g$|s?%+Xa4k9C_;z!aq|g z1382~fX+bhA7CQ#^W~_*Nb(p^w~-$~z%MAYR0nHaYg@gn!EzcVr}1*ST2APiyfRNQ z0`J~1aAM|BW_?3W7^XmDXJxsyWn%v6V{v$WFx6;8UJ2GF;_xc=;rxlYGAg@}jxkr- z8obwH$dE|gVA-i(TXXKra$XY$=Vq)@aOlr_z;X=KT8ox?ti<^j<|>YR)*rye*;3;s zG3bWERzYEZMNe6QvZ}9|7C_xk+;+Bax5cCilb4yI3`~S__mo`F8;RWk*d7A7*ONH^ zlesct|8GLil`0GPje$h+RdkdD1}~UGMl4JP3*QhHMi$_nashATo?ZnZGf3>4jXZrd z@~js;We_RGqT^)V0g+-dlRODLrwm=()wVOZu%Qc#-MR;d*B?fVe|rmwMQJ%M{vC(A z#&wZ^EXFIzcR<#RSMUY18KPk9(ObqICUg3vcfP&U^-l>yeRIxwFU#T58!7vFfZZ=D z9{7D|_f{OE6g4plYpbim_1LDfB9`U7F#Fr|d zrv%Ui0EF$DepVAi3lvbg4XDx4ggfFXTv&z*c=R`VhiDgHfS7t|@R~mdmeN8cRN6K7#88m^pwTyDY?=P>N>GtSO=LNhG1(Z zAznTMxn`6>k?SwOAikn*xY+C7bMA7zT4&H03E2}Z_kK657v-7;qHH0vW~{?@&k>Sl z6*?WhB;Sa13q&eIbnlu{h_t_m^%^7ATPW6CX~!rcZG@GUC2Fn^!M=ciRBBbb5~J0< z*UDJRI01}nbtyUDRkpl)+6SQ4MEIDP#Q8CE6~{g6Phjb6X*4G-lb#lWhkwPQsCzGp z6Sz#5U=iowREHJpRIkmtuxsg5F+RyI{#Cgv>tQ_uSeOBQ)lPLv7SR+9myZab){qlk zkW{&=>H*D;02Sgz^A=Ioi%!jY(W#M>Tqlr90O=-ilJ_YjbmrRY010#RMxFV|b02I@ z)6R?uo&A0M9HhHiDPeqBI}DVJU&Q>TJ66VZkZBgh&OXSTS!j>Wd(C`3Z2u*({~ry7 z>Ej&@u1t?!&mNsC)iI{)+sQVNX(|C1X(ejAp-a2axU5L9XWRp(do`LKnYelGlcF=v zEoNos7PG3{HTC3oMdZJdVa2N;WTrNAvgGGf%bH5W{m($&bijfgTHZ#Vb!hXl4sBlK zuS&(}11RWy@mD{t$8{LEFl@c1;bKAbI^#=HO?IliAUoAwV7vpy(LnVeP#qUkSih#U zQhkA{|5;Gw9%x;ZBaAgNm`^~hRE&PnDx9~Nt2pji@6r9ADUHP0Nq|29;FvpJ<*r37 z7&&}MstbUhIw0`ReL!$K##8c&5Us;+q+)3OZ;YpIEqFXNB!|QrO+~ZgsU_L*)RM-- zBv@dgW-^9J&si&6_}K{e$O!kvwpEVbSHXL7j9@O7x2l5IJ7uV!Tf@1Q8yeyLA#`e% zZI^$BE4+@kyyeENjip=5l|s|@Vkr5S!qSCc39BSf#zXZ?Nd)^F%2-;kGWr&*jJvYR zxT~=;jtX+Dt=0S*ei$EHB*`A{-}TOvjP87Ez36O3l6iKujb{0eG$e#(RiJ zhWH&+ef7|I#Fg90=ludl0B^-D~Sl*Jdykw5X}}ucrO+q+6F{eAnkO@Vqb0{j?TC9WD%{-B3eyE z=nUC`bhW#o9??gUjLtBy$PMd?d|_qROdiM%?;qfV<2-@Imls#Lk0~s?boQ$v!dQM^)&S>}Z@ zHawZkM0vZ6olNzasMt+Xr_oF3g7-M({i^u{I!gycC-0#LaemQUnUHlN2IldXE9D+1 z8zTlg*$j(7cJd>*|6A;uPwd+HcB)Cj`vKJD{noPm3JhBwM|+3~qOG4mo~KNGl1M{VUw zb82gbUHgZGmk@2O#rA+tpsgqCwe`o-`UY?lD-{^tZ6Vg)p<~5?zg8NzCOM{O?b1&oYo|Nqz zGemPw8qGaU&E-maMQd2~{p>@#rW#%Cm|eRtk@^Qw+d!yo6(%)5SNUP!<;ymTbKB6A zcL2$H>bk6_uB*ZdW)#2ttAWAnPC9MWa&x0m%en0W`!xt1z~Mb5FTv$KoA*>4dFxTg z`fRB#6g6CxsdDk!hY{Cr7a?vu2FN`}C|qD$vI5&O&fTo!F`@IDgFF@@8!ignbb|^ggJW?T{~K_XfRK?f#-3!>@ti+&Wg?!`p1f+MczO z?N#oUdJKG-HuCi8z?a&wev|E3zo~L%f&kwP-;yqd2+ZDtucTkU3PMM4cssMJTRXMe zJ`S@;w#Rs5kqne&0-%6=`Sn`kXbS1Jg{$M`tU6w6Hqe?lKWOcqFY3&`{KA~9bxc1QyMKuVhEQ= zu0}unNQCz*)KcN4FI3^3?S0g|Mp6I&x%KUj;+)LZw?CSlZ4F=X-|D`opfKII5|}V( zFUL1Q#8|derQdOSqaO!R^I~QC$F1YLyDJX%ZSmn!A!Mr8QUr? z#$Kf6BPWzuZLRM^&duy8qkaGivcF>?{`t67RE!Udtl>a!J%(c^4)3i?fKD8y_ttkH zhB-LAw_2m8UWCJY>kXX$!(4qIqu_-&ytf9AM#b;p<^-gj8>GlK`ulq{`vLCkMx(My n*3-yxMZ*1G3zj(@;zW;5)ZCBmJu*@Iwq>_Wq~1xSB(DA+)0jAT diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml deleted file mode 100644 index 5b7e389b..00000000 --- a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml +++ /dev/null @@ -1,599 +0,0 @@ - - - - Pachca - - - - Тип аудит-события - - - Пользователь успешно вошел в систему - - - Пользователь вышел из системы - - - Неудачная попытка двухфакторной аутентификации - - - Успешная двухфакторная аутентификация - - - Создана новая учетная запись пользователя - - - Учетная запись пользователя удалена - - - Роль пользователя была изменена - - - Данные пользователя обновлены - - - Создан новый тег - - - Тег удален - - - Пользователь добавлен в тег - - - Пользователь удален из тега - - - Создан новый чат - - - Чат переименован - - - Изменены права доступа к чату - - - Пользователь присоединился к чату - - - Пользователь покинул чат - - - Тег добавлен в чат - - - Тег удален из чата - - - Сообщение отредактировано - - - Сообщение удалено - - - Сообщение создано - - - Реакция добавлена - - - Реакция удалена - - - Тред создан - - - Создан новый токен доступа - - - Токен доступа обновлен - - - Токен доступа удален - - - Данные зашифрованы - - - Данные расшифрованы - - - Доступ к журналам аудита получен - - - Срабатывание правила DLP-системы - - - Поиск сотрудников через API - - - Поиск чатов через API - - - Поиск сообщений через API - - - Доступность чатов для пользователя - - - Чаты, где пользователь является участником - - - Все открытые чаты компании, вне зависимости от участия в них пользователя - - - Роль участника чата - - - Админ - - - Редактор (доступно только для каналов) - - - Участник или подписчик - - - Роль участника чата (с фильтром все) - - - Любая роль - - - Создатель - - - Админ - - - Редактор - - - Участник/подписчик - - - Тип чата - - - Канал или беседа - - - Тред - - - Тип данных дополнительного поля - - - Строковое значение - - - Числовое значение - - - Дата - - - Ссылка - - - Тип файла - - - Обычный файл - - - Изображение - - - Статус приглашения пользователя - - - Принято - - - Отправлено - - - Тип события webhook для участников - - - Добавление - - - Удаление - - - Тип сущности для сообщений - - - Беседа или канал - - - Тред - - - Пользователь - - - Скоуп доступа OAuth токена - - - Просмотр чатов и списка чатов - - - Создание новых чатов - - - Изменение настроек чата - - - Архивация и разархивация чатов - - - Выход из чатов - - - Просмотр участников чата - - - Добавление, изменение и удаление участников чата - - - Скачивание экспортов чата - - - Создание экспортов чата - - - Просмотр сообщений в чатах - - - Отправка сообщений - - - Редактирование сообщений - - - Удаление сообщений - - - Просмотр реакций на сообщения - - - Добавление и удаление реакций - - - Закрепление и открепление сообщений - - - Просмотр тредов (комментариев) - - - Создание тредов (комментариев) - - - Unfurl (разворачивание ссылок) - - - Просмотр информации о сотрудниках и списка сотрудников - - - Создание новых сотрудников - - - Редактирование данных сотрудника - - - Удаление сотрудников - - - Просмотр тегов - - - Создание, редактирование и удаление тегов - - - Изменение настроек бота - - - Просмотр информации о своем профиле - - - Просмотр статуса профиля - - - Изменение и удаление статуса профиля - - - Просмотр статуса сотрудника - - - Изменение и удаление статуса сотрудника - - - Просмотр дополнительных полей - - - Просмотр журнала аудита - - - Просмотр задач - - - Создание задач - - - Изменение задачи - - - Удаление задачи - - - Скачивание файлов - - - Загрузка файлов - - - Получение данных для загрузки файлов - - - Открытие форм (представлений) - - - Просмотр вебхуков - - - Создание и управление вебхуками - - - Просмотр лога вебхуков - - - Удаление записи в логе вебхука - - - Поиск сотрудников - - - Поиск чатов - - - Поиск сообщений - - - Тип события webhook для реакций - - - Создание - - - Удаление - - - Тип сущности для поиска - - - Пользователь - - - Задача - - - Сортировка результатов поиска - - - По релевантности - - - По алфавиту - - - Порядок сортировки - - - По возрастанию - - - По убыванию - - - Тип задачи - - - Позвонить контакту - - - Встреча - - - Простое напоминание - - - Событие - - - Написать письмо - - - Статус напоминания - - - Выполнено - - - Активно - - - Тип события webhook для пользователей - - - Приглашение - - - Подтверждение - - - Обновление - - - Приостановка - - - Активация - - - Удаление - - - Роль пользователя в системе - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Гость - - - Роль пользователя, допустимая при создании и редактировании. Роль `guest` недоступна для установки через API. - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Коды ошибок валидации - - - Обязательное поле (не может быть пустым) - - - Слишком длинное значение (пояснения вы получите в поле message) - - - Поле не соответствует правилам (пояснения вы получите в поле message) - - - Поле имеет непредусмотренное значение - - - Поле имеет недопустимое значение - - - Название для этого поля уже существует - - - Emoji статуса не может содержать значения отличные от Emoji символа - - - Объект не найден - - - Объект уже существует (пояснения вы получите в поле message) - - - Ошибка личного чата (пояснения вы получите в поле message) - - - Отображаемая ошибка (пояснения вы получите в поле message) - - - Действие запрещено - - - Выбран слишком большой диапазон дат - - - Некорректный URL вебхука - - - Достигнут лимит запросов - - - Превышен лимит активных сотрудников (пояснения вы получите в поле message) - - - Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций) - - - Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций) - - - Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций) - - - Ошибка выполнения запроса (пояснения вы получите в поле message) - - - Не удалось найти идентификатор события - - - Время жизни идентификатора события истекло - - - Обязательный параметр не передан - - - Недопустимое значение (не входит в список допустимых) - - - Значение неприменимо в данном контексте (пояснения вы получите в поле message) - - - Нельзя изменить свои собственные данные - - - Нельзя изменить данные владельца - - - Значение уже назначено - - - Недостаточно прав для выполнения действия (пояснения вы получите в поле message) - - - Доступ запрещён (недостаточно прав) - - - Доступ запрещён - - - Некорректные параметры запроса (пояснения вы получите в поле message) - - - Требуется оплата - - - Значение слишком короткое (пояснения вы получите в поле message) - - - Значение слишком длинное (пояснения вы получите в поле message) - - - Использовано зарезервированное системное слово (here, all) - - - Тип события webhook - - - Создание - - - Обновление - - - Удаление - - - diff --git a/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181c..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs deleted file mode 100644 index b53a36a2..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("Pachca")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyDescriptionAttribute("Official Pachca API SDK for .NET")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("0.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("0.0.0+63c81f1ef97ffdf701404195e2e23ef10c4df682")] -[assembly: System.Reflection.AssemblyProductAttribute("Pachca")] -[assembly: System.Reflection.AssemblyTitleAttribute("Pachca")] -[assembly: System.Reflection.AssemblyVersionAttribute("0.0.0.0")] -[assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/pachca/openapi")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache deleted file mode 100644 index ca3bdf21..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -b10e847b2e9024097da0b7fca6158b75db566b67cff2130365fb3427084e48b6 diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 97d70010..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetFrameworkIdentifier = .NETCoreApp -build_property.TargetFrameworkVersion = v8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = Pachca.Sdk -build_property.ProjectDir = /home/runner/work/openapi/openapi/sdk/csharp/generated/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache deleted file mode 100644 index c777a4a19ed989a6c315e0d4f83fd37a9a17a402..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmWIWc6a1qU|@JJUfi5^_SB|!hn~$cks%w;$4%i6pIEatM5%1!1mD%yCIQtD3mAb4 lW%V=ib5r$;O7rqki}dyKO4CzI^a~P`vlG)(i}eYa1OVMl9HjsN diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache deleted file mode 100644 index 522c513d..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache +++ /dev/null @@ -1 +0,0 @@ -8b4bedcecccf98bda5b99823825875d64935e1236dfda69b4f1366b3e253b906 diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt deleted file mode 100644 index 18b478ec..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt +++ /dev/null @@ -1,14 +0,0 @@ -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll deleted file mode 100644 index a747aca637ad2142f5f31a85002b06fb69c220b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236032 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 MKY;$be@_D604k^OJ^%m! diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb deleted file mode 100644 index 54cd6a6fc71f30e8bd26c05b3a2be0a628fb71ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70732 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 diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json deleted file mode 100644 index 6b6705b9..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json +++ /dev/null @@ -1 +0,0 @@ -{"documents":{"/home/runner/work/openapi/openapi/*":"https://raw.githubusercontent.com/pachca/openapi/63c81f1ef97ffdf701404195e2e23ef10c4df682/*"}} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml deleted file mode 100644 index 5b7e389b..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml +++ /dev/null @@ -1,599 +0,0 @@ - - - - Pachca - - - - Тип аудит-события - - - Пользователь успешно вошел в систему - - - Пользователь вышел из системы - - - Неудачная попытка двухфакторной аутентификации - - - Успешная двухфакторная аутентификация - - - Создана новая учетная запись пользователя - - - Учетная запись пользователя удалена - - - Роль пользователя была изменена - - - Данные пользователя обновлены - - - Создан новый тег - - - Тег удален - - - Пользователь добавлен в тег - - - Пользователь удален из тега - - - Создан новый чат - - - Чат переименован - - - Изменены права доступа к чату - - - Пользователь присоединился к чату - - - Пользователь покинул чат - - - Тег добавлен в чат - - - Тег удален из чата - - - Сообщение отредактировано - - - Сообщение удалено - - - Сообщение создано - - - Реакция добавлена - - - Реакция удалена - - - Тред создан - - - Создан новый токен доступа - - - Токен доступа обновлен - - - Токен доступа удален - - - Данные зашифрованы - - - Данные расшифрованы - - - Доступ к журналам аудита получен - - - Срабатывание правила DLP-системы - - - Поиск сотрудников через API - - - Поиск чатов через API - - - Поиск сообщений через API - - - Доступность чатов для пользователя - - - Чаты, где пользователь является участником - - - Все открытые чаты компании, вне зависимости от участия в них пользователя - - - Роль участника чата - - - Админ - - - Редактор (доступно только для каналов) - - - Участник или подписчик - - - Роль участника чата (с фильтром все) - - - Любая роль - - - Создатель - - - Админ - - - Редактор - - - Участник/подписчик - - - Тип чата - - - Канал или беседа - - - Тред - - - Тип данных дополнительного поля - - - Строковое значение - - - Числовое значение - - - Дата - - - Ссылка - - - Тип файла - - - Обычный файл - - - Изображение - - - Статус приглашения пользователя - - - Принято - - - Отправлено - - - Тип события webhook для участников - - - Добавление - - - Удаление - - - Тип сущности для сообщений - - - Беседа или канал - - - Тред - - - Пользователь - - - Скоуп доступа OAuth токена - - - Просмотр чатов и списка чатов - - - Создание новых чатов - - - Изменение настроек чата - - - Архивация и разархивация чатов - - - Выход из чатов - - - Просмотр участников чата - - - Добавление, изменение и удаление участников чата - - - Скачивание экспортов чата - - - Создание экспортов чата - - - Просмотр сообщений в чатах - - - Отправка сообщений - - - Редактирование сообщений - - - Удаление сообщений - - - Просмотр реакций на сообщения - - - Добавление и удаление реакций - - - Закрепление и открепление сообщений - - - Просмотр тредов (комментариев) - - - Создание тредов (комментариев) - - - Unfurl (разворачивание ссылок) - - - Просмотр информации о сотрудниках и списка сотрудников - - - Создание новых сотрудников - - - Редактирование данных сотрудника - - - Удаление сотрудников - - - Просмотр тегов - - - Создание, редактирование и удаление тегов - - - Изменение настроек бота - - - Просмотр информации о своем профиле - - - Просмотр статуса профиля - - - Изменение и удаление статуса профиля - - - Просмотр статуса сотрудника - - - Изменение и удаление статуса сотрудника - - - Просмотр дополнительных полей - - - Просмотр журнала аудита - - - Просмотр задач - - - Создание задач - - - Изменение задачи - - - Удаление задачи - - - Скачивание файлов - - - Загрузка файлов - - - Получение данных для загрузки файлов - - - Открытие форм (представлений) - - - Просмотр вебхуков - - - Создание и управление вебхуками - - - Просмотр лога вебхуков - - - Удаление записи в логе вебхука - - - Поиск сотрудников - - - Поиск чатов - - - Поиск сообщений - - - Тип события webhook для реакций - - - Создание - - - Удаление - - - Тип сущности для поиска - - - Пользователь - - - Задача - - - Сортировка результатов поиска - - - По релевантности - - - По алфавиту - - - Порядок сортировки - - - По возрастанию - - - По убыванию - - - Тип задачи - - - Позвонить контакту - - - Встреча - - - Простое напоминание - - - Событие - - - Написать письмо - - - Статус напоминания - - - Выполнено - - - Активно - - - Тип события webhook для пользователей - - - Приглашение - - - Подтверждение - - - Обновление - - - Приостановка - - - Активация - - - Удаление - - - Роль пользователя в системе - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Гость - - - Роль пользователя, допустимая при создании и редактировании. Роль `guest` недоступна для установки через API. - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Коды ошибок валидации - - - Обязательное поле (не может быть пустым) - - - Слишком длинное значение (пояснения вы получите в поле message) - - - Поле не соответствует правилам (пояснения вы получите в поле message) - - - Поле имеет непредусмотренное значение - - - Поле имеет недопустимое значение - - - Название для этого поля уже существует - - - Emoji статуса не может содержать значения отличные от Emoji символа - - - Объект не найден - - - Объект уже существует (пояснения вы получите в поле message) - - - Ошибка личного чата (пояснения вы получите в поле message) - - - Отображаемая ошибка (пояснения вы получите в поле message) - - - Действие запрещено - - - Выбран слишком большой диапазон дат - - - Некорректный URL вебхука - - - Достигнут лимит запросов - - - Превышен лимит активных сотрудников (пояснения вы получите в поле message) - - - Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций) - - - Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций) - - - Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций) - - - Ошибка выполнения запроса (пояснения вы получите в поле message) - - - Не удалось найти идентификатор события - - - Время жизни идентификатора события истекло - - - Обязательный параметр не передан - - - Недопустимое значение (не входит в список допустимых) - - - Значение неприменимо в данном контексте (пояснения вы получите в поле message) - - - Нельзя изменить свои собственные данные - - - Нельзя изменить данные владельца - - - Значение уже назначено - - - Недостаточно прав для выполнения действия (пояснения вы получите в поле message) - - - Доступ запрещён (недостаточно прав) - - - Доступ запрещён - - - Некорректные параметры запроса (пояснения вы получите в поле message) - - - Требуется оплата - - - Значение слишком короткое (пояснения вы получите в поле message) - - - Значение слишком длинное (пояснения вы получите в поле message) - - - Использовано зарезервированное системное слово (here, all) - - - Тип события webhook - - - Создание - - - Обновление - - - Удаление - - - diff --git a/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll deleted file mode 100644 index 1d7e1776d9437c2e8118c49c4d86f72d3c3185a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97792 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 G;Qs&wm#|>~ diff --git a/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll deleted file mode 100644 index 1d7e1776d9437c2e8118c49c4d86f72d3c3185a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97792 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 G;Qs&wm#|>~ diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json b/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json deleted file mode 100644 index 39d648dd..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "format": 1, - "restore": { - "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj": {} - }, - "projects": { - "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj": { - "version": "0.0.0", - "restore": { - "projectUniqueName": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "projectName": "Pachca.Sdk", - "projectPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "packagesPath": "/home/runner/.nuget/packages/", - "outputPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/", - "projectStyle": "PackageReference", - "configFilePaths": [ - "/home/runner/.nuget/NuGet/NuGet.Config" - ], - "originalTargetFrameworks": [ - "net8.0" - ], - "sources": { - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "10.0.100" - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "downloadDependencies": [ - { - "name": "Microsoft.AspNetCore.App.Ref", - "version": "[8.0.23, 8.0.23]" - }, - { - "name": "Microsoft.NETCore.App.Ref", - "version": "[8.0.23, 8.0.23]" - } - ], - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.102/PortableRuntimeIdentifierGraph.json" - } - } - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props b/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props deleted file mode 100644 index 2098f6f9..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - True - NuGet - $(MSBuildThisFileDirectory)project.assets.json - /home/runner/.nuget/packages/ - /home/runner/.nuget/packages/ - PackageReference - 7.0.0 - - - - - \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets b/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets deleted file mode 100644 index 3dc06ef3..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/sdk/csharp/generated/obj/project.assets.json b/sdk/csharp/generated/obj/project.assets.json deleted file mode 100644 index d721d499..00000000 --- a/sdk/csharp/generated/obj/project.assets.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "version": 3, - "targets": { - "net8.0": {} - }, - "libraries": {}, - "projectFileDependencyGroups": { - "net8.0": [] - }, - "packageFolders": { - "/home/runner/.nuget/packages/": {} - }, - "project": { - "version": "0.0.0", - "restore": { - "projectUniqueName": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "projectName": "Pachca.Sdk", - "projectPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "packagesPath": "/home/runner/.nuget/packages/", - "outputPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/", - "projectStyle": "PackageReference", - "configFilePaths": [ - "/home/runner/.nuget/NuGet/NuGet.Config" - ], - "originalTargetFrameworks": [ - "net8.0" - ], - "sources": { - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "10.0.100" - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "downloadDependencies": [ - { - "name": "Microsoft.AspNetCore.App.Ref", - "version": "[8.0.23, 8.0.23]" - }, - { - "name": "Microsoft.NETCore.App.Ref", - "version": "[8.0.23, 8.0.23]" - } - ], - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.102/PortableRuntimeIdentifierGraph.json" - } - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/project.nuget.cache b/sdk/csharp/generated/obj/project.nuget.cache deleted file mode 100644 index 215873fa..00000000 --- a/sdk/csharp/generated/obj/project.nuget.cache +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": 2, - "dgSpecHash": "21O9MPH9XcE=", - "success": true, - "projectFilePath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "expectedPackageFiles": [ - "/home/runner/.nuget/packages/microsoft.netcore.app.ref/8.0.23/microsoft.netcore.app.ref.8.0.23.nupkg.sha512", - "/home/runner/.nuget/packages/microsoft.aspnetcore.app.ref/8.0.23/microsoft.aspnetcore.app.ref.8.0.23.nupkg.sha512" - ], - "logs": [] -} \ No newline at end of file From 3fa5de43bdc0b900a169c23b3c1ad0eb6742311d Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 15:42:10 +0100 Subject: [PATCH 2/8] feat: support injectable sdk service mocks --- packages/generator/src/lang/csharp.ts | 87 +- packages/generator/src/lang/go.ts | 125 ++- packages/generator/src/lang/kotlin.ts | 113 ++- packages/generator/src/lang/python.ts | 92 +- packages/generator/src/lang/swift.ts | 90 +- packages/generator/src/lang/typescript.ts | 59 +- packages/generator/src/naming.ts | 10 + .../tests/crud/snapshots/cs/Client.cs | 81 +- .../tests/crud/snapshots/go/client.go | 83 +- .../tests/crud/snapshots/kt/Client.kt | 67 +- .../tests/crud/snapshots/py/client.py | 57 +- .../tests/crud/snapshots/swift/Client.swift | 63 +- .../tests/crud/snapshots/ts/client.ts | 54 +- .../tests/edge-cases/snapshots/cs/Client.cs | 57 +- .../tests/edge-cases/snapshots/go/client.go | 71 +- .../tests/edge-cases/snapshots/kt/Client.kt | 45 +- .../tests/edge-cases/snapshots/py/client.py | 38 +- .../edge-cases/snapshots/swift/Client.swift | 49 +- .../tests/edge-cases/snapshots/ts/client.ts | 37 +- .../multi-path-params/snapshots/cs/Client.cs | 50 +- .../multi-path-params/snapshots/go/client.go | 55 +- .../multi-path-params/snapshots/kt/Client.kt | 40 +- .../multi-path-params/snapshots/py/client.py | 37 +- .../snapshots/swift/Client.swift | 39 +- .../multi-path-params/snapshots/ts/client.ts | 30 +- .../tests/patch/snapshots/cs/Client.cs | 28 +- .../tests/patch/snapshots/go/client.go | 41 +- .../tests/patch/snapshots/kt/Client.kt | 20 +- .../tests/patch/snapshots/py/client.py | 21 +- .../tests/patch/snapshots/swift/Client.swift | 27 +- .../tests/patch/snapshots/ts/client.ts | 18 +- .../tests/record/snapshots/cs/Client.cs | 28 +- .../tests/record/snapshots/go/client.go | 41 +- .../tests/record/snapshots/kt/Client.kt | 20 +- .../tests/record/snapshots/py/client.py | 21 +- .../tests/record/snapshots/swift/Client.swift | 27 +- .../tests/record/snapshots/ts/client.ts | 18 +- .../tests/redirect/snapshots/cs/Client.cs | 25 +- .../tests/redirect/snapshots/go/client.go | 41 +- .../tests/redirect/snapshots/kt/Client.kt | 20 +- .../tests/redirect/snapshots/py/client.py | 20 +- .../redirect/snapshots/swift/Client.swift | 27 +- .../tests/redirect/snapshots/ts/client.ts | 18 +- .../tests/search/snapshots/cs/Client.cs | 49 +- .../tests/search/snapshots/go/client.go | 48 +- .../tests/search/snapshots/kt/Client.kt | 43 +- .../tests/search/snapshots/py/client.py | 26 +- .../tests/search/snapshots/swift/Client.swift | 33 +- .../tests/search/snapshots/ts/client.ts | 24 +- .../tests/unwrap/snapshots/cs/Client.cs | 53 +- .../tests/unwrap/snapshots/go/client.go | 71 +- .../tests/unwrap/snapshots/kt/Client.kt | 41 +- .../tests/unwrap/snapshots/py/client.py | 38 +- .../tests/unwrap/snapshots/swift/Client.swift | 49 +- .../tests/unwrap/snapshots/ts/client.ts | 37 +- .../tests/upload/snapshots/cs/Client.cs | 35 +- .../tests/upload/snapshots/go/client.go | 48 +- .../tests/upload/snapshots/kt/Client.kt | 26 +- .../tests/upload/snapshots/py/client.py | 25 +- .../tests/upload/snapshots/swift/Client.swift | 33 +- .../tests/upload/snapshots/ts/client.ts | 24 +- sdk/csharp/generated/Client.cs | 921 +++++++++++++++--- sdk/go/generated/client.go | 815 +++++++++++++--- .../src/main/kotlin/com/pachca/Client.kt | 904 +++++++++++++---- sdk/python/generated/pachca/client.py | 565 ++++++++++- .../Pachca/GeneratedSources/Client.swift | 633 +++++++++--- sdk/typescript/src/generated/client.ts | 643 +++++++++--- 67 files changed, 6056 insertions(+), 1118 deletions(-) diff --git a/packages/generator/src/lang/csharp.ts b/packages/generator/src/lang/csharp.ts index 5d76533f..265decf0 100644 --- a/packages/generator/src/lang/csharp.ts +++ b/packages/generator/src/lang/csharp.ts @@ -18,6 +18,7 @@ import { snakeToUpperSnake, tagToProperty, tagToServiceName, + serviceToImplName, } from '../naming.js'; const CSHARP_KEYWORDS = new Set([ @@ -553,13 +554,26 @@ function emitService( globalHasApiError: boolean, ): void { const serviceName = tagToServiceName(svc.tag); + const implName = serviceToImplName(serviceName); - lines.push(`public sealed class ${serviceName}`); + lines.push(`public abstract class ${serviceName}`); + lines.push('{'); + for (let i = 0; i < svc.operations.length; i++) { + lines.push(''); + emitThrowingOperation(lines, svc.operations[i], ir); + if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { + lines.push(''); + emitThrowingPaginationMethod(lines, svc.operations[i], ir); + } + } + lines.push('}'); + lines.push(''); + lines.push(`public sealed class ${implName} : ${serviceName}`); lines.push('{'); lines.push(' private readonly string _baseUrl;'); lines.push(' private readonly HttpClient _client;'); lines.push(''); - lines.push(` internal ${serviceName}(string baseUrl, HttpClient client)`); + lines.push(` internal ${implName}(string baseUrl, HttpClient client)`); lines.push(' {'); lines.push(' _baseUrl = baseUrl;'); lines.push(' _client = client;'); @@ -577,7 +591,7 @@ function emitService( lines.push('}'); } -function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { +function emitPaginationMethod(lines: string[], op: IROperation, ir: IR, modifier = 'public override'): void { const indent = ' '; const indent2 = ' '; const itemType = csClientTypeRef(op.successResponse.dataRef ?? 'object'); @@ -599,7 +613,7 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { const methodName = `${snakeToPascal(op.methodName)}AllAsync`; - lines.push(`${indent}public async System.Threading.Tasks.Task> ${methodName}(`); + lines.push(`${indent}${modifier} async System.Threading.Tasks.Task> ${methodName}(`); for (let i = 0; i < params.length; i++) { const comma = i < params.length - 1 ? ',' : ')'; lines.push(`${indent2}${params[i]}${comma}`); @@ -648,6 +662,7 @@ function emitOperation( op: IROperation, ir: IR, globalHasApiError: boolean, + modifier = 'public override', ): void { const indent = ' '; const indent2 = ' '; @@ -662,11 +677,11 @@ function emitOperation( if (op.deprecated) lines.push(`${indent}[Obsolete("This method is deprecated")]`); if (params.length === 0) { - lines.push(`${indent}public async ${taskType} ${methodName}(CancellationToken cancellationToken = default)`); + lines.push(`${indent}${modifier} async ${taskType} ${methodName}(CancellationToken cancellationToken = default)`); } else if (params.length === 1) { - lines.push(`${indent}public async ${taskType} ${methodName}(${params[0]}, CancellationToken cancellationToken = default)`); + lines.push(`${indent}${modifier} async ${taskType} ${methodName}(${params[0]}, CancellationToken cancellationToken = default)`); } else { - lines.push(`${indent}public async ${taskType} ${methodName}(`); + lines.push(`${indent}${modifier} async ${taskType} ${methodName}(`); for (const p of params) { lines.push(`${indent2}${p},`); } @@ -679,6 +694,52 @@ function emitOperation( lines.push(`${indent}}`); } +function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { + const returnType = getReturnType(op, ir); + const taskType = returnType ? `System.Threading.Tasks.Task<${returnType}>` : 'System.Threading.Tasks.Task'; + const params = buildMethodParams(op, ir); + const methodName = `${snakeToPascal(op.methodName)}Async`; + const indent = ' '; + const indent2 = ' '; + if (op.deprecated) lines.push(`${indent}[Obsolete("This method is deprecated")]`); + if (params.length === 0) { + lines.push(`${indent}public virtual async ${taskType} ${methodName}(CancellationToken cancellationToken = default)`); + } else if (params.length === 1) { + lines.push(`${indent}public virtual async ${taskType} ${methodName}(${params[0]}, CancellationToken cancellationToken = default)`); + } else { + lines.push(`${indent}public virtual async ${taskType} ${methodName}(`); + for (const p of params) lines.push(`${indent2}${p},`); + lines.push(`${indent2}CancellationToken cancellationToken = default)`); + } + lines.push(`${indent}{`); + lines.push(`${indent2}throw new NotImplementedException(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)});`); + lines.push(`${indent}}`); +} + +function emitThrowingPaginationMethod(lines: string[], op: IROperation, ir: IR): void { + const indent = ' '; + const indent2 = ' '; + const itemType = csClientTypeRef(op.successResponse.dataRef ?? 'object'); + const params: string[] = []; + if (op.externalUrl) params.push(`string ${paramSdkName(op.externalUrl)}`); + for (const p of op.pathParams) params.push(`${csType(p.type)} ${paramSdkName(p.sdkName)}`); + for (const p of op.queryParams) { + if (p.name === 'cursor') continue; + const typeName = csType(p.type); + params.push(p.required ? `${typeName} ${paramSdkName(p.sdkName)}` : `${typeName}? ${paramSdkName(p.sdkName)} = null`); + } + params.push('CancellationToken cancellationToken = default'); + const methodName = `${snakeToPascal(op.methodName)}AllAsync`; + lines.push(`${indent}public virtual async System.Threading.Tasks.Task> ${methodName}(`); + for (let i = 0; i < params.length; i++) { + const comma = i < params.length - 1 ? ',' : ')'; + lines.push(`${indent2}${params[i]}${comma}`); + } + lines.push(`${indent}{`); + lines.push(`${indent2}throw new NotImplementedException(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)});`); + lines.push(`${indent}}`); +} + function getReturnType( op: IROperation, ir: IR, @@ -947,6 +1008,8 @@ function emitPachcaClient( lines.push('{'); lines.push(' private readonly HttpClient _client;'); lines.push(''); + lines.push(' public sealed class Services'); + lines.push(' {'); // Service properties const serviceEntries = ir.services @@ -956,13 +1019,19 @@ function emitPachcaClient( })) .sort((a, b) => a.propName.localeCompare(b.propName)); + for (const s of serviceEntries) { + lines.push(` public ${s.className}? ${s.propName} { get; init; }`); + } + lines.push(' }'); + lines.push(''); for (const s of serviceEntries) { lines.push(` public ${s.className} ${s.propName} { get; }`); } lines.push(''); - lines.push(` public PachcaClient(string token, string baseUrl${csDefault})`); + lines.push(` public PachcaClient(string token, string baseUrl${csDefault}, Services? services = null)`); lines.push(' {'); + lines.push(' services ??= new Services();'); if (hasRedirect) { lines.push(' var handler = new SocketsHttpHandler'); @@ -979,7 +1048,7 @@ function emitPachcaClient( lines.push(''); for (const s of serviceEntries) { - lines.push(` ${s.propName} = new ${s.className}(baseUrl, _client);`); + lines.push(` ${s.propName} = services.${s.propName} ?? new ${serviceToImplName(s.className)}(baseUrl, _client);`); } lines.push(' }'); diff --git a/packages/generator/src/lang/go.ts b/packages/generator/src/lang/go.ts index bc380d35..19046a35 100644 --- a/packages/generator/src/lang/go.ts +++ b/packages/generator/src/lang/go.ts @@ -11,7 +11,7 @@ import { type IRResponseType, } from '../ir.js'; import { buildModelIndex, collectTypeRefs, type GeneratedFile, type GenerateOptions, type LanguageGenerator } from './types.js'; -import { snakeToCamel, snakeToPascal, tagToServiceName } from '../naming.js'; +import { snakeToCamel, snakeToPascal, tagToServiceName, serviceToImplName, serviceToStubName } from '../naming.js'; function upperFirst(s: string): string { if (!s) return s; @@ -403,7 +403,7 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { } if (op.deprecated) lines.push(`// Deprecated: ${goMethodName(op)} is deprecated.`); - lines.push(`func (s *${tagToServiceName(op.tag)}) ${goMethodName(op)}(${args.join(', ')}) ${goReturn(op, ir)} {`); + lines.push(`func (s *${serviceToImplName(tagToServiceName(op.tag))}) ${goMethodName(op)}(${args.join(', ')}) ${goReturn(op, ir)} {`); const { fmt: fmtPath, args: pathArgs } = goPathFormat(op.path, op); const urlExpr = op.externalUrl @@ -583,7 +583,7 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { const itemType = op.successResponse.dataRef ?? 'any'; const paramsName = `${upperFirst(op.methodName)}Params`; - const svcName = tagToServiceName(op.tag); + const svcName = serviceToImplName(tagToServiceName(op.tag)); const args: string[] = ['ctx context.Context']; if (op.externalUrl) args.push(`${op.externalUrl} string`); @@ -627,6 +627,82 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { lines.push('}'); } +function emitServiceContract(lines: string[], svc: IRService, ir: IR): void { + const serviceName = tagToServiceName(svc.tag); + const stubName = serviceToStubName(serviceName); + lines.push(`type ${serviceName} interface {`); + for (const op of svc.operations) { + const args: string[] = ['ctx context.Context']; + if (op.externalUrl) args.push(`${op.externalUrl} string`); + for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)} ${goType(p.type)}`); + if (op.requestBody) { + const rb = op.requestBody; + if (shouldUnwrapBody(rb)) args.push(`${snakeToCamel(rb.unwrapField!.name)} ${goType(rb.unwrapField!.type)}`); + else if (rb.schemaRef) args.push(`request ${rb.schemaRef}`); + } + if (op.queryParams.length > 0) { + const pName = `${upperFirst(op.methodName)}Params`; + const hasReq = op.queryParams.some((p) => p.required); + args.push(`${snakeToCamel('params')} ${hasReq ? pName : `*${pName}`}`); + } + lines.push(`\t${goMethodName(op)}(${args.join(', ')}) ${goReturn(op, ir)}`); + if (op.isPaginated && op.successResponse.dataRef) { + const itemType = op.successResponse.dataRef ?? 'any'; + const pageArgs: string[] = ['ctx context.Context']; + if (op.externalUrl) pageArgs.push(`${op.externalUrl} string`); + for (const p of op.pathParams) pageArgs.push(`${snakeToCamel(p.sdkName)} ${goType(p.type)}`); + if (op.queryParams.length > 0) pageArgs.push(`params *${upperFirst(op.methodName)}Params`); + lines.push(`\t${goMethodName(op)}All(${pageArgs.join(', ')}) ([]${itemType}, error)`); + } + } + lines.push('}'); + lines.push(''); + lines.push(`type ${stubName} struct{}`); + lines.push(''); + for (const op of svc.operations) { + emitStubMethod(lines, op, ir); + lines.push(''); + if (op.isPaginated && op.successResponse.dataRef) { + emitStubPaginationMethod(lines, op); + lines.push(''); + } + } +} + +function emitStubMethod(lines: string[], op: IROperation, ir: IR): void { + const stubName = serviceToStubName(tagToServiceName(op.tag)); + const args: string[] = ['ctx context.Context']; + if (op.externalUrl) args.push(`${op.externalUrl} string`); + for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)} ${goType(p.type)}`); + if (op.requestBody) { + const rb = op.requestBody; + if (shouldUnwrapBody(rb)) args.push(`${snakeToCamel(rb.unwrapField!.name)} ${goType(rb.unwrapField!.type)}`); + else if (rb.schemaRef) args.push(`request ${rb.schemaRef}`); + } + if (op.queryParams.length > 0) { + const pName = `${upperFirst(op.methodName)}Params`; + const hasReq = op.queryParams.some((p) => p.required); + args.push(`${snakeToCamel('params')} ${hasReq ? pName : `*${pName}`}`); + } + lines.push(`func (s *${stubName}) ${goMethodName(op)}(${args.join(', ')}) ${goReturn(op, ir)} {`); + if (op.successResponse.isRedirect) lines.push(`\treturn "", fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); + else if (!op.successResponse.hasBody) lines.push(`\treturn fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); + else lines.push(`\treturn nil, fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); + lines.push('}'); +} + +function emitStubPaginationMethod(lines: string[], op: IROperation): void { + const stubName = serviceToStubName(tagToServiceName(op.tag)); + const itemType = op.successResponse.dataRef ?? 'any'; + const args: string[] = ['ctx context.Context']; + if (op.externalUrl) args.push(`${op.externalUrl} string`); + for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)} ${goType(p.type)}`); + if (op.queryParams.length > 0) args.push(`params *${upperFirst(op.methodName)}Params`); + lines.push(`func (s *${stubName}) ${goMethodName(op)}All(${args.join(', ')}) ([]${itemType}, error) {`); + lines.push(`\treturn nil, fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)})`); + lines.push('}'); +} + function generateClient(ir: IR): string { const lines: string[] = []; lines.push('package pachca'); @@ -694,7 +770,9 @@ function generateClient(ir: IR): string { for (const s of ir.services) { const cls = tagToServiceName(s.tag); - lines.push(`type ${cls} struct {`); + const implName = serviceToImplName(cls); + emitServiceContract(lines, s, ir); + lines.push(`type ${implName} struct {`); lines.push('\tbaseURL string'); lines.push('\tclient *http.Client'); lines.push('}'); @@ -713,21 +791,44 @@ function generateClient(ir: IR): string { const fields = ir.services .map((s) => ({ f: goServiceField(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.f.localeCompare(b.f)); - const clientRows = fields.map((f) => [f.f, `*${f.cls}`]); + const clientRows = fields.map((f) => [f.f, f.cls]); for (const line of goAligned(clientRows)) lines.push(line); lines.push('}'); lines.push(''); + lines.push('type clientConfig struct {'); + if (ir.baseUrl) { + lines.push('\tbaseURL string'); + } else { + lines.push('\tbaseURL string'); + } + for (const f of fields) lines.push(`\t${f.f.charAt(0).toLowerCase() + f.f.slice(1)} ${f.cls}`); + lines.push('}'); + lines.push(''); + lines.push('type ClientOption func(*clientConfig)'); + lines.push(''); if (ir.baseUrl) { lines.push(`const DefaultBaseURL = ${JSON.stringify(ir.baseUrl)}`); lines.push(''); } - lines.push('func NewPachcaClient(token string, baseURL ...string) *PachcaClient {'); + lines.push('func WithBaseURL(baseURL string) ClientOption {'); + lines.push('\treturn func(cfg *clientConfig) { cfg.baseURL = baseURL }'); + lines.push('}'); + lines.push(''); + for (const f of fields) { + lines.push(`func With${f.f}(service ${f.cls}) ClientOption {`); + lines.push(`\treturn func(cfg *clientConfig) { cfg.${f.f.charAt(0).toLowerCase() + f.f.slice(1)} = service }`); + lines.push('}'); + lines.push(''); + } + lines.push('func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient {'); if (ir.baseUrl) { - lines.push(`\turl := DefaultBaseURL`); + lines.push(`\tcfg := clientConfig{baseURL: DefaultBaseURL}`); } else { - lines.push('\turl := ""'); + lines.push('\tcfg := clientConfig{}'); } - lines.push('\tif len(baseURL) > 0 { url = baseURL[0] }'); + lines.push('\tfor _, opt := range opts {'); + lines.push('\t\topt(&cfg)'); + lines.push('\t}'); lines.push('\tclient := &http.Client{'); lines.push('\t\tTransport: &authTransport{token: token, base: http.DefaultTransport},'); if (needErrors) { @@ -738,7 +839,11 @@ function generateClient(ir: IR): string { lines.push('\t}'); lines.push('\treturn &PachcaClient{'); const maxField = Math.max(...fields.map((f) => f.f.length)); - for (const f of fields) lines.push(`\t\t${f.f.padEnd(maxField)}: &${f.cls}{baseURL: url, client: client},`); + for (const f of fields) { + const cfgField = `cfg.${f.f.charAt(0).toLowerCase() + f.f.slice(1)}`; + const impl = `&${serviceToImplName(f.cls)}{baseURL: cfg.baseURL, client: client}`; + lines.push(`\t\t${f.f.padEnd(maxField)}: func() ${f.cls} { if ${cfgField} != nil { return ${cfgField} }; return ${impl} }(),`); + } lines.push('\t}'); lines.push('}'); lines.push(''); diff --git a/packages/generator/src/lang/kotlin.ts b/packages/generator/src/lang/kotlin.ts index d0b94f01..316c1c7d 100644 --- a/packages/generator/src/lang/kotlin.ts +++ b/packages/generator/src/lang/kotlin.ts @@ -17,6 +17,7 @@ import { snakeToUpperSnake, tagToProperty, tagToServiceName, + serviceToImplName, } from '../naming.js'; const KOTLIN_KEYWORDS = new Set([ @@ -397,11 +398,23 @@ function emitService( globalHasApiError: boolean, ): void { const serviceName = tagToServiceName(svc.tag); + const implName = serviceToImplName(serviceName); - lines.push(`class ${serviceName} internal constructor(`); + lines.push(`abstract class ${serviceName} {`); + for (let i = 0; i < svc.operations.length; i++) { + if (i > 0) lines.push(''); + emitThrowingOperation(lines, svc.operations[i], ir); + if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { + lines.push(''); + emitThrowingPaginationMethod(lines, svc.operations[i], ir); + } + } + lines.push('}'); + lines.push(''); + lines.push(`class ${implName} internal constructor(`); lines.push(' private val baseUrl: String,'); lines.push(' private val client: HttpClient,'); - lines.push(') {'); + lines.push(`) : ${serviceName}() {`); for (let i = 0; i < svc.operations.length; i++) { if (i > 0) lines.push(''); @@ -415,6 +428,58 @@ function emitService( lines.push('}'); } +function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { + const indent = ' '; + const indent2 = ' '; + const returnType = getReturnType(op, ir); + const returnSuffix = returnType ? `: ${returnType}` : ''; + const params = buildMethodParams(op, ir); + + if (op.deprecated) lines.push(`${indent}@Deprecated("This method is deprecated")`); + if (params.length === 0) { + lines.push(`${indent}open suspend fun ${op.methodName}()${returnSuffix} {`); + } else if (params.length === 1) { + lines.push(`${indent}open suspend fun ${op.methodName}(${params[0]})${returnSuffix} {`); + } else if (params.length <= 2) { + lines.push(`${indent}open suspend fun ${op.methodName}(${params.join(', ')})${returnSuffix} {`); + } else { + lines.push(`${indent}open suspend fun ${op.methodName}(`); + for (const p of params) lines.push(`${indent2}${p},`); + lines.push(`${indent})${returnSuffix} {`); + } + lines.push(`${indent2}throw NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); + lines.push(`${indent}}`); +} + +function emitThrowingPaginationMethod(lines: string[], op: IROperation, ir: IR): void { + const indent = ' '; + const indent2 = ' '; + const itemType = op.successResponse.dataRef ?? 'Any'; + const params: string[] = []; + if (op.externalUrl) params.push(`${op.externalUrl}: String`); + for (const p of op.pathParams) params.push(`${p.sdkName}: ${ktType(p.type)}`); + for (const p of op.queryParams) { + if (p.name === 'cursor') continue; + const typeName = ktType(p.type); + params.push(p.required ? `${p.sdkName}: ${typeName}` : `${p.sdkName}: ${typeName}? = null`); + } + + if (params.length <= 2) { + lines.push(`${indent}open suspend fun ${op.methodName}All(${params.join(', ')}): List<${itemType}> {`); + } else { + lines.push(`${indent}open suspend fun ${op.methodName}All(`); + for (const p of params) lines.push(`${indent2}${p},`); + lines.push(`${indent}): List<${itemType}> {`); + } + lines.push(`${indent2}throw NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)})`); + lines.push(`${indent}}`); +} + +function stripKotlinDefaultValue(param: string): string { + const index = param.indexOf(' = '); + return index === -1 ? param : param.slice(0, index); +} + function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { const indent = ' '; const indent2 = ' '; @@ -429,12 +494,13 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { const typeName = ktType(p.type); params.push(p.required ? `${p.sdkName}: ${typeName}` : `${p.sdkName}: ${typeName}? = null`); } + const overrideParams = params.map(stripKotlinDefaultValue); - if (params.length <= 2) { - lines.push(`${indent}suspend fun ${op.methodName}All(${params.join(', ')}): List<${itemType}> {`); + if (overrideParams.length <= 2) { + lines.push(`${indent}override suspend fun ${op.methodName}All(${overrideParams.join(', ')}): List<${itemType}> {`); } else { - lines.push(`${indent}suspend fun ${op.methodName}All(`); - for (const p of params) lines.push(`${indent2}${p},`); + lines.push(`${indent}override suspend fun ${op.methodName}All(`); + for (const p of overrideParams) lines.push(`${indent2}${p},`); lines.push(`${indent}): List<${itemType}> {`); } @@ -476,21 +542,21 @@ function emitOperation( const returnType = getReturnType(op, ir); const returnSuffix = returnType ? `: ${returnType}` : ''; - const params = buildMethodParams(op, ir); + const params = buildMethodParams(op, ir).map(stripKotlinDefaultValue); if (op.deprecated) lines.push(`${indent}@Deprecated("This method is deprecated")`); if (params.length === 0) { - lines.push(`${indent}suspend fun ${op.methodName}()${returnSuffix} {`); + lines.push(`${indent}override suspend fun ${op.methodName}()${returnSuffix} {`); } else if (params.length === 1) { lines.push( - `${indent}suspend fun ${op.methodName}(${params[0]})${returnSuffix} {`, + `${indent}override suspend fun ${op.methodName}(${params[0]})${returnSuffix} {`, ); } else if (params.length <= 2) { lines.push( - `${indent}suspend fun ${op.methodName}(${params.join(', ')})${returnSuffix} {`, + `${indent}override suspend fun ${op.methodName}(${params.join(', ')})${returnSuffix} {`, ); } else { - lines.push(`${indent}suspend fun ${op.methodName}(`); + lines.push(`${indent}override suspend fun ${op.methodName}(`); for (const p of params) { lines.push(`${indent2}${p},`); } @@ -779,7 +845,21 @@ function emitPachcaClient( hasRedirect: boolean, ): void { const ktDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(`class PachcaClient(token: String, baseUrl: String${ktDefault}) : Closeable {`); + const serviceEntries = ir.services + .map((svc) => ({ + propName: tagToProperty(svc.tag), + className: tagToServiceName(svc.tag), + })) + .sort((a, b) => a.propName.localeCompare(b.propName)); + + lines.push('data class PachcaServices('); + for (const [index, s] of serviceEntries.entries()) { + const suffix = index < serviceEntries.length - 1 ? ',' : ''; + lines.push(` val ${s.propName}: ${s.className}? = null${suffix}`); + } + lines.push(')'); + lines.push(''); + lines.push(`class PachcaClient(token: String, baseUrl: String${ktDefault}, services: PachcaServices = PachcaServices()) : Closeable {`); lines.push(' private val client = HttpClient {'); lines.push(' expectSuccess = false'); if (hasRedirect) { @@ -802,15 +882,8 @@ function emitPachcaClient( lines.push(' }'); lines.push(''); - const serviceEntries = ir.services - .map((svc) => ({ - propName: tagToProperty(svc.tag), - className: tagToServiceName(svc.tag), - })) - .sort((a, b) => a.propName.localeCompare(b.propName)); - for (const s of serviceEntries) { - lines.push(` val ${s.propName} = ${s.className}(baseUrl, client)`); + lines.push(` val ${s.propName}: ${s.className} = services.${s.propName} ?: ${serviceToImplName(s.className)}(baseUrl, client)`); } lines.push(''); diff --git a/packages/generator/src/lang/python.ts b/packages/generator/src/lang/python.ts index 56acf26c..4b9bf7cd 100644 --- a/packages/generator/src/lang/python.ts +++ b/packages/generator/src/lang/python.ts @@ -15,6 +15,7 @@ import { camelToSnake, snakeToUpperSnake, tagToServiceName, + serviceToImplName, } from '../naming.js'; const PYTHON_KEYWORDS = new Set([ @@ -634,6 +635,59 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { lines.push(' return items'); } +function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { + const args: string[] = []; + if (op.externalUrl) args.push(`${camelToSnake(op.externalUrl)}: str`); + for (const p of op.pathParams) args.push(`${pyParamName(p.sdkName)}: ${pyType(p.type)}`); + + if (op.requestBody) { + const rb = op.requestBody; + if (shouldUnwrapBody(rb)) { + const f = rb.unwrapField!; + args.push(`${pyFieldName(f)}: ${pyType(f.type)}`); + } else if (rb.schemaRef) { + args.push(`request: ${rb.schemaRef}`); + } + } + + if (op.queryParams.length > 0) { + const pascal = op.methodName.charAt(0).toUpperCase() + op.methodName.slice(1); + const hasRequired = op.queryParams.some((p) => p.required); + args.push(hasRequired ? `params: ${pascal}Params` : `params: ${pascal}Params | None = None`); + } + + if (op.deprecated) lines.push(' # Deprecated'); + lines.push(` async def ${pyMethodName(op)}(`); + if (args.length === 0) { + lines.push(` self) -> ${opReturnType(op, ir)}:`); + } else { + lines.push(' self,'); + for (const a of args) lines.push(` ${a},`); + lines.push(` ) -> ${opReturnType(op, ir)}:`); + } + lines.push(` raise NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); +} + +function emitThrowingPaginationMethod(lines: string[], op: IROperation): void { + const itemType = op.successResponse.dataRef ?? 'object'; + const pascal = op.methodName.charAt(0).toUpperCase() + op.methodName.slice(1); + const paramsType = op.queryParams.length > 0 ? `${pascal}Params` : null; + + const args: string[] = []; + if (op.externalUrl) args.push(`${camelToSnake(op.externalUrl)}: str`); + for (const p of op.pathParams) args.push(`${pyParamName(p.sdkName)}: ${pyType(p.type)}`); + if (paramsType) { + const hasRequired = op.queryParams.some((p) => p.required && p.name !== 'cursor'); + args.push(hasRequired ? `params: ${paramsType}` : `params: ${paramsType} | None = None`); + } + + lines.push(` async def ${pyMethodName(op)}_all(`); + lines.push(' self,'); + for (const a of args) lines.push(` ${a},`); + lines.push(` ) -> list[${itemType}]:`); + lines.push(` raise NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)})`); +} + function generateClient(ir: IR): { content: string; needUtils: boolean } { const lines: string[] = []; const needToDict = needsAsdict(ir); @@ -643,6 +697,8 @@ function generateClient(ir: IR): { content: string; needUtils: boolean } { if (ir.services.length > 0) { lines.push('from __future__ import annotations'); lines.push(''); + lines.push('from dataclasses import dataclass'); + lines.push(''); lines.push('import httpx'); lines.push(''); } @@ -669,7 +725,20 @@ function generateClient(ir: IR): { content: string; needUtils: boolean } { lines.push(''); for (const svc of ir.services) { - lines.push(`class ${tagToServiceName(svc.tag)}:`); + const serviceName = tagToServiceName(svc.tag); + const implName = serviceToImplName(serviceName); + lines.push(`class ${serviceName}:`); + for (let i = 0; i < svc.operations.length; i++) { + emitThrowingOperation(lines, svc.operations[i], ir); + if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { + lines.push(''); + emitThrowingPaginationMethod(lines, svc.operations[i]); + } + if (i < svc.operations.length - 1) lines.push(''); + } + lines.push(''); + lines.push(''); + lines.push(`class ${implName}(${serviceName}):`); lines.push(' def __init__(self, client: httpx.AsyncClient) -> None:'); lines.push(' self._client = client'); lines.push(''); @@ -685,19 +754,28 @@ function generateClient(ir: IR): { content: string; needUtils: boolean } { lines.push(''); } + lines.push('@dataclass'); + lines.push('class PachcaServices:'); + const serviceEntries = ir.services + .map((s) => ({ prop: pyServiceProp(s.tag), cls: tagToServiceName(s.tag) })) + .sort((a, b) => a.prop.localeCompare(b.prop)); + for (const s of serviceEntries) { + lines.push(` ${s.prop}: ${s.cls} | None = None`); + } + lines.push(''); + lines.push(''); + lines.push('class PachcaClient:'); const pyDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(` def __init__(self, token: str, base_url: str${pyDefault}) -> None:`); + lines.push(` def __init__(self, token: str, base_url: str${pyDefault}, services: PachcaServices | None = None) -> None:`); + lines.push(' services = services or PachcaServices()'); lines.push(' self._client = httpx.AsyncClient('); lines.push(' base_url=base_url,'); lines.push(' headers={"Authorization": f"Bearer {token}"},'); lines.push(' transport=RetryTransport(httpx.AsyncHTTPTransport()),'); lines.push(' )'); - const services = ir.services - .map((s) => ({ prop: pyServiceProp(s.tag), cls: tagToServiceName(s.tag) })) - .sort((a, b) => a.prop.localeCompare(b.prop)); - for (const s of services) { - lines.push(` self.${s.prop} = ${s.cls}(self._client)`); + for (const s of serviceEntries) { + lines.push(` self.${s.prop}: ${s.cls} = services.${s.prop} or ${serviceToImplName(s.cls)}(self._client)`); } lines.push(''); lines.push(' async def close(self) -> None:'); diff --git a/packages/generator/src/lang/swift.ts b/packages/generator/src/lang/swift.ts index 695c5614..2c9a5125 100644 --- a/packages/generator/src/lang/swift.ts +++ b/packages/generator/src/lang/swift.ts @@ -11,7 +11,7 @@ import { type IRUnion, } from '../ir.js'; import { buildModelIndex, collectTypeRefs, type GeneratedFile, type GenerateOptions, type LanguageGenerator } from './types.js'; -import { snakeToCamel, tagToProperty, tagToServiceName } from '../naming.js'; +import { snakeToCamel, tagToProperty, tagToServiceName, serviceToImplName } from '../naming.js'; const SWIFT_KEYWORDS = new Set([ 'as', 'break', 'case', 'catch', 'class', 'continue', 'default', 'defer', 'do', 'else', @@ -255,7 +255,7 @@ function opReturn(op: IROperation, ir: IR): string { return op.successResponse.dataRef ?? 'String'; } -function emitOperation(lines: string[], op: IROperation, ir: IR): void { +function emitOperation(lines: string[], op: IROperation, ir: IR, fnPrefix = 'public func'): void { const args: string[] = []; if (op.externalUrl) { args.push(`${op.externalUrl}: String`); @@ -276,7 +276,7 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { } if (op.deprecated) lines.push(' @available(*, deprecated)'); - lines.push(` public func ${op.methodName}(${args.join(', ')}) async throws -> ${opReturn(op, ir)} {`); + lines.push(` ${fnPrefix} ${op.methodName}(${args.join(', ')}) async throws -> ${opReturn(op, ir)} {`); if (op.queryParams.length > 0) { const swiftUrlBase = op.externalUrl ? `\\(${op.externalUrl})` : `\\(baseURL)${op.path}`; lines.push(` var components = URLComponents(string: "${swiftUrlBase}")!`); @@ -418,7 +418,7 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { lines.push(' }'); } -function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { +function emitPaginationMethod(lines: string[], op: IROperation, ir: IR, fnPrefix = 'public func'): void { const itemType = op.successResponse.dataRef ?? 'Any'; // Build params minus cursor @@ -431,7 +431,7 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { args.push(`${snakeToCamel(q.sdkName)}: ${t}${q.required ? '' : ' = nil'}`); } - lines.push(` public func ${op.methodName}All(${args.join(', ')}) async throws -> [${itemType}] {`); + lines.push(` ${fnPrefix} ${op.methodName}All(${args.join(', ')}) async throws -> [${itemType}] {`); lines.push(` var items: [${itemType}] = []`); lines.push(' var cursor: String? = nil'); lines.push(' repeat {'); @@ -460,13 +460,68 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { lines.push(' }'); } +function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { + const args: string[] = []; + if (op.externalUrl) args.push(`${op.externalUrl}: String`); + for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)}: ${swiftType(p.type)}`); + if (op.requestBody) { + const rb = op.requestBody; + if (shouldUnwrapBody(rb)) args.push(`${swiftIdentifier(rb.unwrapField!.name)}: ${swiftType(rb.unwrapField!.type)}`); + else if (rb.schemaRef) args.push(`request body: ${rb.schemaRef}`); + } + for (const q of op.queryParams) { + const t = swiftType(q.type, { nullable: !q.required }); + args.push(`${snakeToCamel(q.sdkName)}: ${t}${q.required ? '' : ' = nil'}`); + } + if (op.deprecated) lines.push(' @available(*, deprecated)'); + lines.push(` open func ${op.methodName}(${args.join(', ')}) async throws -> ${opReturn(op, ir)} {`); + lines.push(` throw pachcaNotImplemented(${JSON.stringify(`${op.tag}.${op.methodName}`)})`); + lines.push(' }'); +} + +function emitThrowingPaginationMethod(lines: string[], op: IROperation): void { + const itemType = op.successResponse.dataRef ?? 'Any'; + const args: string[] = []; + if (op.externalUrl) args.push(`${op.externalUrl}: String`); + for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)}: ${swiftType(p.type)}`); + for (const q of op.queryParams) { + if (q.name === 'cursor') continue; + const t = swiftType(q.type, { nullable: !q.required }); + args.push(`${snakeToCamel(q.sdkName)}: ${t}${q.required ? '' : ' = nil'}`); + } + lines.push(` open func ${op.methodName}All(${args.join(', ')}) async throws -> [${itemType}] {`); + lines.push(` throw pachcaNotImplemented(${JSON.stringify(`${op.tag}.${op.methodName}All`)})`); + lines.push(' }'); +} + function generateClient(ir: IR): string { const lines: string[] = []; lines.push(...FOUNDATION_IMPORTS); lines.push(''); + const hasServices = ir.services.length > 0; + if (hasServices) { + lines.push('private func pachcaNotImplemented(_ method: String) -> Error {'); + lines.push(' NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"])'); + lines.push('}'); + lines.push(''); + } for (const s of ir.services) { const cls = tagToServiceName(s.tag); - lines.push(`public struct ${cls} {`); + const implName = serviceToImplName(cls); + lines.push(`open class ${cls} {`); + lines.push(' public init() {}'); + lines.push(''); + for (let i = 0; i < s.operations.length; i++) { + emitThrowingOperation(lines, s.operations[i], ir); + if (s.operations[i].isPaginated && s.operations[i].successResponse.dataRef) { + lines.push(''); + emitThrowingPaginationMethod(lines, s.operations[i]); + } + if (i < s.operations.length - 1) lines.push(''); + } + lines.push('}'); + lines.push(''); + lines.push(`public final class ${implName}: ${cls} {`); lines.push(' let baseURL: String'); lines.push(' let headers: [String: String]'); lines.push(' let session: URLSession'); @@ -475,13 +530,14 @@ function generateClient(ir: IR): string { lines.push(' self.baseURL = baseURL'); lines.push(' self.headers = headers'); lines.push(' self.session = session'); + lines.push(' super.init()'); lines.push(' }'); lines.push(''); for (let i = 0; i < s.operations.length; i++) { - emitOperation(lines, s.operations[i], ir); + emitOperation(lines, s.operations[i], ir, 'public override func'); if (s.operations[i].isPaginated && s.operations[i].successResponse.dataRef) { lines.push(''); - emitPaginationMethod(lines, s.operations[i], ir); + emitPaginationMethod(lines, s.operations[i], ir, 'public override func'); } if (i < s.operations.length - 1) lines.push(''); } @@ -504,16 +560,28 @@ function generateClient(ir: IR): string { lines.push(''); } - lines.push('public struct PachcaClient {'); const svcs = ir.services .map((s) => ({ prop: tagToProperty(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); + if (hasServices) { + lines.push('public struct PachcaServices {'); + for (const s of svcs) lines.push(` public var ${s.prop}: ${s.cls}? = nil`); + lines.push(''); + lines.push(' public init() {}'); + lines.push('}'); + lines.push(''); + } + lines.push('public struct PachcaClient {'); for (const s of svcs) lines.push(` public let ${s.prop}: ${s.cls}`); lines.push(''); const swiftDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(` public init(token: String, baseURL: String${swiftDefault}) {`); + if (hasServices) lines.push(` public init(token: String, baseURL: String${swiftDefault}, services: PachcaServices = PachcaServices()) {`); + else lines.push(` public init(token: String, baseURL: String${swiftDefault}) {`); lines.push(' let headers = ["Authorization": "Bearer \\(token)"]'); - for (const s of svcs) lines.push(` self.${s.prop} = ${s.cls}(baseURL: baseURL, headers: headers)`); + for (const s of svcs) { + if (hasServices) lines.push(` self.${s.prop} = services.${s.prop} ?? ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); + else lines.push(` self.${s.prop} = ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); + } lines.push(' }'); lines.push('}'); lines.push(''); diff --git a/packages/generator/src/lang/typescript.ts b/packages/generator/src/lang/typescript.ts index ace37ca0..792ff8a6 100644 --- a/packages/generator/src/lang/typescript.ts +++ b/packages/generator/src/lang/typescript.ts @@ -18,6 +18,7 @@ import { kebabToCamel, tagToProperty, tagToServiceName, + serviceToImplName, } from '../naming.js'; function fieldSdkName(field: IRField): string { @@ -465,17 +466,21 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { } if (hasServices) { - lines.push('export class PachcaClient {'); + lines.push('export interface PachcaServices {'); const serviceEntries = ir.services .map((s) => ({ prop: tagToProperty(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); + for (const s of serviceEntries) lines.push(` ${s.prop}?: ${s.cls};`); + lines.push('}'); + lines.push(''); + lines.push('export class PachcaClient {'); for (const s of serviceEntries) lines.push(` readonly ${s.prop}: ${s.cls};`); lines.push(''); const defaultUrl = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(` constructor(token: string, baseUrl: string${defaultUrl}) {`); + lines.push(` constructor(token: string, baseUrl: string${defaultUrl}, services: PachcaServices = {}) {`); lines.push(' const headers = { Authorization: `Bearer ${token}` };'); for (const s of serviceEntries) { - lines.push(` this.${s.prop} = new ${s.cls}(baseUrl, headers);`); + lines.push(` this.${s.prop} = services.${s.prop} ?? new ${serviceToImplName(s.cls)}(baseUrl, headers);`); } lines.push(' }'); lines.push('}'); @@ -488,11 +493,25 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { function emitService(lines: string[], svc: IRService, ir: IR): void { const serviceName = tagToServiceName(svc.tag); - lines.push(`class ${serviceName} {`); + const implName = serviceToImplName(serviceName); + lines.push(`export abstract class ${serviceName} {`); + for (let i = 0; i < svc.operations.length; i++) { + emitThrowingMethod(lines, svc.operations[i], ir); + if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { + lines.push(''); + emitThrowingPaginationMethod(lines, svc.operations[i], ir); + } + if (i < svc.operations.length - 1) lines.push(''); + } + lines.push('}'); + lines.push(''); + lines.push(`export class ${implName} extends ${serviceName} {`); lines.push(' constructor('); lines.push(' private baseUrl: string,'); lines.push(' private headers: Record,'); - lines.push(' ) {}'); + lines.push(' ) {'); + lines.push(' super();'); + lines.push(' }'); lines.push(''); for (let i = 0; i < svc.operations.length; i++) { emitOperation(lines, svc.operations[i], ir); @@ -505,12 +524,38 @@ function emitService(lines: string[], svc: IRService, ir: IR): void { lines.push('}'); } +function emitThrowingMethod(lines: string[], op: IROperation, ir: IR): void { + const args = methodArgs(op); + const ret = responseTypeName(op, ir); + if (op.deprecated) lines.push(' /** @deprecated */'); + lines.push(` async ${op.methodName}(${args}): Promise<${ret}> {`); + lines.push(` throw new Error(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)});`); + lines.push(' }'); +} + +function emitThrowingPaginationMethod(lines: string[], op: IROperation, ir: IR): void { + const itemType = op.successResponse.dataRef ?? 'unknown'; + const paramsType = op.queryParams.length > 0 ? irParamTypeName(op) : null; + const args: string[] = []; + if (op.externalUrl) args.push(`${op.externalUrl}: string`); + for (const p of op.pathParams) { + args.push(`${p.sdkName}: ${tsType(p.type, { allModels: new Map(), inlineAsObject: new Set() })}`); + } + if (paramsType) { + const hasRequired = op.queryParams.some((q) => q.required && q.name !== 'cursor'); + args.push(hasRequired ? `params: Omit<${paramsType}, 'cursor'>` : `params?: Omit<${paramsType}, 'cursor'>`); + } + lines.push(` async ${op.methodName}All(${args.join(', ')}): Promise<${itemType}[]> {`); + lines.push(` throw new Error(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)});`); + lines.push(' }'); +} + function emitOperation(lines: string[], op: IROperation, ir: IR): void { const args = methodArgs(op); const ret = responseTypeName(op, ir); const path = escapeTemplatePath(op.path, op); if (op.deprecated) lines.push(' /** @deprecated */'); - lines.push(` async ${op.methodName}(${args}): Promise<${ret}> {`); + lines.push(` override async ${op.methodName}(${args}): Promise<${ret}> {`); if (op.requestBody?.contentType === 'multipart') { lines.push(' const form = new FormData();'); @@ -645,7 +690,7 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { args.push(hasRequired ? `params: Omit<${paramsType}, 'cursor'>` : `params?: Omit<${paramsType}, 'cursor'>`); } - lines.push(` async ${op.methodName}All(${args.join(', ')}): Promise<${itemType}[]> {`); + lines.push(` override async ${op.methodName}All(${args.join(', ')}): Promise<${itemType}[]> {`); lines.push(` const items: ${itemType}[] = [];`); lines.push(' let cursor: string | undefined;'); lines.push(' do {'); diff --git a/packages/generator/src/naming.ts b/packages/generator/src/naming.ts index 322d6b8a..c42934ca 100644 --- a/packages/generator/src/naming.ts +++ b/packages/generator/src/naming.ts @@ -47,6 +47,16 @@ export function tagToServiceName(tag: string): string { return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('') + 'Service'; } +/** Service class name → implementation class name: "ChatsService" → "ChatsServiceImpl" */ +export function serviceToImplName(serviceName: string): string { + return `${serviceName}Impl`; +} + +/** Service class name → stub class name: "ChatsService" → "ChatsServiceStub" */ +export function serviceToStubName(serviceName: string): string { + return `${serviceName}Stub`; +} + /** "ChatOperations_listChats" → "listChats" */ export function operationIdToMethod(operationId: string): string { const parts = operationId.split('_'); diff --git a/packages/generator/tests/crud/snapshots/cs/Client.cs b/packages/generator/tests/crud/snapshots/cs/Client.cs index 583e8c6c..c4c74879 100644 --- a/packages/generator/tests/crud/snapshots/cs/Client.cs +++ b/packages/generator/tests/crud/snapshots/cs/Client.cs @@ -11,18 +11,71 @@ namespace Pachca.Sdk; -public sealed class ChatsService +public abstract class ChatsService +{ + + public virtual async System.Threading.Tasks.Task ListChatsAsync( + ChatAvailability? availability = null, + int? limit = null, + string? cursor = null, + string? sortField = null, + SortOrder? sortOrder = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.listChats is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListChatsAllAsync( + ChatAvailability? availability = null, + int? limit = null, + string? sortField = null, + SortOrder? sortOrder = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.listChatsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.getChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.createChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateChatAsync( + int id, + ChatUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.updateChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.archiveChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.deleteChat is not implemented"); + } +} + +public sealed class ChatsServiceImpl : ChatsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ChatsService(string baseUrl, HttpClient client) + internal ChatsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListChatsAsync( + public override async System.Threading.Tasks.Task ListChatsAsync( ChatAvailability? availability = null, int? limit = null, string? cursor = null, @@ -56,7 +109,7 @@ public async System.Threading.Tasks.Task ListChatsAsync( } } - public async System.Threading.Tasks.Task> ListChatsAllAsync( + public override async System.Threading.Tasks.Task> ListChatsAllAsync( ChatAvailability? availability = null, int? limit = null, string? sortField = null, @@ -74,7 +127,7 @@ public async System.Threading.Tasks.Task> ListChatsAllAsync( return items; } - public async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -91,7 +144,7 @@ public async System.Threading.Tasks.Task GetChatAsync(int id, Cancellation } } - public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -109,7 +162,7 @@ public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest } } - public async System.Threading.Tasks.Task UpdateChatAsync( + public override async System.Threading.Tasks.Task UpdateChatAsync( int id, ChatUpdateRequest request, CancellationToken cancellationToken = default) @@ -130,7 +183,7 @@ public async System.Threading.Tasks.Task UpdateChatAsync( } } - public async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}/archive"; using var request = new HttpRequestMessage(HttpMethod.Put, url); @@ -147,7 +200,7 @@ public async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationTo } } - public async System.Threading.Tasks.Task DeleteChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -169,15 +222,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public ChatsService? Chats { get; init; } + } + public ChatsService Chats { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Chats = new ChatsService(baseUrl, _client); + Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/crud/snapshots/go/client.go b/packages/generator/tests/crud/snapshots/go/client.go index 800bd86f..0cad340f 100644 --- a/packages/generator/tests/crud/snapshots/go/client.go +++ b/packages/generator/tests/crud/snapshots/go/client.go @@ -47,12 +47,52 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type ChatsService struct { +type ChatsService interface { + ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) + ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) + GetChat(ctx context.Context, id int32) (*Chat, error) + CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) + UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) + ArchiveChat(ctx context.Context, id int32) error + DeleteChat(ctx context.Context, id int32) error +} + +type ChatsServiceStub struct{} + +func (s *ChatsServiceStub) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { + return nil, fmt.Errorf("Chats.listChats is not implemented") +} + +func (s *ChatsServiceStub) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { + return nil, fmt.Errorf("Chats.listChatsAll is not implemented") +} + +func (s *ChatsServiceStub) GetChat(ctx context.Context, id int32) (*Chat, error) { + return nil, fmt.Errorf("Chats.getChat is not implemented") +} + +func (s *ChatsServiceStub) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { + return nil, fmt.Errorf("Chats.createChat is not implemented") +} + +func (s *ChatsServiceStub) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { + return nil, fmt.Errorf("Chats.updateChat is not implemented") +} + +func (s *ChatsServiceStub) ArchiveChat(ctx context.Context, id int32) error { + return fmt.Errorf("Chats.archiveChat is not implemented") +} + +func (s *ChatsServiceStub) DeleteChat(ctx context.Context, id int32) error { + return fmt.Errorf("Chats.deleteChat is not implemented") +} + +type ChatsServiceImpl struct { baseURL string client *http.Client } -func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { +func (s *ChatsServiceImpl) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/chats", s.baseURL)) if err != nil { return nil, err @@ -101,7 +141,7 @@ func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) ( } } -func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { +func (s *ChatsServiceImpl) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { if params == nil { params = &ListChatsParams{} } @@ -121,7 +161,7 @@ func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams } } -func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { +func (s *ChatsServiceImpl) GetChat(ctx context.Context, id int32) (*Chat, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/chats/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -151,7 +191,7 @@ func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { } } -func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { +func (s *ChatsServiceImpl) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -186,7 +226,7 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest } } -func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { +func (s *ChatsServiceImpl) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -221,7 +261,7 @@ func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpd } } -func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { +func (s *ChatsServiceImpl) ArchiveChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/chats/%v/archive", s.baseURL, id), nil) if err != nil { return err @@ -245,7 +285,7 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { } } -func (s *ChatsService) DeleteChat(ctx context.Context, id int32) error { +func (s *ChatsServiceImpl) DeleteChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/chats/%v", s.baseURL, id), nil) if err != nil { return err @@ -270,18 +310,35 @@ func (s *ChatsService) DeleteChat(ctx context.Context, id int32) error { } type PachcaClient struct { - Chats *ChatsService + Chats ChatsService +} + +type clientConfig struct { + baseURL string + chats ChatsService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithChats(service ChatsService) ClientOption { + return func(cfg *clientConfig) { cfg.chats = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Chats: &ChatsService{baseURL: url, client: client}, + Chats: func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/crud/snapshots/kt/Client.kt b/packages/generator/tests/crud/snapshots/kt/Client.kt index f8993ad5..83d6c3a6 100644 --- a/packages/generator/tests/crud/snapshots/kt/Client.kt +++ b/packages/generator/tests/crud/snapshots/kt/Client.kt @@ -13,11 +13,52 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class ChatsService internal constructor( +abstract class ChatsService { + open suspend fun listChats( + availability: ChatAvailability? = null, + limit: Int? = null, + cursor: String? = null, + sortField: String? = null, + sortOrder: SortOrder? = null, + ): ListChatsResponse { + throw NotImplementedError("Chats.listChats is not implemented") + } + + open suspend fun listChatsAll( + availability: ChatAvailability? = null, + limit: Int? = null, + sortField: String? = null, + sortOrder: SortOrder? = null, + ): List { + throw NotImplementedError("Chats.listChatsAll is not implemented") + } + + open suspend fun getChat(id: Int): Chat { + throw NotImplementedError("Chats.getChat is not implemented") + } + + open suspend fun createChat(request: ChatCreateRequest): Chat { + throw NotImplementedError("Chats.createChat is not implemented") + } + + open suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + throw NotImplementedError("Chats.updateChat is not implemented") + } + + open suspend fun archiveChat(id: Int) { + throw NotImplementedError("Chats.archiveChat is not implemented") + } + + open suspend fun deleteChat(id: Int) { + throw NotImplementedError("Chats.deleteChat is not implemented") + } +} + +class ChatsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun listChats( +) : ChatsService() { + override suspend fun listChats( availability: ChatAvailability? = null, limit: Int? = null, cursor: String? = null, @@ -38,7 +79,7 @@ class ChatsService internal constructor( } } - suspend fun listChatsAll( + override suspend fun listChatsAll( availability: ChatAvailability? = null, limit: Int? = null, sortField: String? = null, @@ -60,7 +101,7 @@ class ChatsService internal constructor( return items } - suspend fun getChat(id: Int): Chat { + override suspend fun getChat(id: Int): Chat { val response = client.get("$baseUrl/chats/$id") return when (response.status.value) { 200 -> response.body().data @@ -69,7 +110,7 @@ class ChatsService internal constructor( } } - suspend fun createChat(request: ChatCreateRequest): Chat { + override suspend fun createChat(request: ChatCreateRequest): Chat { val response = client.post("$baseUrl/chats") { contentType(ContentType.Application.Json) setBody(request) @@ -81,7 +122,7 @@ class ChatsService internal constructor( } } - suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + override suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { val response = client.put("$baseUrl/chats/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -93,7 +134,7 @@ class ChatsService internal constructor( } } - suspend fun archiveChat(id: Int) { + override suspend fun archiveChat(id: Int) { val response = client.put("$baseUrl/chats/$id/archive") when (response.status.value) { 204 -> return @@ -102,7 +143,7 @@ class ChatsService internal constructor( } } - suspend fun deleteChat(id: Int) { + override suspend fun deleteChat(id: Int) { val response = client.delete("$baseUrl/chats/$id") when (response.status.value) { 204 -> return @@ -112,7 +153,11 @@ class ChatsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val chats: ChatsService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -131,7 +176,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val chats = ChatsService(baseUrl, client) + val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/crud/snapshots/py/client.py b/packages/generator/tests/crud/snapshots/py/client.py index e73999ce..51382b96 100644 --- a/packages/generator/tests/crud/snapshots/py/client.py +++ b/packages/generator/tests/crud/snapshots/py/client.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ( @@ -16,6 +18,51 @@ from .utils import deserialize, serialize, RetryTransport class ChatsService: + async def list_chats( + self, + params: ListChatsParams | None = None, + ) -> ListChatsResponse: + raise NotImplementedError("Chats.listChats is not implemented") + + async def list_chats_all( + self, + params: ListChatsParams | None = None, + ) -> list[Chat]: + raise NotImplementedError("Chats.listChatsAll is not implemented") + + async def get_chat( + self, + id: int, + ) -> Chat: + raise NotImplementedError("Chats.getChat is not implemented") + + async def create_chat( + self, + request: ChatCreateRequest, + ) -> Chat: + raise NotImplementedError("Chats.createChat is not implemented") + + async def update_chat( + self, + id: int, + request: ChatUpdateRequest, + ) -> Chat: + raise NotImplementedError("Chats.updateChat is not implemented") + + async def archive_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Chats.archiveChat is not implemented") + + async def delete_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Chats.deleteChat is not implemented") + + +class ChatsServiceImpl(ChatsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -146,14 +193,20 @@ async def delete_chat( raise deserialize(ApiError, response.json()) +@dataclass +class PachcaServices: + chats: ChatsService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.chats = ChatsService(self._client) + self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/crud/snapshots/swift/Client.swift b/packages/generator/tests/crud/snapshots/swift/Client.swift index a08a1a28..e6348ca9 100644 --- a/packages/generator/tests/crud/snapshots/swift/Client.swift +++ b/packages/generator/tests/crud/snapshots/swift/Client.swift @@ -3,18 +3,55 @@ import Foundation import FoundationNetworking #endif -public struct ChatsService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class ChatsService { + public init() {} + + open func listChats(availability: ChatAvailability? = nil, limit: Int? = nil, cursor: String? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> ListChatsResponse { + throw pachcaNotImplemented("Chats.listChats") + } + + open func listChatsAll(availability: ChatAvailability? = nil, limit: Int? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> [Chat] { + throw pachcaNotImplemented("Chats.listChatsAll") + } + + open func getChat(id: Int) async throws -> Chat { + throw pachcaNotImplemented("Chats.getChat") + } + + open func createChat(request body: ChatCreateRequest) async throws -> Chat { + throw pachcaNotImplemented("Chats.createChat") + } + + open func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { + throw pachcaNotImplemented("Chats.updateChat") + } + + open func archiveChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Chats.archiveChat") + } + + open func deleteChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Chats.deleteChat") + } +} + +public final class ChatsServiceImpl: ChatsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func listChats(availability: ChatAvailability? = nil, limit: Int? = nil, cursor: String? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> ListChatsResponse { + public override func listChats(availability: ChatAvailability? = nil, limit: Int? = nil, cursor: String? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> ListChatsResponse { var components = URLComponents(string: "\(baseURL)/chats")! var queryItems: [URLQueryItem] = [] if let availability { queryItems.append(URLQueryItem(name: "availability", value: availability.rawValue)) } @@ -37,7 +74,7 @@ public struct ChatsService { } } - public func listChatsAll(availability: ChatAvailability? = nil, limit: Int? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> [Chat] { + public override func listChatsAll(availability: ChatAvailability? = nil, limit: Int? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> [Chat] { var items: [Chat] = [] var cursor: String? = nil repeat { @@ -48,7 +85,7 @@ public struct ChatsService { return items } - public func getChat(id: Int) async throws -> Chat { + public override func getChat(id: Int) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -63,7 +100,7 @@ public struct ChatsService { } } - public func createChat(request body: ChatCreateRequest) async throws -> Chat { + public override func createChat(request body: ChatCreateRequest) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -81,7 +118,7 @@ public struct ChatsService { } } - public func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { + public override func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -99,7 +136,7 @@ public struct ChatsService { } } - public func archiveChat(id: Int) async throws -> Void { + public override func archiveChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/archive")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -115,7 +152,7 @@ public struct ChatsService { } } - public func deleteChat(id: Int) async throws -> Void { + public override func deleteChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -132,11 +169,17 @@ public struct ChatsService { } } +public struct PachcaServices { + public var chats: ChatsService? = nil + + public init() {} +} + public struct PachcaClient { public let chats: ChatsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = ChatsService(baseURL: baseURL, headers: headers) + self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/crud/snapshots/ts/client.ts b/packages/generator/tests/crud/snapshots/ts/client.ts index c00d7c3f..9bd1a1f0 100644 --- a/packages/generator/tests/crud/snapshots/ts/client.ts +++ b/packages/generator/tests/crud/snapshots/ts/client.ts @@ -9,13 +9,43 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -class ChatsService { +export abstract class ChatsService { + async listChats(params?: ListChatsParams): Promise { + throw new Error("Chats.listChats is not implemented"); + } + + async listChatsAll(params?: Omit): Promise { + throw new Error("Chats.listChatsAll is not implemented"); + } + + async getChat(id: number): Promise { + throw new Error("Chats.getChat is not implemented"); + } + + async createChat(request: ChatCreateRequest): Promise { + throw new Error("Chats.createChat is not implemented"); + } + + async updateChat(id: number, request: ChatUpdateRequest): Promise { + throw new Error("Chats.updateChat is not implemented"); + } + + async archiveChat(id: number): Promise { + throw new Error("Chats.archiveChat is not implemented"); + } + + async deleteChat(id: number): Promise { + throw new Error("Chats.deleteChat is not implemented"); + } +} + +export class ChatsServiceImpl extends ChatsService { constructor( private baseUrl: string, private headers: Record, ) {} - async listChats(params?: ListChatsParams): Promise { + override async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); if (params?.availability !== undefined) query.set("availability", params.availability); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -37,7 +67,7 @@ class ChatsService { } } - async listChatsAll(params?: Omit): Promise { + override async listChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -48,7 +78,7 @@ class ChatsService { return items; } - async getChat(id: number): Promise { + override async getChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { headers: this.headers, }); @@ -63,7 +93,7 @@ class ChatsService { } } - async createChat(request: ChatCreateRequest): Promise { + override async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -80,7 +110,7 @@ class ChatsService { } } - async updateChat(id: number, request: ChatUpdateRequest): Promise { + override async updateChat(id: number, request: ChatUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -97,7 +127,7 @@ class ChatsService { } } - async archiveChat(id: number): Promise { + override async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -112,7 +142,7 @@ class ChatsService { } } - async deleteChat(id: number): Promise { + override async deleteChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "DELETE", headers: this.headers, @@ -128,11 +158,15 @@ class ChatsService { } } +export interface PachcaServices { + chats?: ChatsService; +} + export class PachcaClient { readonly chats: ChatsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.chats = new ChatsService(baseUrl, headers); + this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs index bc4a882d..50edee50 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs @@ -12,18 +12,39 @@ namespace Pachca.Sdk; -public sealed class EventsService +public abstract class EventsService +{ + + public virtual async System.Threading.Tasks.Task ListEventsAsync( + bool? isActive = null, + List? scopes = null, + EventFilter? filter = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Events.listEvents is not implemented"); + } + + public virtual async System.Threading.Tasks.Task PublishEventAsync( + int id, + OAuthScope scope, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Events.publishEvent is not implemented"); + } +} + +public sealed class EventsServiceImpl : EventsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal EventsService(string baseUrl, HttpClient client) + internal EventsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListEventsAsync( + public override async System.Threading.Tasks.Task ListEventsAsync( bool? isActive = null, List? scopes = null, EventFilter? filter = null, @@ -49,7 +70,7 @@ public async System.Threading.Tasks.Task ListEventsAsync( } } - public async System.Threading.Tasks.Task PublishEventAsync( + public override async System.Threading.Tasks.Task PublishEventAsync( int id, OAuthScope scope, CancellationToken cancellationToken = default) @@ -70,18 +91,27 @@ public async System.Threading.Tasks.Task PublishEventAsync( } } -public sealed class UploadsService +public abstract class UploadsService +{ + + public virtual async System.Threading.Tasks.Task CreateUploadAsync(UploadRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Uploads.createUpload is not implemented"); + } +} + +public sealed class UploadsServiceImpl : UploadsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal UploadsService(string baseUrl, HttpClient client) + internal UploadsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task CreateUploadAsync(UploadRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateUploadAsync(UploadRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/uploads"; using var content = new MultipartFormDataContent(); @@ -105,17 +135,24 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public EventsService? Events { get; init; } + public UploadsService? Uploads { get; init; } + } + public EventsService Events { get; } public UploadsService Uploads { get; } - public PachcaClient(string token, string baseUrl) + public PachcaClient(string token, string baseUrl, Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Events = new EventsService(baseUrl, _client); - Uploads = new UploadsService(baseUrl, _client); + Events = services.Events ?? new EventsServiceImpl(baseUrl, _client); + Uploads = services.Uploads ?? new UploadsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/edge-cases/snapshots/go/client.go b/packages/generator/tests/edge-cases/snapshots/go/client.go index 13f517d8..8dbb7f0d 100644 --- a/packages/generator/tests/edge-cases/snapshots/go/client.go +++ b/packages/generator/tests/edge-cases/snapshots/go/client.go @@ -49,12 +49,27 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type EventsService struct { +type EventsService interface { + ListEvents(ctx context.Context, params *ListEventsParams) (*ListEventsResponse, error) + PublishEvent(ctx context.Context, id int32, scope OAuthScope) (*Event, error) +} + +type EventsServiceStub struct{} + +func (s *EventsServiceStub) ListEvents(ctx context.Context, params *ListEventsParams) (*ListEventsResponse, error) { + return nil, fmt.Errorf("Events.listEvents is not implemented") +} + +func (s *EventsServiceStub) PublishEvent(ctx context.Context, id int32, scope OAuthScope) (*Event, error) { + return nil, fmt.Errorf("Events.publishEvent is not implemented") +} + +type EventsServiceImpl struct { baseURL string client *http.Client } -func (s *EventsService) ListEvents(ctx context.Context, params *ListEventsParams) (*ListEventsResponse, error) { +func (s *EventsServiceImpl) ListEvents(ctx context.Context, params *ListEventsParams) (*ListEventsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/events", s.baseURL)) if err != nil { return nil, err @@ -91,7 +106,7 @@ func (s *EventsService) ListEvents(ctx context.Context, params *ListEventsParams } } -func (s *EventsService) PublishEvent(ctx context.Context, id int32, scope OAuthScope) (*Event, error) { +func (s *EventsServiceImpl) PublishEvent(ctx context.Context, id int32, scope OAuthScope) (*Event, error) { body, err := json.Marshal(map[string]any{"scope": scope}) if err != nil { return nil, err @@ -120,12 +135,22 @@ func (s *EventsService) PublishEvent(ctx context.Context, id int32, scope OAuthS } } -type UploadsService struct { +type UploadsService interface { + CreateUpload(ctx context.Context, request UploadRequest) error +} + +type UploadsServiceStub struct{} + +func (s *UploadsServiceStub) CreateUpload(ctx context.Context, request UploadRequest) error { + return fmt.Errorf("Uploads.createUpload is not implemented") +} + +type UploadsServiceImpl struct { baseURL string client *http.Client } -func (s *UploadsService) CreateUpload(ctx context.Context, request UploadRequest) error { +func (s *UploadsServiceImpl) CreateUpload(ctx context.Context, request UploadRequest) error { pr, pw := io.Pipe() writer := multipart.NewWriter(pw) go func() { @@ -161,18 +186,40 @@ func (s *UploadsService) CreateUpload(ctx context.Context, request UploadRequest } type PachcaClient struct { - Events *EventsService - Uploads *UploadsService + Events EventsService + Uploads UploadsService } -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := "" - if len(baseURL) > 0 { url = baseURL[0] } +type clientConfig struct { + baseURL string + events EventsService + uploads UploadsService +} + +type ClientOption func(*clientConfig) + +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithEvents(service EventsService) ClientOption { + return func(cfg *clientConfig) { cfg.events = service } +} + +func WithUploads(service UploadsService) ClientOption { + return func(cfg *clientConfig) { cfg.uploads = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Events : &EventsService{baseURL: url, client: client}, - Uploads: &UploadsService{baseURL: url, client: client}, + Events : func() EventsService { if cfg.events != nil { return cfg.events }; return &EventsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Uploads: func() UploadsService { if cfg.uploads != nil { return cfg.uploads }; return &UploadsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt index 89814d15..08374224 100644 --- a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt +++ b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt @@ -14,11 +14,25 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class EventsService internal constructor( +abstract class EventsService { + open suspend fun listEvents( + isActive: Boolean? = null, + scopes: List? = null, + filter: EventFilter? = null, + ): ListEventsResponse { + throw NotImplementedError("Events.listEvents is not implemented") + } + + open suspend fun publishEvent(id: Int, scope: OAuthScope): Event { + throw NotImplementedError("Events.publishEvent is not implemented") + } +} + +class EventsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun listEvents( +) : EventsService() { + override suspend fun listEvents( isActive: Boolean? = null, scopes: List? = null, filter: EventFilter? = null, @@ -34,7 +48,7 @@ class EventsService internal constructor( } } - suspend fun publishEvent(id: Int, scope: OAuthScope): Event { + override suspend fun publishEvent(id: Int, scope: OAuthScope): Event { val response = client.put("$baseUrl/events/$id/publish") { contentType(ContentType.Application.Json) setBody(PublishEventRequest(scope = scope)) @@ -46,11 +60,17 @@ class EventsService internal constructor( } } -class UploadsService internal constructor( +abstract class UploadsService { + open suspend fun createUpload(request: UploadRequest) { + throw NotImplementedError("Uploads.createUpload is not implemented") + } +} + +class UploadsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun createUpload(request: UploadRequest) { +) : UploadsService() { + override suspend fun createUpload(request: UploadRequest) { val response = client.submitFormWithBinaryData( "$baseUrl/uploads", formData { @@ -67,7 +87,12 @@ class UploadsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String) : Closeable { +data class PachcaServices( + val events: EventsService? = null, + val uploads: UploadsService? = null +) + +class PachcaClient(token: String, baseUrl: String, services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -86,8 +111,8 @@ class PachcaClient(token: String, baseUrl: String) : Closeable { } } - val events = EventsService(baseUrl, client) - val uploads = UploadsService(baseUrl, client) + val events: EventsService = services.events ?: EventsServiceImpl(baseUrl, client) + val uploads: UploadsService = services.uploads ?: UploadsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/edge-cases/snapshots/py/client.py b/packages/generator/tests/edge-cases/snapshots/py/client.py index 687f9eb9..985db341 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/client.py +++ b/packages/generator/tests/edge-cases/snapshots/py/client.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ( @@ -13,6 +15,21 @@ from .utils import deserialize, RetryTransport class EventsService: + async def list_events( + self, + params: ListEventsParams | None = None, + ) -> ListEventsResponse: + raise NotImplementedError("Events.listEvents is not implemented") + + async def publish_event( + self, + id: int, + scope: OAuthScope, + ) -> Event: + raise NotImplementedError("Events.publishEvent is not implemented") + + +class EventsServiceImpl(EventsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -60,6 +77,14 @@ async def publish_event( class UploadsService: + async def create_upload( + self, + request: UploadRequest, + ) -> None: + raise NotImplementedError("Uploads.createUpload is not implemented") + + +class UploadsServiceImpl(UploadsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -83,15 +108,22 @@ async def create_upload( ) +@dataclass +class PachcaServices: + events: EventsService | None = None + uploads: UploadsService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str) -> None: + def __init__(self, token: str, base_url: str, services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.events = EventsService(self._client) - self.uploads = UploadsService(self._client) + self.events: EventsService = services.events or EventsServiceImpl(self._client) + self.uploads: UploadsService = services.uploads or UploadsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift index 0cb2aa07..ee947ffa 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift @@ -3,18 +3,35 @@ import Foundation import FoundationNetworking #endif -public struct EventsService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class EventsService { + public init() {} + + open func listEvents(isActive: Bool? = nil, scopes: [OAuthScope]? = nil, filter: EventFilter? = nil) async throws -> ListEventsResponse { + throw pachcaNotImplemented("Events.listEvents") + } + + open func publishEvent(id: Int, scope: OAuthScope) async throws -> Event { + throw pachcaNotImplemented("Events.publishEvent") + } +} + +public final class EventsServiceImpl: EventsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func listEvents(isActive: Bool? = nil, scopes: [OAuthScope]? = nil, filter: EventFilter? = nil) async throws -> ListEventsResponse { + public override func listEvents(isActive: Bool? = nil, scopes: [OAuthScope]? = nil, filter: EventFilter? = nil) async throws -> ListEventsResponse { var components = URLComponents(string: "\(baseURL)/events")! var queryItems: [URLQueryItem] = [] if let isActive { queryItems.append(URLQueryItem(name: "is_active", value: String(isActive))) } @@ -33,7 +50,7 @@ public struct EventsService { } } - public func publishEvent(id: Int, scope: OAuthScope) async throws -> Event { + public override func publishEvent(id: Int, scope: OAuthScope) async throws -> Event { var request = URLRequest(url: URL(string: "\(baseURL)/events/\(id)/publish")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -50,18 +67,27 @@ public struct EventsService { } } -public struct UploadsService { +open class UploadsService { + public init() {} + + open func createUpload(request body: UploadRequest) async throws -> Void { + throw pachcaNotImplemented("Uploads.createUpload") + } +} + +public final class UploadsServiceImpl: UploadsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func createUpload(request body: UploadRequest) async throws -> Void { + public override func createUpload(request body: UploadRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/uploads")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -92,13 +118,20 @@ public struct UploadsService { } } +public struct PachcaServices { + public var events: EventsService? = nil + public var uploads: UploadsService? = nil + + public init() {} +} + public struct PachcaClient { public let events: EventsService public let uploads: UploadsService - public init(token: String, baseURL: String) { + public init(token: String, baseURL: String, services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.events = EventsService(baseURL: baseURL, headers: headers) - self.uploads = UploadsService(baseURL: baseURL, headers: headers) + self.events = services.events ?? EventsServiceImpl(baseURL: baseURL, headers: headers) + self.uploads = services.uploads ?? UploadsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/client.ts b/packages/generator/tests/edge-cases/snapshots/ts/client.ts index 769675da..3046fdbf 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/client.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/client.ts @@ -7,13 +7,23 @@ import { } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -class EventsService { +export abstract class EventsService { + async listEvents(params?: ListEventsParams): Promise { + throw new Error("Events.listEvents is not implemented"); + } + + async publishEvent(id: number, scope: OAuthScope): Promise { + throw new Error("Events.publishEvent is not implemented"); + } +} + +export class EventsServiceImpl extends EventsService { constructor( private baseUrl: string, private headers: Record, ) {} - async listEvents(params?: ListEventsParams): Promise { + override async listEvents(params?: ListEventsParams): Promise { const query = new URLSearchParams(); if (params?.isActive !== undefined) query.set("is_active", String(params.isActive)); if (params?.scopes !== undefined) query.set("scopes", String(params.scopes)); @@ -31,7 +41,7 @@ class EventsService { } } - async publishEvent(id: number, scope: OAuthScope): Promise { + override async publishEvent(id: number, scope: OAuthScope): Promise { const response = await fetchWithRetry(`${this.baseUrl}/events/${id}/publish`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -47,13 +57,19 @@ class EventsService { } } -class UploadsService { +export abstract class UploadsService { + async createUpload(request: UploadRequest): Promise { + throw new Error("Uploads.createUpload is not implemented"); + } +} + +export class UploadsServiceImpl extends UploadsService { constructor( private baseUrl: string, private headers: Record, ) {} - async createUpload(request: UploadRequest): Promise { + override async createUpload(request: UploadRequest): Promise { const form = new FormData(); form.set("Content-Disposition", request.contentDisposition); form.set("file", request.file, "upload"); @@ -71,13 +87,18 @@ class UploadsService { } } +export interface PachcaServices { + events?: EventsService; + uploads?: UploadsService; +} + export class PachcaClient { readonly events: EventsService; readonly uploads: UploadsService; - constructor(token: string, baseUrl: string) { + constructor(token: string, baseUrl: string, services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.events = new EventsService(baseUrl, headers); - this.uploads = new UploadsService(baseUrl, headers); + this.events = services.events ?? new EventsServiceImpl(baseUrl, headers); + this.uploads = services.uploads ?? new UploadsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs index 77819166..f4e66b52 100644 --- a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs +++ b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs @@ -11,18 +11,48 @@ namespace Pachca.Sdk; -public sealed class TasksService +public abstract class TasksService +{ + + public virtual async System.Threading.Tasks.Task GetTaskAsync( + int projectId, + int taskId, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.getTask is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateTaskAsync( + int projectId, + int taskId, + TaskUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.updateTask is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteCommentAsync( + int projectId, + int taskId, + int commentId, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.deleteComment is not implemented"); + } +} + +public sealed class TasksServiceImpl : TasksService { private readonly string _baseUrl; private readonly HttpClient _client; - internal TasksService(string baseUrl, HttpClient client) + internal TasksServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task GetTaskAsync( + public override async System.Threading.Tasks.Task GetTaskAsync( int projectId, int taskId, CancellationToken cancellationToken = default) @@ -40,7 +70,7 @@ internal TasksService(string baseUrl, HttpClient client) } } - public async System.Threading.Tasks.Task UpdateTaskAsync( + public override async System.Threading.Tasks.Task UpdateTaskAsync( int projectId, int taskId, TaskUpdateRequest request, @@ -60,7 +90,7 @@ internal TasksService(string baseUrl, HttpClient client) } } - public async System.Threading.Tasks.Task DeleteCommentAsync( + public override async System.Threading.Tasks.Task DeleteCommentAsync( int projectId, int taskId, int commentId, @@ -84,15 +114,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public TasksService? Tasks { get; init; } + } + public TasksService Tasks { get; } - public PachcaClient(string token, string baseUrl = "https://api.example.com/v1") + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Tasks = new TasksService(baseUrl, _client); + Tasks = services.Tasks ?? new TasksServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/multi-path-params/snapshots/go/client.go b/packages/generator/tests/multi-path-params/snapshots/go/client.go index 7c39db2e..2232cb8e 100644 --- a/packages/generator/tests/multi-path-params/snapshots/go/client.go +++ b/packages/generator/tests/multi-path-params/snapshots/go/client.go @@ -46,12 +46,32 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type TasksService struct { +type TasksService interface { + GetTask(ctx context.Context, projectId int32, taskId int32) (*Task, error) + UpdateTask(ctx context.Context, projectId int32, taskId int32, request TaskUpdateRequest) (*Task, error) + DeleteComment(ctx context.Context, projectId int32, taskId int32, commentId int32) error +} + +type TasksServiceStub struct{} + +func (s *TasksServiceStub) GetTask(ctx context.Context, projectId int32, taskId int32) (*Task, error) { + return nil, fmt.Errorf("Tasks.getTask is not implemented") +} + +func (s *TasksServiceStub) UpdateTask(ctx context.Context, projectId int32, taskId int32, request TaskUpdateRequest) (*Task, error) { + return nil, fmt.Errorf("Tasks.updateTask is not implemented") +} + +func (s *TasksServiceStub) DeleteComment(ctx context.Context, projectId int32, taskId int32, commentId int32) error { + return fmt.Errorf("Tasks.deleteComment is not implemented") +} + +type TasksServiceImpl struct { baseURL string client *http.Client } -func (s *TasksService) GetTask(ctx context.Context, projectId int32, taskId int32) (*Task, error) { +func (s *TasksServiceImpl) GetTask(ctx context.Context, projectId int32, taskId int32) (*Task, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/projects/%v/tasks/%v", s.baseURL, projectId, taskId), nil) if err != nil { return nil, err @@ -75,7 +95,7 @@ func (s *TasksService) GetTask(ctx context.Context, projectId int32, taskId int3 } } -func (s *TasksService) UpdateTask(ctx context.Context, projectId int32, taskId int32, request TaskUpdateRequest) (*Task, error) { +func (s *TasksServiceImpl) UpdateTask(ctx context.Context, projectId int32, taskId int32, request TaskUpdateRequest) (*Task, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -104,7 +124,7 @@ func (s *TasksService) UpdateTask(ctx context.Context, projectId int32, taskId i } } -func (s *TasksService) DeleteComment(ctx context.Context, projectId int32, taskId int32, commentId int32) error { +func (s *TasksServiceImpl) DeleteComment(ctx context.Context, projectId int32, taskId int32, commentId int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/projects/%v/tasks/%v/comments/%v", s.baseURL, projectId, taskId, commentId), nil) if err != nil { return err @@ -123,18 +143,35 @@ func (s *TasksService) DeleteComment(ctx context.Context, projectId int32, taskI } type PachcaClient struct { - Tasks *TasksService + Tasks TasksService } +type clientConfig struct { + baseURL string + tasks TasksService +} + +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.example.com/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithTasks(service TasksService) ClientOption { + return func(cfg *clientConfig) { cfg.tasks = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Tasks: &TasksService{baseURL: url, client: client}, + Tasks: func() TasksService { if cfg.tasks != nil { return cfg.tasks }; return &TasksServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt index a626f3f4..053fbd9e 100644 --- a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt +++ b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt @@ -13,11 +13,33 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class TasksService internal constructor( +abstract class TasksService { + open suspend fun getTask(projectId: Int, taskId: Int): Task { + throw NotImplementedError("Tasks.getTask is not implemented") + } + + open suspend fun updateTask( + projectId: Int, + taskId: Int, + request: TaskUpdateRequest, + ): Task { + throw NotImplementedError("Tasks.updateTask is not implemented") + } + + open suspend fun deleteComment( + projectId: Int, + taskId: Int, + commentId: Int, + ) { + throw NotImplementedError("Tasks.deleteComment is not implemented") + } +} + +class TasksServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun getTask(projectId: Int, taskId: Int): Task { +) : TasksService() { + override suspend fun getTask(projectId: Int, taskId: Int): Task { val response = client.get("$baseUrl/projects/$projectId/tasks/$taskId") return when (response.status.value) { 200 -> response.body().data @@ -25,7 +47,7 @@ class TasksService internal constructor( } } - suspend fun updateTask( + override suspend fun updateTask( projectId: Int, taskId: Int, request: TaskUpdateRequest, @@ -40,7 +62,7 @@ class TasksService internal constructor( } } - suspend fun deleteComment( + override suspend fun deleteComment( projectId: Int, taskId: Int, commentId: Int, @@ -53,7 +75,11 @@ class TasksService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1") : Closeable { +data class PachcaServices( + val tasks: TasksService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -72,7 +98,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1" } } - val tasks = TasksService(baseUrl, client) + val tasks: TasksService = services.tasks ?: TasksServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/multi-path-params/snapshots/py/client.py b/packages/generator/tests/multi-path-params/snapshots/py/client.py index 92696119..cbc3a600 100644 --- a/packages/generator/tests/multi-path-params/snapshots/py/client.py +++ b/packages/generator/tests/multi-path-params/snapshots/py/client.py @@ -1,11 +1,38 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import Task, TaskUpdateRequest from .utils import deserialize, serialize, RetryTransport class TasksService: + async def get_task( + self, + project_id: int, + task_id: int, + ) -> Task: + raise NotImplementedError("Tasks.getTask is not implemented") + + async def update_task( + self, + project_id: int, + task_id: int, + request: TaskUpdateRequest, + ) -> Task: + raise NotImplementedError("Tasks.updateTask is not implemented") + + async def delete_comment( + self, + project_id: int, + task_id: int, + comment_id: int, + ) -> None: + raise NotImplementedError("Tasks.deleteComment is not implemented") + + +class TasksServiceImpl(TasksService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -63,14 +90,20 @@ async def delete_comment( ) +@dataclass +class PachcaServices: + tasks: TasksService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.example.com/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.example.com/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.tasks = TasksService(self._client) + self.tasks: TasksService = services.tasks or TasksServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift index 2d780992..c6662ca2 100644 --- a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift +++ b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift @@ -3,18 +3,39 @@ import Foundation import FoundationNetworking #endif -public struct TasksService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class TasksService { + public init() {} + + open func getTask(projectId: Int, taskId: Int) async throws -> Task { + throw pachcaNotImplemented("Tasks.getTask") + } + + open func updateTask(projectId: Int, taskId: Int, request body: TaskUpdateRequest) async throws -> Task { + throw pachcaNotImplemented("Tasks.updateTask") + } + + open func deleteComment(projectId: Int, taskId: Int, commentId: Int) async throws -> Void { + throw pachcaNotImplemented("Tasks.deleteComment") + } +} + +public final class TasksServiceImpl: TasksService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func getTask(projectId: Int, taskId: Int) async throws -> Task { + public override func getTask(projectId: Int, taskId: Int) async throws -> Task { var request = URLRequest(url: URL(string: "\(baseURL)/projects/\(projectId)/tasks/\(taskId)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -27,7 +48,7 @@ public struct TasksService { } } - public func updateTask(projectId: Int, taskId: Int, request body: TaskUpdateRequest) async throws -> Task { + public override func updateTask(projectId: Int, taskId: Int, request body: TaskUpdateRequest) async throws -> Task { var request = URLRequest(url: URL(string: "\(baseURL)/projects/\(projectId)/tasks/\(taskId)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -43,7 +64,7 @@ public struct TasksService { } } - public func deleteComment(projectId: Int, taskId: Int, commentId: Int) async throws -> Void { + public override func deleteComment(projectId: Int, taskId: Int, commentId: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/projects/\(projectId)/tasks/\(taskId)/comments/\(commentId)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -58,11 +79,17 @@ public struct TasksService { } } +public struct PachcaServices { + public var tasks: TasksService? = nil + + public init() {} +} + public struct PachcaClient { public let tasks: TasksService - public init(token: String, baseURL: String = "https://api.example.com/v1") { + public init(token: String, baseURL: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.tasks = TasksService(baseURL: baseURL, headers: headers) + self.tasks = services.tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts index bc1ac931..058a2028 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts +++ b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts @@ -1,13 +1,27 @@ import { Task, TaskUpdateRequest } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -class TasksService { +export abstract class TasksService { + async getTask(projectId: number, taskId: number): Promise { + throw new Error("Tasks.getTask is not implemented"); + } + + async updateTask(projectId: number, taskId: number, request: TaskUpdateRequest): Promise { + throw new Error("Tasks.updateTask is not implemented"); + } + + async deleteComment(projectId: number, taskId: number, commentId: number): Promise { + throw new Error("Tasks.deleteComment is not implemented"); + } +} + +export class TasksServiceImpl extends TasksService { constructor( private baseUrl: string, private headers: Record, ) {} - async getTask(projectId: number, taskId: number): Promise { + override async getTask(projectId: number, taskId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}`, { headers: this.headers, }); @@ -20,7 +34,7 @@ class TasksService { } } - async updateTask(projectId: number, taskId: number, request: TaskUpdateRequest): Promise { + override async updateTask(projectId: number, taskId: number, request: TaskUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -35,7 +49,7 @@ class TasksService { } } - async deleteComment(projectId: number, taskId: number, commentId: number): Promise { + override async deleteComment(projectId: number, taskId: number, commentId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}/comments/${commentId}`, { method: "DELETE", headers: this.headers, @@ -49,11 +63,15 @@ class TasksService { } } +export interface PachcaServices { + tasks?: TasksService; +} + export class PachcaClient { readonly tasks: TasksService; - constructor(token: string, baseUrl: string = "https://api.example.com/v1") { + constructor(token: string, baseUrl: string = "https://api.example.com/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.tasks = new TasksService(baseUrl, headers); + this.tasks = services.tasks ?? new TasksServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/patch/snapshots/cs/Client.cs b/packages/generator/tests/patch/snapshots/cs/Client.cs index 7d167bda..69456507 100644 --- a/packages/generator/tests/patch/snapshots/cs/Client.cs +++ b/packages/generator/tests/patch/snapshots/cs/Client.cs @@ -11,18 +11,30 @@ namespace Pachca.Sdk; -public sealed class ItemsService +public abstract class ItemsService +{ + + public virtual async System.Threading.Tasks.Task PatchItemAsync( + int id, + ItemPatchRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Items.patchItem is not implemented"); + } +} + +public sealed class ItemsServiceImpl : ItemsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ItemsService(string baseUrl, HttpClient client) + internal ItemsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task PatchItemAsync( + public override async System.Threading.Tasks.Task PatchItemAsync( int id, ItemPatchRequest request, CancellationToken cancellationToken = default) @@ -46,15 +58,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public ItemsService? Items { get; init; } + } + public ItemsService Items { get; } - public PachcaClient(string token, string baseUrl = "https://api.example.com/v1") + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Items = new ItemsService(baseUrl, _client); + Items = services.Items ?? new ItemsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/patch/snapshots/go/client.go b/packages/generator/tests/patch/snapshots/go/client.go index 38f212e7..1461b94d 100644 --- a/packages/generator/tests/patch/snapshots/go/client.go +++ b/packages/generator/tests/patch/snapshots/go/client.go @@ -46,12 +46,22 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type ItemsService struct { +type ItemsService interface { + PatchItem(ctx context.Context, id int32, request ItemPatchRequest) (*Item, error) +} + +type ItemsServiceStub struct{} + +func (s *ItemsServiceStub) PatchItem(ctx context.Context, id int32, request ItemPatchRequest) (*Item, error) { + return nil, fmt.Errorf("Items.patchItem is not implemented") +} + +type ItemsServiceImpl struct { baseURL string client *http.Client } -func (s *ItemsService) PatchItem(ctx context.Context, id int32, request ItemPatchRequest) (*Item, error) { +func (s *ItemsServiceImpl) PatchItem(ctx context.Context, id int32, request ItemPatchRequest) (*Item, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -83,18 +93,35 @@ func (s *ItemsService) PatchItem(ctx context.Context, id int32, request ItemPatc } type PachcaClient struct { - Items *ItemsService + Items ItemsService +} + +type clientConfig struct { + baseURL string + items ItemsService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.example.com/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithItems(service ItemsService) ClientOption { + return func(cfg *clientConfig) { cfg.items = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Items: &ItemsService{baseURL: url, client: client}, + Items: func() ItemsService { if cfg.items != nil { return cfg.items }; return &ItemsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/patch/snapshots/kt/Client.kt b/packages/generator/tests/patch/snapshots/kt/Client.kt index e6d58c79..b35908ff 100644 --- a/packages/generator/tests/patch/snapshots/kt/Client.kt +++ b/packages/generator/tests/patch/snapshots/kt/Client.kt @@ -13,11 +13,17 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class ItemsService internal constructor( +abstract class ItemsService { + open suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { + throw NotImplementedError("Items.patchItem is not implemented") + } +} + +class ItemsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { +) : ItemsService() { + override suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { val response = client.patch("$baseUrl/items/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -29,7 +35,11 @@ class ItemsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1") : Closeable { +data class PachcaServices( + val items: ItemsService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -48,7 +58,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1" } } - val items = ItemsService(baseUrl, client) + val items: ItemsService = services.items ?: ItemsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/patch/snapshots/py/client.py b/packages/generator/tests/patch/snapshots/py/client.py index bde4cefb..14a279bf 100644 --- a/packages/generator/tests/patch/snapshots/py/client.py +++ b/packages/generator/tests/patch/snapshots/py/client.py @@ -1,11 +1,22 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ItemPatchRequest, Item, ApiError from .utils import deserialize, serialize, RetryTransport class ItemsService: + async def patch_item( + self, + id: int, + request: ItemPatchRequest, + ) -> Item: + raise NotImplementedError("Items.patchItem is not implemented") + + +class ItemsServiceImpl(ItemsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -26,14 +37,20 @@ async def patch_item( raise deserialize(ApiError, body) +@dataclass +class PachcaServices: + items: ItemsService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.example.com/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.example.com/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.items = ItemsService(self._client) + self.items: ItemsService = services.items or ItemsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/patch/snapshots/swift/Client.swift b/packages/generator/tests/patch/snapshots/swift/Client.swift index 08813432..d8a2989a 100644 --- a/packages/generator/tests/patch/snapshots/swift/Client.swift +++ b/packages/generator/tests/patch/snapshots/swift/Client.swift @@ -3,18 +3,31 @@ import Foundation import FoundationNetworking #endif -public struct ItemsService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class ItemsService { + public init() {} + + open func patchItem(id: Int, request body: ItemPatchRequest) async throws -> Item { + throw pachcaNotImplemented("Items.patchItem") + } +} + +public final class ItemsServiceImpl: ItemsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func patchItem(id: Int, request body: ItemPatchRequest) async throws -> Item { + public override func patchItem(id: Int, request body: ItemPatchRequest) async throws -> Item { var request = URLRequest(url: URL(string: "\(baseURL)/items/\(id)")!) request.httpMethod = "PATCH" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -31,11 +44,17 @@ public struct ItemsService { } } +public struct PachcaServices { + public var items: ItemsService? = nil + + public init() {} +} + public struct PachcaClient { public let items: ItemsService - public init(token: String, baseURL: String = "https://api.example.com/v1") { + public init(token: String, baseURL: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.items = ItemsService(baseURL: baseURL, headers: headers) + self.items = services.items ?? ItemsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/patch/snapshots/ts/client.ts b/packages/generator/tests/patch/snapshots/ts/client.ts index c4340151..c8f74033 100644 --- a/packages/generator/tests/patch/snapshots/ts/client.ts +++ b/packages/generator/tests/patch/snapshots/ts/client.ts @@ -1,13 +1,19 @@ import { ItemPatchRequest, Item, ApiError } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -class ItemsService { +export abstract class ItemsService { + async patchItem(id: number, request: ItemPatchRequest): Promise { + throw new Error("Items.patchItem is not implemented"); + } +} + +export class ItemsServiceImpl extends ItemsService { constructor( private baseUrl: string, private headers: Record, ) {} - async patchItem(id: number, request: ItemPatchRequest): Promise { + override async patchItem(id: number, request: ItemPatchRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/items/${id}`, { method: "PATCH", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -23,11 +29,15 @@ class ItemsService { } } +export interface PachcaServices { + items?: ItemsService; +} + export class PachcaClient { readonly items: ItemsService; - constructor(token: string, baseUrl: string = "https://api.example.com/v1") { + constructor(token: string, baseUrl: string = "https://api.example.com/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.items = new ItemsService(baseUrl, headers); + this.items = services.items ?? new ItemsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/record/snapshots/cs/Client.cs b/packages/generator/tests/record/snapshots/cs/Client.cs index 6ffdfeb3..83cd4ad7 100644 --- a/packages/generator/tests/record/snapshots/cs/Client.cs +++ b/packages/generator/tests/record/snapshots/cs/Client.cs @@ -11,18 +11,30 @@ namespace Pachca.Sdk; -public sealed class LinkPreviewsService +public abstract class LinkPreviewsService +{ + + public virtual async System.Threading.Tasks.Task CreateLinkPreviewsAsync( + int id, + LinkPreviewsRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Link Previews.createLinkPreviews is not implemented"); + } +} + +public sealed class LinkPreviewsServiceImpl : LinkPreviewsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal LinkPreviewsService(string baseUrl, HttpClient client) + internal LinkPreviewsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task CreateLinkPreviewsAsync( + public override async System.Threading.Tasks.Task CreateLinkPreviewsAsync( int id, LinkPreviewsRequest request, CancellationToken cancellationToken = default) @@ -48,15 +60,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public LinkPreviewsService? LinkPreviews { get; init; } + } + public LinkPreviewsService LinkPreviews { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - LinkPreviews = new LinkPreviewsService(baseUrl, _client); + LinkPreviews = services.LinkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/record/snapshots/go/client.go b/packages/generator/tests/record/snapshots/go/client.go index 91e9c6ff..270fe968 100644 --- a/packages/generator/tests/record/snapshots/go/client.go +++ b/packages/generator/tests/record/snapshots/go/client.go @@ -46,12 +46,22 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type LinkPreviewsService struct { +type LinkPreviewsService interface { + CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error +} + +type LinkPreviewsServiceStub struct{} + +func (s *LinkPreviewsServiceStub) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { + return fmt.Errorf("Link Previews.createLinkPreviews is not implemented") +} + +type LinkPreviewsServiceImpl struct { baseURL string client *http.Client } -func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { +func (s *LinkPreviewsServiceImpl) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { body, err := json.Marshal(request) if err != nil { return err @@ -81,18 +91,35 @@ func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, } type PachcaClient struct { - LinkPreviews *LinkPreviewsService + LinkPreviews LinkPreviewsService +} + +type clientConfig struct { + baseURL string + linkPreviews LinkPreviewsService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithLinkPreviews(service LinkPreviewsService) ClientOption { + return func(cfg *clientConfig) { cfg.linkPreviews = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - LinkPreviews: &LinkPreviewsService{baseURL: url, client: client}, + LinkPreviews: func() LinkPreviewsService { if cfg.linkPreviews != nil { return cfg.linkPreviews }; return &LinkPreviewsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/record/snapshots/kt/Client.kt b/packages/generator/tests/record/snapshots/kt/Client.kt index ab6a6a8e..5b5cf3b8 100644 --- a/packages/generator/tests/record/snapshots/kt/Client.kt +++ b/packages/generator/tests/record/snapshots/kt/Client.kt @@ -13,11 +13,17 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class LinkPreviewsService internal constructor( +abstract class LinkPreviewsService { + open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { + throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") + } +} + +class LinkPreviewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { +) : LinkPreviewsService() { + override suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { val response = client.post("$baseUrl/messages/$id/link_previews") { contentType(ContentType.Application.Json) setBody(request) @@ -30,7 +36,11 @@ class LinkPreviewsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val linkPreviews: LinkPreviewsService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -49,7 +59,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val linkPreviews = LinkPreviewsService(baseUrl, client) + val linkPreviews: LinkPreviewsService = services.linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/record/snapshots/py/client.py b/packages/generator/tests/record/snapshots/py/client.py index e52317a7..061dd59d 100644 --- a/packages/generator/tests/record/snapshots/py/client.py +++ b/packages/generator/tests/record/snapshots/py/client.py @@ -1,11 +1,22 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import LinkPreviewsRequest, OAuthError, ApiError from .utils import deserialize, serialize, RetryTransport class LinkPreviewsService: + async def create_link_previews( + self, + id: int, + request: LinkPreviewsRequest, + ) -> None: + raise NotImplementedError("Link Previews.createLinkPreviews is not implemented") + + +class LinkPreviewsServiceImpl(LinkPreviewsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -27,14 +38,20 @@ async def create_link_previews( raise deserialize(ApiError, response.json()) +@dataclass +class PachcaServices: + link_previews: LinkPreviewsService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.link_previews = LinkPreviewsService(self._client) + self.link_previews: LinkPreviewsService = services.link_previews or LinkPreviewsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/record/snapshots/swift/Client.swift b/packages/generator/tests/record/snapshots/swift/Client.swift index d9907a27..2798597e 100644 --- a/packages/generator/tests/record/snapshots/swift/Client.swift +++ b/packages/generator/tests/record/snapshots/swift/Client.swift @@ -3,18 +3,31 @@ import Foundation import FoundationNetworking #endif -public struct LinkPreviewsService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class LinkPreviewsService { + public init() {} + + open func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { + throw pachcaNotImplemented("Link Previews.createLinkPreviews") + } +} + +public final class LinkPreviewsServiceImpl: LinkPreviewsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { + public override func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/link_previews")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -33,11 +46,17 @@ public struct LinkPreviewsService { } } +public struct PachcaServices { + public var linkPreviews: LinkPreviewsService? = nil + + public init() {} +} + public struct PachcaClient { public let linkPreviews: LinkPreviewsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.linkPreviews = LinkPreviewsService(baseURL: baseURL, headers: headers) + self.linkPreviews = services.linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/record/snapshots/ts/client.ts b/packages/generator/tests/record/snapshots/ts/client.ts index 3486be90..05104c67 100644 --- a/packages/generator/tests/record/snapshots/ts/client.ts +++ b/packages/generator/tests/record/snapshots/ts/client.ts @@ -1,13 +1,19 @@ import { LinkPreviewsRequest, OAuthError, ApiError } from "./types"; import { serialize, fetchWithRetry } from "./utils"; -class LinkPreviewsService { +export abstract class LinkPreviewsService { + async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + throw new Error("Link Previews.createLinkPreviews is not implemented"); + } +} + +export class LinkPreviewsServiceImpl extends LinkPreviewsService { constructor( private baseUrl: string, private headers: Record, ) {} - async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + override async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/link_previews`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -24,11 +30,15 @@ class LinkPreviewsService { } } +export interface PachcaServices { + linkPreviews?: LinkPreviewsService; +} + export class PachcaClient { readonly linkPreviews: LinkPreviewsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.linkPreviews = new LinkPreviewsService(baseUrl, headers); + this.linkPreviews = services.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/redirect/snapshots/cs/Client.cs b/packages/generator/tests/redirect/snapshots/cs/Client.cs index c6ee8d27..d051bc0d 100644 --- a/packages/generator/tests/redirect/snapshots/cs/Client.cs +++ b/packages/generator/tests/redirect/snapshots/cs/Client.cs @@ -11,18 +11,27 @@ namespace Pachca.Sdk; -public sealed class CommonService +public abstract class CommonService +{ + + public virtual async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.downloadExport is not implemented"); + } +} + +public sealed class CommonServiceImpl : CommonService { private readonly string _baseUrl; private readonly HttpClient _client; - internal CommonService(string baseUrl, HttpClient client) + internal CommonServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/exports/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -45,10 +54,16 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public CommonService? Common { get; init; } + } + public CommonService Common { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); var handler = new SocketsHttpHandler { AllowAutoRedirect = false, @@ -57,7 +72,7 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Common = new CommonService(baseUrl, _client); + Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/redirect/snapshots/go/client.go b/packages/generator/tests/redirect/snapshots/go/client.go index ae944414..6351f8a4 100644 --- a/packages/generator/tests/redirect/snapshots/go/client.go +++ b/packages/generator/tests/redirect/snapshots/go/client.go @@ -46,12 +46,22 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type CommonService struct { +type CommonService interface { + DownloadExport(ctx context.Context, id int32) (string, error) +} + +type CommonServiceStub struct{} + +func (s *CommonServiceStub) DownloadExport(ctx context.Context, id int32) (string, error) { + return "", fmt.Errorf("Common.downloadExport is not implemented") +} + +type CommonServiceImpl struct { baseURL string client *http.Client } -func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, error) { +func (s *CommonServiceImpl) DownloadExport(ctx context.Context, id int32) (string, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/exports/%v", s.baseURL, id), nil) if err != nil { return "", err @@ -80,14 +90,31 @@ func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, e } type PachcaClient struct { - Common *CommonService + Common CommonService +} + +type clientConfig struct { + baseURL string + common CommonService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithCommon(service CommonService) ClientOption { + return func(cfg *clientConfig) { cfg.common = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -95,6 +122,6 @@ func NewPachcaClient(token string, baseURL ...string) *PachcaClient { }, } return &PachcaClient{ - Common: &CommonService{baseURL: url, client: client}, + Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/redirect/snapshots/kt/Client.kt b/packages/generator/tests/redirect/snapshots/kt/Client.kt index 7fdbc4a5..09d5b791 100644 --- a/packages/generator/tests/redirect/snapshots/kt/Client.kt +++ b/packages/generator/tests/redirect/snapshots/kt/Client.kt @@ -13,11 +13,17 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class CommonService internal constructor( +abstract class CommonService { + open suspend fun downloadExport(id: Int): String { + throw NotImplementedError("Common.downloadExport is not implemented") + } +} + +class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun downloadExport(id: Int): String { +) : CommonService() { + override suspend fun downloadExport(id: Int): String { val response = client.get("$baseUrl/exports/$id") return when (response.status.value) { 302 -> response.headers[HttpHeaders.Location] @@ -28,7 +34,11 @@ class CommonService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val common: CommonService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false followRedirects = false @@ -48,7 +58,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val common = CommonService(baseUrl, client) + val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/redirect/snapshots/py/client.py b/packages/generator/tests/redirect/snapshots/py/client.py index 9a2fbe6e..e8afda0d 100644 --- a/packages/generator/tests/redirect/snapshots/py/client.py +++ b/packages/generator/tests/redirect/snapshots/py/client.py @@ -1,11 +1,21 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import OAuthError, ApiError from .utils import deserialize, RetryTransport class CommonService: + async def download_export( + self, + id: int, + ) -> str: + raise NotImplementedError("Common.downloadExport is not implemented") + + +class CommonServiceImpl(CommonService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -31,14 +41,20 @@ async def download_export( raise deserialize(ApiError, response.json()) +@dataclass +class PachcaServices: + common: CommonService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.common = CommonService(self._client) + self.common: CommonService = services.common or CommonServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/redirect/snapshots/swift/Client.swift b/packages/generator/tests/redirect/snapshots/swift/Client.swift index f6a80bdd..7b318ca1 100644 --- a/packages/generator/tests/redirect/snapshots/swift/Client.swift +++ b/packages/generator/tests/redirect/snapshots/swift/Client.swift @@ -3,18 +3,31 @@ import Foundation import FoundationNetworking #endif -public struct CommonService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class CommonService { + public init() {} + + open func downloadExport(id: Int) async throws -> String { + throw pachcaNotImplemented("Common.downloadExport") + } +} + +public final class CommonServiceImpl: CommonService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func downloadExport(id: Int) async throws -> String { + public override func downloadExport(id: Int) async throws -> String { var request = URLRequest(url: URL(string: "\(baseURL)/exports/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let delegate = RedirectPreventer() @@ -46,11 +59,17 @@ private final class RedirectPreventer: NSObject, URLSessionTaskDelegate { } } +public struct PachcaServices { + public var common: CommonService? = nil + + public init() {} +} + public struct PachcaClient { public let common: CommonService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.common = CommonService(baseURL: baseURL, headers: headers) + self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/redirect/snapshots/ts/client.ts b/packages/generator/tests/redirect/snapshots/ts/client.ts index 62e389e7..e5b89bd4 100644 --- a/packages/generator/tests/redirect/snapshots/ts/client.ts +++ b/packages/generator/tests/redirect/snapshots/ts/client.ts @@ -1,13 +1,19 @@ import { OAuthError, ApiError } from "./types"; import { fetchWithRetry } from "./utils"; -class CommonService { +export abstract class CommonService { + async downloadExport(id: number): Promise { + throw new Error("Common.downloadExport is not implemented"); + } +} + +export class CommonServiceImpl extends CommonService { constructor( private baseUrl: string, private headers: Record, ) {} - async downloadExport(id: number): Promise { + override async downloadExport(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/exports/${id}`, { headers: this.headers, redirect: "manual", @@ -28,11 +34,15 @@ class CommonService { } } +export interface PachcaServices { + common?: CommonService; +} + export class PachcaClient { readonly common: CommonService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.common = new CommonService(baseUrl, headers); + this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/search/snapshots/cs/Client.cs b/packages/generator/tests/search/snapshots/cs/Client.cs index d098afc2..9e933be5 100644 --- a/packages/generator/tests/search/snapshots/cs/Client.cs +++ b/packages/generator/tests/search/snapshots/cs/Client.cs @@ -11,18 +11,49 @@ namespace Pachca.Sdk; -public sealed class SearchService +public abstract class SearchService +{ + + public virtual async System.Threading.Tasks.Task SearchMessagesAsync( + string query, + List? chatIds = null, + List? userIds = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + SearchSort? sort = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchMessages is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> SearchMessagesAllAsync( + string query, + List? chatIds = null, + List? userIds = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + SearchSort? sort = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchMessagesAll is not implemented"); + } +} + +public sealed class SearchServiceImpl : SearchService { private readonly string _baseUrl; private readonly HttpClient _client; - internal SearchService(string baseUrl, HttpClient client) + internal SearchServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task SearchMessagesAsync( + public override async System.Threading.Tasks.Task SearchMessagesAsync( string query, List? chatIds = null, List? userIds = null, @@ -66,7 +97,7 @@ public async System.Threading.Tasks.Task SearchMessagesA } } - public async System.Threading.Tasks.Task> SearchMessagesAllAsync( + public override async System.Threading.Tasks.Task> SearchMessagesAllAsync( string query, List? chatIds = null, List? userIds = null, @@ -92,15 +123,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public SearchService? Search { get; init; } + } + public SearchService Search { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Search = new SearchService(baseUrl, _client); + Search = services.Search ?? new SearchServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/search/snapshots/go/client.go b/packages/generator/tests/search/snapshots/go/client.go index 5868f5ea..13805f10 100644 --- a/packages/generator/tests/search/snapshots/go/client.go +++ b/packages/generator/tests/search/snapshots/go/client.go @@ -46,12 +46,27 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type SearchService struct { +type SearchService interface { + SearchMessages(ctx context.Context, params SearchMessagesParams) (*SearchMessagesResponse, error) + SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]MessageSearchResult, error) +} + +type SearchServiceStub struct{} + +func (s *SearchServiceStub) SearchMessages(ctx context.Context, params SearchMessagesParams) (*SearchMessagesResponse, error) { + return nil, fmt.Errorf("Search.searchMessages is not implemented") +} + +func (s *SearchServiceStub) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]MessageSearchResult, error) { + return nil, fmt.Errorf("Search.searchMessagesAll is not implemented") +} + +type SearchServiceImpl struct { baseURL string client *http.Client } -func (s *SearchService) SearchMessages(ctx context.Context, params SearchMessagesParams) (*SearchMessagesResponse, error) { +func (s *SearchServiceImpl) SearchMessages(ctx context.Context, params SearchMessagesParams) (*SearchMessagesResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/messages", s.baseURL)) if err != nil { return nil, err @@ -105,7 +120,7 @@ func (s *SearchService) SearchMessages(ctx context.Context, params SearchMessage } } -func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]MessageSearchResult, error) { +func (s *SearchServiceImpl) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]MessageSearchResult, error) { if params == nil { params = &SearchMessagesParams{} } @@ -126,18 +141,35 @@ func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMes } type PachcaClient struct { - Search *SearchService + Search SearchService +} + +type clientConfig struct { + baseURL string + search SearchService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithSearch(service SearchService) ClientOption { + return func(cfg *clientConfig) { cfg.search = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Search: &SearchService{baseURL: url, client: client}, + Search: func() SearchService { if cfg.search != nil { return cfg.search }; return &SearchServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/search/snapshots/kt/Client.kt b/packages/generator/tests/search/snapshots/kt/Client.kt index 1c1cf20e..64b4b1e5 100644 --- a/packages/generator/tests/search/snapshots/kt/Client.kt +++ b/packages/generator/tests/search/snapshots/kt/Client.kt @@ -13,11 +13,38 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class SearchService internal constructor( +abstract class SearchService { + open suspend fun searchMessages( + query: String, + chatIds: List? = null, + userIds: List? = null, + createdFrom: String? = null, + createdTo: String? = null, + sort: SearchSort? = null, + limit: Int? = null, + cursor: String? = null, + ): SearchMessagesResponse { + throw NotImplementedError("Search.searchMessages is not implemented") + } + + open suspend fun searchMessagesAll( + query: String, + chatIds: List? = null, + userIds: List? = null, + createdFrom: String? = null, + createdTo: String? = null, + sort: SearchSort? = null, + limit: Int? = null, + ): List { + throw NotImplementedError("Search.searchMessagesAll is not implemented") + } +} + +class SearchServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun searchMessages( +) : SearchService() { + override suspend fun searchMessages( query: String, chatIds: List? = null, userIds: List? = null, @@ -44,7 +71,7 @@ class SearchService internal constructor( } } - suspend fun searchMessagesAll( + override suspend fun searchMessagesAll( query: String, chatIds: List? = null, userIds: List? = null, @@ -73,7 +100,11 @@ class SearchService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val search: SearchService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -92,7 +123,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val search = SearchService(baseUrl, client) + val search: SearchService = services.search ?: SearchServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/search/snapshots/py/client.py b/packages/generator/tests/search/snapshots/py/client.py index b7f30dd4..3b0ecaac 100644 --- a/packages/generator/tests/search/snapshots/py/client.py +++ b/packages/generator/tests/search/snapshots/py/client.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ( @@ -12,6 +14,20 @@ from .utils import deserialize, RetryTransport class SearchService: + async def search_messages( + self, + params: SearchMessagesParams, + ) -> SearchMessagesResponse: + raise NotImplementedError("Search.searchMessages is not implemented") + + async def search_messages_all( + self, + params: SearchMessagesParams, + ) -> list[MessageSearchResult]: + raise NotImplementedError("Search.searchMessagesAll is not implemented") + + +class SearchServiceImpl(SearchService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -70,14 +86,20 @@ async def search_messages_all( return items +@dataclass +class PachcaServices: + search: SearchService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.search = SearchService(self._client) + self.search: SearchService = services.search or SearchServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/search/snapshots/swift/Client.swift b/packages/generator/tests/search/snapshots/swift/Client.swift index 98fc752e..3a7d9da1 100644 --- a/packages/generator/tests/search/snapshots/swift/Client.swift +++ b/packages/generator/tests/search/snapshots/swift/Client.swift @@ -3,18 +3,35 @@ import Foundation import FoundationNetworking #endif -public struct SearchService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class SearchService { + public init() {} + + open func searchMessages(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> SearchMessagesResponse { + throw pachcaNotImplemented("Search.searchMessages") + } + + open func searchMessagesAll(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil) async throws -> [MessageSearchResult] { + throw pachcaNotImplemented("Search.searchMessagesAll") + } +} + +public final class SearchServiceImpl: SearchService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func searchMessages(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> SearchMessagesResponse { + public override func searchMessages(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> SearchMessagesResponse { var components = URLComponents(string: "\(baseURL)/search/messages")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "query", value: String(query))) @@ -40,7 +57,7 @@ public struct SearchService { } } - public func searchMessagesAll(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil) async throws -> [MessageSearchResult] { + public override func searchMessagesAll(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil) async throws -> [MessageSearchResult] { var items: [MessageSearchResult] = [] var cursor: String? = nil repeat { @@ -52,11 +69,17 @@ public struct SearchService { } } +public struct PachcaServices { + public var search: SearchService? = nil + + public init() {} +} + public struct PachcaClient { public let search: SearchService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.search = SearchService(baseURL: baseURL, headers: headers) + self.search = services.search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/search/snapshots/ts/client.ts b/packages/generator/tests/search/snapshots/ts/client.ts index aa0b0e4a..6567036b 100644 --- a/packages/generator/tests/search/snapshots/ts/client.ts +++ b/packages/generator/tests/search/snapshots/ts/client.ts @@ -6,13 +6,23 @@ import { } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -class SearchService { +export abstract class SearchService { + async searchMessages(params: SearchMessagesParams): Promise { + throw new Error("Search.searchMessages is not implemented"); + } + + async searchMessagesAll(params: Omit): Promise { + throw new Error("Search.searchMessagesAll is not implemented"); + } +} + +export class SearchServiceImpl extends SearchService { constructor( private baseUrl: string, private headers: Record, ) {} - async searchMessages(params: SearchMessagesParams): Promise { + override async searchMessages(params: SearchMessagesParams): Promise { const query = new URLSearchParams(); query.set("query", params.query); if (params?.chatIds !== undefined) { @@ -40,7 +50,7 @@ class SearchService { } } - async searchMessagesAll(params: Omit): Promise { + override async searchMessagesAll(params: Omit): Promise { const items: MessageSearchResult[] = []; let cursor: string | undefined; do { @@ -52,11 +62,15 @@ class SearchService { } } +export interface PachcaServices { + search?: SearchService; +} + export class PachcaClient { readonly search: SearchService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.search = new SearchService(baseUrl, headers); + this.search = services.search ?? new SearchServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/unwrap/snapshots/cs/Client.cs b/packages/generator/tests/unwrap/snapshots/cs/Client.cs index b3347652..0376c99a 100644 --- a/packages/generator/tests/unwrap/snapshots/cs/Client.cs +++ b/packages/generator/tests/unwrap/snapshots/cs/Client.cs @@ -11,18 +11,30 @@ namespace Pachca.Sdk; -public sealed class MembersService +public abstract class MembersService +{ + + public virtual async System.Threading.Tasks.Task AddMembersAsync( + int id, + List memberIds, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.addMembers is not implemented"); + } +} + +public sealed class MembersServiceImpl : MembersService { private readonly string _baseUrl; private readonly HttpClient _client; - internal MembersService(string baseUrl, HttpClient client) + internal MembersServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task AddMembersAsync( + public override async System.Threading.Tasks.Task AddMembersAsync( int id, List memberIds, CancellationToken cancellationToken = default) @@ -45,18 +57,32 @@ public async System.Threading.Tasks.Task AddMembersAsync( } } -public sealed class ChatsService +public abstract class ChatsService +{ + + public virtual async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.createChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.archiveChat is not implemented"); + } +} + +public sealed class ChatsServiceImpl : ChatsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ChatsService(string baseUrl, HttpClient client) + internal ChatsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -74,7 +100,7 @@ public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest } } - public async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}/archive"; using var request = new HttpRequestMessage(HttpMethod.Put, url); @@ -96,17 +122,24 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public ChatsService? Chats { get; init; } + public MembersService? Members { get; init; } + } + public ChatsService Chats { get; } public MembersService Members { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Chats = new ChatsService(baseUrl, _client); - Members = new MembersService(baseUrl, _client); + Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); + Members = services.Members ?? new MembersServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/unwrap/snapshots/go/client.go b/packages/generator/tests/unwrap/snapshots/go/client.go index 91ce3b41..be0ec69d 100644 --- a/packages/generator/tests/unwrap/snapshots/go/client.go +++ b/packages/generator/tests/unwrap/snapshots/go/client.go @@ -46,12 +46,22 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type MembersService struct { +type MembersService interface { + AddMembers(ctx context.Context, id int32, memberIds []int32) error +} + +type MembersServiceStub struct{} + +func (s *MembersServiceStub) AddMembers(ctx context.Context, id int32, memberIds []int32) error { + return fmt.Errorf("Members.addMembers is not implemented") +} + +type MembersServiceImpl struct { baseURL string client *http.Client } -func (s *MembersService) AddMembers(ctx context.Context, id int32, memberIds []int32) error { +func (s *MembersServiceImpl) AddMembers(ctx context.Context, id int32, memberIds []int32) error { body, err := json.Marshal(map[string]any{"member_ids": memberIds}) if err != nil { return err @@ -80,12 +90,27 @@ func (s *MembersService) AddMembers(ctx context.Context, id int32, memberIds []i } } -type ChatsService struct { +type ChatsService interface { + CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) + ArchiveChat(ctx context.Context, id int32) error +} + +type ChatsServiceStub struct{} + +func (s *ChatsServiceStub) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { + return nil, fmt.Errorf("Chats.createChat is not implemented") +} + +func (s *ChatsServiceStub) ArchiveChat(ctx context.Context, id int32) error { + return fmt.Errorf("Chats.archiveChat is not implemented") +} + +type ChatsServiceImpl struct { baseURL string client *http.Client } -func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { +func (s *ChatsServiceImpl) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -120,7 +145,7 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest } } -func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { +func (s *ChatsServiceImpl) ArchiveChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/chats/%v/archive", s.baseURL, id), nil) if err != nil { return err @@ -145,20 +170,42 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { } type PachcaClient struct { - Chats *ChatsService - Members *MembersService + Chats ChatsService + Members MembersService } +type clientConfig struct { + baseURL string + chats ChatsService + members MembersService +} + +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithChats(service ChatsService) ClientOption { + return func(cfg *clientConfig) { cfg.chats = service } +} + +func WithMembers(service MembersService) ClientOption { + return func(cfg *clientConfig) { cfg.members = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Chats : &ChatsService{baseURL: url, client: client}, - Members: &MembersService{baseURL: url, client: client}, + Chats : func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Members: func() MembersService { if cfg.members != nil { return cfg.members }; return &MembersServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/unwrap/snapshots/kt/Client.kt b/packages/generator/tests/unwrap/snapshots/kt/Client.kt index 66c39fa7..b32635ed 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/Client.kt +++ b/packages/generator/tests/unwrap/snapshots/kt/Client.kt @@ -13,11 +13,17 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class MembersService internal constructor( +abstract class MembersService { + open suspend fun addMembers(id: Int, memberIds: List) { + throw NotImplementedError("Members.addMembers is not implemented") + } +} + +class MembersServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun addMembers(id: Int, memberIds: List) { +) : MembersService() { + override suspend fun addMembers(id: Int, memberIds: List) { val response = client.post("$baseUrl/chats/$id/members") { contentType(ContentType.Application.Json) setBody(AddMembersRequest(memberIds = memberIds)) @@ -30,11 +36,21 @@ class MembersService internal constructor( } } -class ChatsService internal constructor( +abstract class ChatsService { + open suspend fun createChat(request: ChatCreateRequest): Chat { + throw NotImplementedError("Chats.createChat is not implemented") + } + + open suspend fun archiveChat(id: Int) { + throw NotImplementedError("Chats.archiveChat is not implemented") + } +} + +class ChatsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun createChat(request: ChatCreateRequest): Chat { +) : ChatsService() { + override suspend fun createChat(request: ChatCreateRequest): Chat { val response = client.post("$baseUrl/chats") { contentType(ContentType.Application.Json) setBody(request) @@ -46,7 +62,7 @@ class ChatsService internal constructor( } } - suspend fun archiveChat(id: Int) { + override suspend fun archiveChat(id: Int) { val response = client.put("$baseUrl/chats/$id/archive") when (response.status.value) { 204 -> return @@ -56,7 +72,12 @@ class ChatsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val chats: ChatsService? = null, + val members: MembersService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -75,8 +96,8 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val chats = ChatsService(baseUrl, client) - val members = MembersService(baseUrl, client) + val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) + val members: MembersService = services.members ?: MembersServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/unwrap/snapshots/py/client.py b/packages/generator/tests/unwrap/snapshots/py/client.py index 86aa8a30..61f559cb 100644 --- a/packages/generator/tests/unwrap/snapshots/py/client.py +++ b/packages/generator/tests/unwrap/snapshots/py/client.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ( @@ -11,6 +13,15 @@ from .utils import deserialize, serialize, RetryTransport class MembersService: + async def add_members( + self, + id: int, + member_ids: list[int], + ) -> None: + raise NotImplementedError("Members.addMembers is not implemented") + + +class MembersServiceImpl(MembersService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -33,6 +44,20 @@ async def add_members( class ChatsService: + async def create_chat( + self, + request: ChatCreateRequest, + ) -> Chat: + raise NotImplementedError("Chats.createChat is not implemented") + + async def archive_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Chats.archiveChat is not implemented") + + +class ChatsServiceImpl(ChatsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -69,15 +94,22 @@ async def archive_chat( raise deserialize(ApiError, response.json()) +@dataclass +class PachcaServices: + chats: ChatsService | None = None + members: MembersService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.chats = ChatsService(self._client) - self.members = MembersService(self._client) + self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) + self.members: MembersService = services.members or MembersServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/unwrap/snapshots/swift/Client.swift b/packages/generator/tests/unwrap/snapshots/swift/Client.swift index fb1531e6..7233b1fd 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Client.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Client.swift @@ -3,18 +3,31 @@ import Foundation import FoundationNetworking #endif -public struct MembersService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class MembersService { + public init() {} + + open func addMembers(id: Int, memberIds: [Int]) async throws -> Void { + throw pachcaNotImplemented("Members.addMembers") + } +} + +public final class MembersServiceImpl: MembersService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func addMembers(id: Int, memberIds: [Int]) async throws -> Void { + public override func addMembers(id: Int, memberIds: [Int]) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/members")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -33,18 +46,31 @@ public struct MembersService { } } -public struct ChatsService { +open class ChatsService { + public init() {} + + open func createChat(request body: ChatCreateRequest) async throws -> Chat { + throw pachcaNotImplemented("Chats.createChat") + } + + open func archiveChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Chats.archiveChat") + } +} + +public final class ChatsServiceImpl: ChatsService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func createChat(request body: ChatCreateRequest) async throws -> Chat { + public override func createChat(request body: ChatCreateRequest) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -62,7 +88,7 @@ public struct ChatsService { } } - public func archiveChat(id: Int) async throws -> Void { + public override func archiveChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/archive")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -79,13 +105,20 @@ public struct ChatsService { } } +public struct PachcaServices { + public var chats: ChatsService? = nil + public var members: MembersService? = nil + + public init() {} +} + public struct PachcaClient { public let chats: ChatsService public let members: MembersService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = ChatsService(baseURL: baseURL, headers: headers) - self.members = MembersService(baseURL: baseURL, headers: headers) + self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.members = services.members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/unwrap/snapshots/ts/client.ts b/packages/generator/tests/unwrap/snapshots/ts/client.ts index b81c4050..675a8396 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/client.ts +++ b/packages/generator/tests/unwrap/snapshots/ts/client.ts @@ -6,13 +6,19 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -class MembersService { +export abstract class MembersService { + async addMembers(id: number, memberIds: number[]): Promise { + throw new Error("Members.addMembers is not implemented"); + } +} + +export class MembersServiceImpl extends MembersService { constructor( private baseUrl: string, private headers: Record, ) {} - async addMembers(id: number, memberIds: number[]): Promise { + override async addMembers(id: number, memberIds: number[]): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -29,13 +35,23 @@ class MembersService { } } -class ChatsService { +export abstract class ChatsService { + async createChat(request: ChatCreateRequest): Promise { + throw new Error("Chats.createChat is not implemented"); + } + + async archiveChat(id: number): Promise { + throw new Error("Chats.archiveChat is not implemented"); + } +} + +export class ChatsServiceImpl extends ChatsService { constructor( private baseUrl: string, private headers: Record, ) {} - async createChat(request: ChatCreateRequest): Promise { + override async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -52,7 +68,7 @@ class ChatsService { } } - async archiveChat(id: number): Promise { + override async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -68,13 +84,18 @@ class ChatsService { } } +export interface PachcaServices { + chats?: ChatsService; + members?: MembersService; +} + export class PachcaClient { readonly chats: ChatsService; readonly members: MembersService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.chats = new ChatsService(baseUrl, headers); - this.members = new MembersService(baseUrl, headers); + this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.members = services.members ?? new MembersServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/upload/snapshots/cs/Client.cs b/packages/generator/tests/upload/snapshots/cs/Client.cs index 298e3beb..9bd1c371 100644 --- a/packages/generator/tests/upload/snapshots/cs/Client.cs +++ b/packages/generator/tests/upload/snapshots/cs/Client.cs @@ -12,18 +12,35 @@ namespace Pachca.Sdk; -public sealed class CommonService +public abstract class CommonService +{ + + public virtual async System.Threading.Tasks.Task UploadFileAsync( + string directUrl, + FileUploadRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.uploadFile is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.getUploadParams is not implemented"); + } +} + +public sealed class CommonServiceImpl : CommonService { private readonly string _baseUrl; private readonly HttpClient _client; - internal CommonService(string baseUrl, HttpClient client) + internal CommonServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task UploadFileAsync( + public override async System.Threading.Tasks.Task UploadFileAsync( string directUrl, FileUploadRequest request, CancellationToken cancellationToken = default) @@ -55,7 +72,7 @@ public async System.Threading.Tasks.Task UploadFileAsync( } } - public async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/uploads"; using var request = new HttpRequestMessage(HttpMethod.Post, url); @@ -77,15 +94,21 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public CommonService? Common { get; init; } + } + public CommonService Common { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Common = new CommonService(baseUrl, _client); + Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/upload/snapshots/go/client.go b/packages/generator/tests/upload/snapshots/go/client.go index 954ce86e..81a74581 100644 --- a/packages/generator/tests/upload/snapshots/go/client.go +++ b/packages/generator/tests/upload/snapshots/go/client.go @@ -47,12 +47,27 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type CommonService struct { +type CommonService interface { + UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error + GetUploadParams(ctx context.Context) (*UploadParams, error) +} + +type CommonServiceStub struct{} + +func (s *CommonServiceStub) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { + return fmt.Errorf("Common.uploadFile is not implemented") +} + +func (s *CommonServiceStub) GetUploadParams(ctx context.Context) (*UploadParams, error) { + return nil, fmt.Errorf("Common.getUploadParams is not implemented") +} + +type CommonServiceImpl struct { baseURL string client *http.Client } -func (s *CommonService) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { +func (s *CommonServiceImpl) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { pr, pw := io.Pipe() writer := multipart.NewWriter(pw) go func() { @@ -98,7 +113,7 @@ func (s *CommonService) UploadFile(ctx context.Context, directUrl string, reques } } -func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, error) { +func (s *CommonServiceImpl) GetUploadParams(ctx context.Context) (*UploadParams, error) { req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/uploads", s.baseURL), nil) if err != nil { return nil, err @@ -127,18 +142,35 @@ func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, err } type PachcaClient struct { - Common *CommonService + Common CommonService +} + +type clientConfig struct { + baseURL string + common CommonService } +type ClientOption func(*clientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithCommon(service CommonService) ClientOption { + return func(cfg *clientConfig) { cfg.common = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, } return &PachcaClient{ - Common: &CommonService{baseURL: url, client: client}, + Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/packages/generator/tests/upload/snapshots/kt/Client.kt b/packages/generator/tests/upload/snapshots/kt/Client.kt index 7aa1c5fc..349a0f8c 100644 --- a/packages/generator/tests/upload/snapshots/kt/Client.kt +++ b/packages/generator/tests/upload/snapshots/kt/Client.kt @@ -14,11 +14,21 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class CommonService internal constructor( +abstract class CommonService { + open suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { + throw NotImplementedError("Common.uploadFile is not implemented") + } + + open suspend fun getUploadParams(): UploadParams { + throw NotImplementedError("Common.getUploadParams is not implemented") + } +} + +class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { +) : CommonService() { + override suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { val response = client.submitFormWithBinaryData( directUrl, formData { @@ -44,7 +54,7 @@ class CommonService internal constructor( } } - suspend fun getUploadParams(): UploadParams { + override suspend fun getUploadParams(): UploadParams { val response = client.post("$baseUrl/uploads") return when (response.status.value) { 201 -> response.body().data @@ -54,7 +64,11 @@ class CommonService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val common: CommonService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -73,7 +87,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val common = CommonService(baseUrl, client) + val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/upload/snapshots/py/client.py b/packages/generator/tests/upload/snapshots/py/client.py index e9fd7fe6..80a7b765 100644 --- a/packages/generator/tests/upload/snapshots/py/client.py +++ b/packages/generator/tests/upload/snapshots/py/client.py @@ -1,11 +1,26 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import FileUploadRequest, OAuthError, UploadParams from .utils import deserialize, RetryTransport class CommonService: + async def upload_file( + self, + direct_url: str, + request: FileUploadRequest, + ) -> None: + raise NotImplementedError("Common.uploadFile is not implemented") + + async def get_upload_params( + self) -> UploadParams: + raise NotImplementedError("Common.getUploadParams is not implemented") + + +class CommonServiceImpl(CommonService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -56,14 +71,20 @@ async def get_upload_params( ) +@dataclass +class PachcaServices: + common: CommonService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.common = CommonService(self._client) + self.common: CommonService = services.common or CommonServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/upload/snapshots/swift/Client.swift b/packages/generator/tests/upload/snapshots/swift/Client.swift index fc08c5cb..43728060 100644 --- a/packages/generator/tests/upload/snapshots/swift/Client.swift +++ b/packages/generator/tests/upload/snapshots/swift/Client.swift @@ -3,18 +3,35 @@ import Foundation import FoundationNetworking #endif -public struct CommonService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class CommonService { + public init() {} + + open func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { + throw pachcaNotImplemented("Common.uploadFile") + } + + open func getUploadParams() async throws -> UploadParams { + throw pachcaNotImplemented("Common.getUploadParams") + } +} + +public final class CommonServiceImpl: CommonService { let baseURL: String let headers: [String: String] let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + super.init() self.baseURL = baseURL self.headers = headers self.session = session } - public func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { + public override func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(directUrl)")!) request.httpMethod = "POST" let boundary = UUID().uuidString @@ -52,7 +69,7 @@ public struct CommonService { } } - public func getUploadParams() async throws -> UploadParams { + public override func getUploadParams() async throws -> UploadParams { var request = URLRequest(url: URL(string: "\(baseURL)/uploads")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -69,11 +86,17 @@ public struct CommonService { } } +public struct PachcaServices { + public var common: CommonService? = nil + + public init() {} +} + public struct PachcaClient { public let common: CommonService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.common = CommonService(baseURL: baseURL, headers: headers) + self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/upload/snapshots/ts/client.ts b/packages/generator/tests/upload/snapshots/ts/client.ts index db1d5470..861bf913 100644 --- a/packages/generator/tests/upload/snapshots/ts/client.ts +++ b/packages/generator/tests/upload/snapshots/ts/client.ts @@ -1,13 +1,23 @@ import { FileUploadRequest, OAuthError, UploadParams } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -class CommonService { +export abstract class CommonService { + async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + throw new Error("Common.uploadFile is not implemented"); + } + + async getUploadParams(): Promise { + throw new Error("Common.getUploadParams is not implemented"); + } +} + +export class CommonServiceImpl extends CommonService { constructor( private baseUrl: string, private headers: Record, ) {} - async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + override async uploadFile(directUrl: string, request: FileUploadRequest): Promise { const form = new FormData(); form.set("content-disposition", request.contentDisposition); form.set("acl", request.acl); @@ -32,7 +42,7 @@ class CommonService { } } - async getUploadParams(): Promise { + override async getUploadParams(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/uploads`, { method: "POST", headers: this.headers, @@ -49,11 +59,15 @@ class CommonService { } } +export interface PachcaServices { + common?: CommonService; +} + export class PachcaClient { readonly common: CommonService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.common = new CommonService(baseUrl, headers); + this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); } } diff --git a/sdk/csharp/generated/Client.cs b/sdk/csharp/generated/Client.cs index 044b2aae..0c6dd36f 100644 --- a/sdk/csharp/generated/Client.cs +++ b/sdk/csharp/generated/Client.cs @@ -12,18 +12,51 @@ namespace Pachca.Sdk; -public sealed class SecurityService +public abstract class SecurityService +{ + + public virtual async System.Threading.Tasks.Task GetAuditEventsAsync( + DateTimeOffset? startTime = null, + DateTimeOffset? endTime = null, + AuditEventKey? eventKey = null, + string? actorId = null, + string? actorType = null, + string? entityId = null, + string? entityType = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Security.getAuditEvents is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> GetAuditEventsAllAsync( + DateTimeOffset? startTime = null, + DateTimeOffset? endTime = null, + AuditEventKey? eventKey = null, + string? actorId = null, + string? actorType = null, + string? entityId = null, + string? entityType = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Security.getAuditEventsAll is not implemented"); + } +} + +public sealed class SecurityServiceImpl : SecurityService { private readonly string _baseUrl; private readonly HttpClient _client; - internal SecurityService(string baseUrl, HttpClient client) + internal SecurityServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task GetAuditEventsAsync( + public override async System.Threading.Tasks.Task GetAuditEventsAsync( DateTimeOffset? startTime = null, DateTimeOffset? endTime = null, AuditEventKey? eventKey = null, @@ -69,7 +102,7 @@ public async System.Threading.Tasks.Task GetAuditEventsA } } - public async System.Threading.Tasks.Task> GetAuditEventsAllAsync( + public override async System.Threading.Tasks.Task> GetAuditEventsAllAsync( DateTimeOffset? startTime = null, DateTimeOffset? endTime = null, AuditEventKey? eventKey = null, @@ -92,18 +125,50 @@ public async System.Threading.Tasks.Task> GetAuditEventsAllAsyn } } -public sealed class BotsService +public abstract class BotsService +{ + + public virtual async System.Threading.Tasks.Task GetWebhookEventsAsync( + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Bots.getWebhookEvents is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> GetWebhookEventsAllAsync( + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Bots.getWebhookEventsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateBotAsync( + int id, + BotUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Bots.updateBot is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteWebhookEventAsync(string id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Bots.deleteWebhookEvent is not implemented"); + } +} + +public sealed class BotsServiceImpl : BotsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal BotsService(string baseUrl, HttpClient client) + internal BotsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task GetWebhookEventsAsync( + public override async System.Threading.Tasks.Task GetWebhookEventsAsync( int? limit = null, string? cursor = null, CancellationToken cancellationToken = default) @@ -128,7 +193,7 @@ public async System.Threading.Tasks.Task GetWebhookEve } } - public async System.Threading.Tasks.Task> GetWebhookEventsAllAsync( + public override async System.Threading.Tasks.Task> GetWebhookEventsAllAsync( int? limit = null, CancellationToken cancellationToken = default) { @@ -143,7 +208,7 @@ public async System.Threading.Tasks.Task> GetWebhookEventsAll return items; } - public async System.Threading.Tasks.Task UpdateBotAsync( + public override async System.Threading.Tasks.Task UpdateBotAsync( int id, BotUpdateRequest request, CancellationToken cancellationToken = default) @@ -164,7 +229,7 @@ public async System.Threading.Tasks.Task UpdateBotAsync( } } - public async System.Threading.Tasks.Task DeleteWebhookEventAsync(string id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteWebhookEventAsync(string id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/webhooks/events/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -182,18 +247,75 @@ public async System.Threading.Tasks.Task DeleteWebhookEventAsync(string id, Canc } } -public sealed class ChatsService +public abstract class ChatsService +{ + + public virtual async System.Threading.Tasks.Task ListChatsAsync( + SortOrder? sortId = null, + ChatAvailability? availability = null, + DateTimeOffset? lastMessageAtAfter = null, + DateTimeOffset? lastMessageAtBefore = null, + bool? personal = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.listChats is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListChatsAllAsync( + SortOrder? sortId = null, + ChatAvailability? availability = null, + DateTimeOffset? lastMessageAtAfter = null, + DateTimeOffset? lastMessageAtBefore = null, + bool? personal = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.listChatsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.getChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.createChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateChatAsync( + int id, + ChatUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.updateChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.archiveChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UnarchiveChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Chats.unarchiveChat is not implemented"); + } +} + +public sealed class ChatsServiceImpl : ChatsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ChatsService(string baseUrl, HttpClient client) + internal ChatsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListChatsAsync( + public override async System.Threading.Tasks.Task ListChatsAsync( SortOrder? sortId = null, ChatAvailability? availability = null, DateTimeOffset? lastMessageAtAfter = null, @@ -233,7 +355,7 @@ public async System.Threading.Tasks.Task ListChatsAsync( } } - public async System.Threading.Tasks.Task> ListChatsAllAsync( + public override async System.Threading.Tasks.Task> ListChatsAllAsync( SortOrder? sortId = null, ChatAvailability? availability = null, DateTimeOffset? lastMessageAtAfter = null, @@ -253,7 +375,7 @@ public async System.Threading.Tasks.Task> ListChatsAllAsync( return items; } - public async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -270,7 +392,7 @@ public async System.Threading.Tasks.Task GetChatAsync(int id, Cancellation } } - public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -288,7 +410,7 @@ public async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest } } - public async System.Threading.Tasks.Task UpdateChatAsync( + public override async System.Threading.Tasks.Task UpdateChatAsync( int id, ChatUpdateRequest request, CancellationToken cancellationToken = default) @@ -309,7 +431,7 @@ public async System.Threading.Tasks.Task UpdateChatAsync( } } - public async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}/archive"; using var request = new HttpRequestMessage(HttpMethod.Put, url); @@ -326,7 +448,7 @@ public async System.Threading.Tasks.Task ArchiveChatAsync(int id, CancellationTo } } - public async System.Threading.Tasks.Task UnarchiveChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task UnarchiveChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}/unarchive"; using var request = new HttpRequestMessage(HttpMethod.Put, url); @@ -344,18 +466,50 @@ public async System.Threading.Tasks.Task UnarchiveChatAsync(int id, Cancellation } } -public sealed class CommonService +public abstract class CommonService +{ + + public virtual async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.downloadExport is not implemented"); + } + + public virtual async System.Threading.Tasks.Task ListPropertiesAsync(SearchEntityType entityType, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.listProperties is not implemented"); + } + + public virtual async System.Threading.Tasks.Task RequestExportAsync(ExportRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.requestExport is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UploadFileAsync( + string directUrl, + FileUploadRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.uploadFile is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Common.getUploadParams is not implemented"); + } +} + +public sealed class CommonServiceImpl : CommonService { private readonly string _baseUrl; private readonly HttpClient _client; - internal CommonService(string baseUrl, HttpClient client) + internal CommonServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/exports/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -373,7 +527,7 @@ public async System.Threading.Tasks.Task DownloadExportAsync(int id, Can } } - public async System.Threading.Tasks.Task ListPropertiesAsync(SearchEntityType entityType, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task ListPropertiesAsync(SearchEntityType entityType, CancellationToken cancellationToken = default) { var queryParts = new List(); queryParts.Add($"entity_type={Uri.EscapeDataString(PachcaUtils.EnumToApiString(entityType))}"); @@ -392,7 +546,7 @@ public async System.Threading.Tasks.Task ListPropertiesA } } - public async System.Threading.Tasks.Task RequestExportAsync(ExportRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task RequestExportAsync(ExportRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/exports"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -410,7 +564,7 @@ public async System.Threading.Tasks.Task RequestExportAsync(ExportRequest reques } } - public async System.Threading.Tasks.Task UploadFileAsync( + public override async System.Threading.Tasks.Task UploadFileAsync( string directUrl, FileUploadRequest request, CancellationToken cancellationToken = default) @@ -440,7 +594,7 @@ public async System.Threading.Tasks.Task UploadFileAsync( } } - public async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetUploadParamsAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/uploads"; using var request = new HttpRequestMessage(HttpMethod.Post, url); @@ -458,18 +612,87 @@ public async System.Threading.Tasks.Task GetUploadParamsAsync(Canc } } -public sealed class MembersService +public abstract class MembersService +{ + + public virtual async System.Threading.Tasks.Task ListMembersAsync( + int id, + ChatMemberRoleFilter? role = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.listMembers is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListMembersAllAsync( + int id, + ChatMemberRoleFilter? role = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.listMembersAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task AddTagsAsync( + int id, + List groupTagIds, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.addTags is not implemented"); + } + + public virtual async System.Threading.Tasks.Task AddMembersAsync( + int id, + AddMembersRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.addMembers is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateMemberRoleAsync( + int id, + int userId, + ChatMemberRole role, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.updateMemberRole is not implemented"); + } + + public virtual async System.Threading.Tasks.Task RemoveTagAsync( + int id, + int tagId, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.removeTag is not implemented"); + } + + public virtual async System.Threading.Tasks.Task LeaveChatAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.leaveChat is not implemented"); + } + + public virtual async System.Threading.Tasks.Task RemoveMemberAsync( + int id, + int userId, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Members.removeMember is not implemented"); + } +} + +public sealed class MembersServiceImpl : MembersService { private readonly string _baseUrl; private readonly HttpClient _client; - internal MembersService(string baseUrl, HttpClient client) + internal MembersServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListMembersAsync( + public override async System.Threading.Tasks.Task ListMembersAsync( int id, ChatMemberRoleFilter? role = null, int? limit = null, @@ -498,7 +721,7 @@ public async System.Threading.Tasks.Task ListMembersAsync( } } - public async System.Threading.Tasks.Task> ListMembersAllAsync( + public override async System.Threading.Tasks.Task> ListMembersAllAsync( int id, ChatMemberRoleFilter? role = null, int? limit = null, @@ -515,7 +738,7 @@ public async System.Threading.Tasks.Task> ListMembersAllAsync( return items; } - public async System.Threading.Tasks.Task AddTagsAsync( + public override async System.Threading.Tasks.Task AddTagsAsync( int id, List groupTagIds, CancellationToken cancellationToken = default) @@ -537,7 +760,7 @@ public async System.Threading.Tasks.Task AddTagsAsync( } } - public async System.Threading.Tasks.Task AddMembersAsync( + public override async System.Threading.Tasks.Task AddMembersAsync( int id, AddMembersRequest request, CancellationToken cancellationToken = default) @@ -558,7 +781,7 @@ public async System.Threading.Tasks.Task AddMembersAsync( } } - public async System.Threading.Tasks.Task UpdateMemberRoleAsync( + public override async System.Threading.Tasks.Task UpdateMemberRoleAsync( int id, int userId, ChatMemberRole role, @@ -581,7 +804,7 @@ public async System.Threading.Tasks.Task UpdateMemberRoleAsync( } } - public async System.Threading.Tasks.Task RemoveTagAsync( + public override async System.Threading.Tasks.Task RemoveTagAsync( int id, int tagId, CancellationToken cancellationToken = default) @@ -601,7 +824,7 @@ public async System.Threading.Tasks.Task RemoveTagAsync( } } - public async System.Threading.Tasks.Task LeaveChatAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task LeaveChatAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/chats/{id}/leave"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -618,7 +841,7 @@ public async System.Threading.Tasks.Task LeaveChatAsync(int id, CancellationToke } } - public async System.Threading.Tasks.Task RemoveMemberAsync( + public override async System.Threading.Tasks.Task RemoveMemberAsync( int id, int userId, CancellationToken cancellationToken = default) @@ -639,18 +862,79 @@ public async System.Threading.Tasks.Task RemoveMemberAsync( } } -public sealed class GroupTagsService +public abstract class GroupTagsService +{ + + public virtual async System.Threading.Tasks.Task ListTagsAsync( + TagNamesFilter? names = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.listTags is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListTagsAllAsync( + TagNamesFilter? names = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.listTagsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetTagAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.getTag is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetTagUsersAsync( + int id, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.getTagUsers is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> GetTagUsersAllAsync( + int id, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.getTagUsersAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateTagAsync(GroupTagRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.createTag is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateTagAsync( + int id, + GroupTagRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.updateTag is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteTagAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Group tags.deleteTag is not implemented"); + } +} + +public sealed class GroupTagsServiceImpl : GroupTagsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal GroupTagsService(string baseUrl, HttpClient client) + internal GroupTagsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListTagsAsync( + public override async System.Threading.Tasks.Task ListTagsAsync( TagNamesFilter? names = null, int? limit = null, string? cursor = null, @@ -678,7 +962,7 @@ public async System.Threading.Tasks.Task ListTagsAsync( } } - public async System.Threading.Tasks.Task> ListTagsAllAsync( + public override async System.Threading.Tasks.Task> ListTagsAllAsync( TagNamesFilter? names = null, int? limit = null, CancellationToken cancellationToken = default) @@ -694,7 +978,7 @@ public async System.Threading.Tasks.Task> ListTagsAllAsync( return items; } - public async System.Threading.Tasks.Task GetTagAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetTagAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/group_tags/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -711,7 +995,7 @@ public async System.Threading.Tasks.Task GetTagAsync(int id, Cancellat } } - public async System.Threading.Tasks.Task GetTagUsersAsync( + public override async System.Threading.Tasks.Task GetTagUsersAsync( int id, int? limit = null, string? cursor = null, @@ -737,7 +1021,7 @@ public async System.Threading.Tasks.Task GetTagUsersAsync( } } - public async System.Threading.Tasks.Task> GetTagUsersAllAsync( + public override async System.Threading.Tasks.Task> GetTagUsersAllAsync( int id, int? limit = null, CancellationToken cancellationToken = default) @@ -753,7 +1037,7 @@ public async System.Threading.Tasks.Task> GetTagUsersAllAsync( return items; } - public async System.Threading.Tasks.Task CreateTagAsync(GroupTagRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateTagAsync(GroupTagRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/group_tags"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -771,7 +1055,7 @@ public async System.Threading.Tasks.Task CreateTagAsync(GroupTagReques } } - public async System.Threading.Tasks.Task UpdateTagAsync( + public override async System.Threading.Tasks.Task UpdateTagAsync( int id, GroupTagRequest request, CancellationToken cancellationToken = default) @@ -792,7 +1076,7 @@ public async System.Threading.Tasks.Task UpdateTagAsync( } } - public async System.Threading.Tasks.Task DeleteTagAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteTagAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/group_tags/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -810,18 +1094,74 @@ public async System.Threading.Tasks.Task DeleteTagAsync(int id, CancellationToke } } -public sealed class MessagesService +public abstract class MessagesService +{ + + public virtual async System.Threading.Tasks.Task ListChatMessagesAsync( + int chatId, + SortOrder? sortId = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.listChatMessages is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListChatMessagesAllAsync( + int chatId, + SortOrder? sortId = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.listChatMessagesAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetMessageAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.getMessage is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateMessageAsync(MessageCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.createMessage is not implemented"); + } + + public virtual async System.Threading.Tasks.Task PinMessageAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.pinMessage is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateMessageAsync( + int id, + MessageUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.updateMessage is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteMessageAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.deleteMessage is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UnpinMessageAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Messages.unpinMessage is not implemented"); + } +} + +public sealed class MessagesServiceImpl : MessagesService { private readonly string _baseUrl; private readonly HttpClient _client; - internal MessagesService(string baseUrl, HttpClient client) + internal MessagesServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListChatMessagesAsync( + public override async System.Threading.Tasks.Task ListChatMessagesAsync( int chatId, SortOrder? sortId = null, int? limit = null, @@ -851,7 +1191,7 @@ public async System.Threading.Tasks.Task ListChatMessa } } - public async System.Threading.Tasks.Task> ListChatMessagesAllAsync( + public override async System.Threading.Tasks.Task> ListChatMessagesAllAsync( int chatId, SortOrder? sortId = null, int? limit = null, @@ -868,7 +1208,7 @@ public async System.Threading.Tasks.Task> ListChatMessagesAllAsync return items; } - public async System.Threading.Tasks.Task GetMessageAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetMessageAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -885,7 +1225,7 @@ public async System.Threading.Tasks.Task GetMessageAsync(int id, Cancel } } - public async System.Threading.Tasks.Task CreateMessageAsync(MessageCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateMessageAsync(MessageCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -903,7 +1243,7 @@ public async System.Threading.Tasks.Task CreateMessageAsync(MessageCrea } } - public async System.Threading.Tasks.Task PinMessageAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task PinMessageAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages/{id}/pin"; using var request = new HttpRequestMessage(HttpMethod.Post, url); @@ -920,7 +1260,7 @@ public async System.Threading.Tasks.Task PinMessageAsync(int id, CancellationTok } } - public async System.Threading.Tasks.Task UpdateMessageAsync( + public override async System.Threading.Tasks.Task UpdateMessageAsync( int id, MessageUpdateRequest request, CancellationToken cancellationToken = default) @@ -941,7 +1281,7 @@ public async System.Threading.Tasks.Task UpdateMessageAsync( } } - public async System.Threading.Tasks.Task DeleteMessageAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteMessageAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -958,7 +1298,7 @@ public async System.Threading.Tasks.Task DeleteMessageAsync(int id, Cancellation } } - public async System.Threading.Tasks.Task UnpinMessageAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task UnpinMessageAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages/{id}/pin"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -976,18 +1316,30 @@ public async System.Threading.Tasks.Task UnpinMessageAsync(int id, CancellationT } } -public sealed class LinkPreviewsService +public abstract class LinkPreviewsService +{ + + public virtual async System.Threading.Tasks.Task CreateLinkPreviewsAsync( + int id, + LinkPreviewsRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Link Previews.createLinkPreviews is not implemented"); + } +} + +public sealed class LinkPreviewsServiceImpl : LinkPreviewsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal LinkPreviewsService(string baseUrl, HttpClient client) + internal LinkPreviewsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task CreateLinkPreviewsAsync( + public override async System.Threading.Tasks.Task CreateLinkPreviewsAsync( int id, LinkPreviewsRequest request, CancellationToken cancellationToken = default) @@ -1009,18 +1361,56 @@ public async System.Threading.Tasks.Task CreateLinkPreviewsAsync( } } -public sealed class ReactionsService +public abstract class ReactionsService +{ + + public virtual async System.Threading.Tasks.Task ListReactionsAsync( + int id, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Reactions.listReactions is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListReactionsAllAsync( + int id, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Reactions.listReactionsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task AddReactionAsync( + int id, + ReactionRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Reactions.addReaction is not implemented"); + } + + public virtual async System.Threading.Tasks.Task RemoveReactionAsync( + int id, + string code, + string? name = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Reactions.removeReaction is not implemented"); + } +} + +public sealed class ReactionsServiceImpl : ReactionsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ReactionsService(string baseUrl, HttpClient client) + internal ReactionsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListReactionsAsync( + public override async System.Threading.Tasks.Task ListReactionsAsync( int id, int? limit = null, string? cursor = null, @@ -1046,7 +1436,7 @@ public async System.Threading.Tasks.Task ListReactionsAsy } } - public async System.Threading.Tasks.Task> ListReactionsAllAsync( + public override async System.Threading.Tasks.Task> ListReactionsAllAsync( int id, int? limit = null, CancellationToken cancellationToken = default) @@ -1062,7 +1452,7 @@ public async System.Threading.Tasks.Task> ListReactionsAllAsync( return items; } - public async System.Threading.Tasks.Task AddReactionAsync( + public override async System.Threading.Tasks.Task AddReactionAsync( int id, ReactionRequest request, CancellationToken cancellationToken = default) @@ -1083,7 +1473,7 @@ public async System.Threading.Tasks.Task AddReactionAsync( } } - public async System.Threading.Tasks.Task RemoveReactionAsync( + public override async System.Threading.Tasks.Task RemoveReactionAsync( int id, string code, string? name = null, @@ -1109,18 +1499,31 @@ public async System.Threading.Tasks.Task RemoveReactionAsync( } } -public sealed class ReadMembersService +public abstract class ReadMembersService +{ + + public virtual async System.Threading.Tasks.Task ListReadMembersAsync( + int id, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Read members.listReadMembers is not implemented"); + } +} + +public sealed class ReadMembersServiceImpl : ReadMembersService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ReadMembersService(string baseUrl, HttpClient client) + internal ReadMembersServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListReadMembersAsync( + public override async System.Threading.Tasks.Task ListReadMembersAsync( int id, int? limit = null, string? cursor = null, @@ -1147,18 +1550,32 @@ public async System.Threading.Tasks.Task ListReadMembersAsync( } } -public sealed class ThreadsService +public abstract class ThreadsService +{ + + public virtual async System.Threading.Tasks.Task GetThreadAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Threads.getThread is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateThreadAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Threads.createThread is not implemented"); + } +} + +public sealed class ThreadsServiceImpl : ThreadsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ThreadsService(string baseUrl, HttpClient client) + internal ThreadsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task GetThreadAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetThreadAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/threads/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1175,7 +1592,7 @@ internal ThreadsService(string baseUrl, HttpClient client) } } - public async System.Threading.Tasks.Task CreateThreadAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateThreadAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/messages/{id}/thread"; using var request = new HttpRequestMessage(HttpMethod.Post, url); @@ -1193,18 +1610,47 @@ internal ThreadsService(string baseUrl, HttpClient client) } } -public sealed class ProfileService +public abstract class ProfileService +{ + + public virtual async System.Threading.Tasks.Task GetTokenInfoAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Profile.getTokenInfo is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetProfileAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Profile.getProfile is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetStatusAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Profile.getStatus is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpdateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Profile.updateStatus is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteStatusAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Profile.deleteStatus is not implemented"); + } +} + +public sealed class ProfileServiceImpl : ProfileService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ProfileService(string baseUrl, HttpClient client) + internal ProfileServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task GetTokenInfoAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetTokenInfoAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/oauth/token/info"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1221,7 +1667,7 @@ public async System.Threading.Tasks.Task GetTokenInfoAsync(Canc } } - public async System.Threading.Tasks.Task GetProfileAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetProfileAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1238,7 +1684,7 @@ public async System.Threading.Tasks.Task GetProfileAsync(CancellationToken } } - public async System.Threading.Tasks.Task GetStatusAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetStatusAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile/status"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1255,7 +1701,7 @@ public async System.Threading.Tasks.Task GetStatusAsync(CancellationToke } } - public async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpdateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpdateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile/status"; using var httpRequest = new HttpRequestMessage(HttpMethod.Put, url); @@ -1273,7 +1719,7 @@ public async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpd } } - public async System.Threading.Tasks.Task DeleteStatusAsync(CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteStatusAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile/status"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -1291,18 +1737,107 @@ public async System.Threading.Tasks.Task DeleteStatusAsync(CancellationToken can } } -public sealed class SearchService +public abstract class SearchService +{ + + public virtual async System.Threading.Tasks.Task SearchChatsAsync( + string? query = null, + int? limit = null, + string? cursor = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + bool? active = null, + ChatSubtype? chatSubtype = null, + bool? personal = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchChats is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> SearchChatsAllAsync( + string? query = null, + int? limit = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + bool? active = null, + ChatSubtype? chatSubtype = null, + bool? personal = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchChatsAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task SearchMessagesAsync( + string? query = null, + int? limit = null, + string? cursor = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + List? chatIds = null, + List? userIds = null, + bool? active = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchMessages is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> SearchMessagesAllAsync( + string? query = null, + int? limit = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + List? chatIds = null, + List? userIds = null, + bool? active = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchMessagesAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task SearchUsersAsync( + string? query = null, + int? limit = null, + string? cursor = null, + SearchSortOrder? sort = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + List? companyRoles = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchUsers is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> SearchUsersAllAsync( + string? query = null, + int? limit = null, + SearchSortOrder? sort = null, + SortOrder? order = null, + DateTimeOffset? createdFrom = null, + DateTimeOffset? createdTo = null, + List? companyRoles = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Search.searchUsersAll is not implemented"); + } +} + +public sealed class SearchServiceImpl : SearchService { private readonly string _baseUrl; private readonly HttpClient _client; - internal SearchService(string baseUrl, HttpClient client) + internal SearchServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task SearchChatsAsync( + public override async System.Threading.Tasks.Task SearchChatsAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1348,7 +1883,7 @@ public async System.Threading.Tasks.Task SearchChatsAsync( } } - public async System.Threading.Tasks.Task> SearchChatsAllAsync( + public override async System.Threading.Tasks.Task> SearchChatsAllAsync( string? query = null, int? limit = null, SortOrder? order = null, @@ -1370,7 +1905,7 @@ public async System.Threading.Tasks.Task> SearchChatsAllAsync( return items; } - public async System.Threading.Tasks.Task SearchMessagesAsync( + public override async System.Threading.Tasks.Task SearchMessagesAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1416,7 +1951,7 @@ public async System.Threading.Tasks.Task SearchMessage } } - public async System.Threading.Tasks.Task> SearchMessagesAllAsync( + public override async System.Threading.Tasks.Task> SearchMessagesAllAsync( string? query = null, int? limit = null, SortOrder? order = null, @@ -1438,7 +1973,7 @@ public async System.Threading.Tasks.Task> SearchMessagesAllAsync( return items; } - public async System.Threading.Tasks.Task SearchUsersAsync( + public override async System.Threading.Tasks.Task SearchUsersAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1481,7 +2016,7 @@ public async System.Threading.Tasks.Task SearchUsersAsync( } } - public async System.Threading.Tasks.Task> SearchUsersAllAsync( + public override async System.Threading.Tasks.Task> SearchUsersAllAsync( string? query = null, int? limit = null, SearchSortOrder? sort = null, @@ -1503,18 +2038,60 @@ public async System.Threading.Tasks.Task> SearchUsersAllAsync( } } -public sealed class TasksService +public abstract class TasksService +{ + + public virtual async System.Threading.Tasks.Task ListTasksAsync( + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.listTasks is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListTasksAllAsync( + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.listTasksAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetTaskAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.getTask is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateTaskAsync(TaskCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.createTask is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateTaskAsync( + int id, + TaskUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.updateTask is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteTaskAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Tasks.deleteTask is not implemented"); + } +} + +public sealed class TasksServiceImpl : TasksService { private readonly string _baseUrl; private readonly HttpClient _client; - internal TasksService(string baseUrl, HttpClient client) + internal TasksServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListTasksAsync( + public override async System.Threading.Tasks.Task ListTasksAsync( int? limit = null, string? cursor = null, CancellationToken cancellationToken = default) @@ -1539,7 +2116,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( } } - public async System.Threading.Tasks.Task> ListTasksAllAsync( + public override async System.Threading.Tasks.Task> ListTasksAllAsync( int? limit = null, CancellationToken cancellationToken = default) { @@ -1554,7 +2131,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( return items; } - public async System.Threading.Tasks.Task GetTaskAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetTaskAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/tasks/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1571,7 +2148,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( } } - public async System.Threading.Tasks.Task CreateTaskAsync(TaskCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateTaskAsync(TaskCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/tasks"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -1589,7 +2166,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( } } - public async System.Threading.Tasks.Task UpdateTaskAsync( + public override async System.Threading.Tasks.Task UpdateTaskAsync( int id, TaskUpdateRequest request, CancellationToken cancellationToken = default) @@ -1610,7 +2187,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( } } - public async System.Threading.Tasks.Task DeleteTaskAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteTaskAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/tasks/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -1628,18 +2205,80 @@ public async System.Threading.Tasks.Task DeleteTaskAsync(int id, CancellationTok } } -public sealed class UsersService +public abstract class UsersService +{ + + public virtual async System.Threading.Tasks.Task ListUsersAsync( + string? query = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.listUsers is not implemented"); + } + + public virtual async System.Threading.Tasks.Task> ListUsersAllAsync( + string? query = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.listUsersAll is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetUserAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.getUser is not implemented"); + } + + public virtual async System.Threading.Tasks.Task GetUserStatusAsync(int userId, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.getUserStatus is not implemented"); + } + + public virtual async System.Threading.Tasks.Task CreateUserAsync(UserCreateRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.createUser is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateUserAsync( + int id, + UserUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.updateUser is not implemented"); + } + + public virtual async System.Threading.Tasks.Task UpdateUserStatusAsync( + int userId, + StatusUpdateRequest request, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.updateUserStatus is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteUserAsync(int id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.deleteUser is not implemented"); + } + + public virtual async System.Threading.Tasks.Task DeleteUserStatusAsync(int userId, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Users.deleteUserStatus is not implemented"); + } +} + +public sealed class UsersServiceImpl : UsersService { private readonly string _baseUrl; private readonly HttpClient _client; - internal UsersService(string baseUrl, HttpClient client) + internal UsersServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task ListUsersAsync( + public override async System.Threading.Tasks.Task ListUsersAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1667,7 +2306,7 @@ public async System.Threading.Tasks.Task ListUsersAsync( } } - public async System.Threading.Tasks.Task> ListUsersAllAsync( + public override async System.Threading.Tasks.Task> ListUsersAllAsync( string? query = null, int? limit = null, CancellationToken cancellationToken = default) @@ -1683,7 +2322,7 @@ public async System.Threading.Tasks.Task> ListUsersAllAsync( return items; } - public async System.Threading.Tasks.Task GetUserAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetUserAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users/{id}"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1700,7 +2339,7 @@ public async System.Threading.Tasks.Task GetUserAsync(int id, Cancellation } } - public async System.Threading.Tasks.Task GetUserStatusAsync(int userId, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task GetUserStatusAsync(int userId, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users/{userId}/status"; using var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -1717,7 +2356,7 @@ public async System.Threading.Tasks.Task GetUserStatusAsync(int userId, } } - public async System.Threading.Tasks.Task CreateUserAsync(UserCreateRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task CreateUserAsync(UserCreateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -1735,7 +2374,7 @@ public async System.Threading.Tasks.Task CreateUserAsync(UserCreateRequest } } - public async System.Threading.Tasks.Task UpdateUserAsync( + public override async System.Threading.Tasks.Task UpdateUserAsync( int id, UserUpdateRequest request, CancellationToken cancellationToken = default) @@ -1756,7 +2395,7 @@ public async System.Threading.Tasks.Task UpdateUserAsync( } } - public async System.Threading.Tasks.Task UpdateUserStatusAsync( + public override async System.Threading.Tasks.Task UpdateUserStatusAsync( int userId, StatusUpdateRequest request, CancellationToken cancellationToken = default) @@ -1777,7 +2416,7 @@ public async System.Threading.Tasks.Task UpdateUserStatusAsync( } } - public async System.Threading.Tasks.Task DeleteUserAsync(int id, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteUserAsync(int id, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users/{id}"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -1794,7 +2433,7 @@ public async System.Threading.Tasks.Task DeleteUserAsync(int id, CancellationTok } } - public async System.Threading.Tasks.Task DeleteUserStatusAsync(int userId, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task DeleteUserStatusAsync(int userId, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users/{userId}/status"; using var request = new HttpRequestMessage(HttpMethod.Delete, url); @@ -1812,18 +2451,27 @@ public async System.Threading.Tasks.Task DeleteUserStatusAsync(int userId, Cance } } -public sealed class ViewsService +public abstract class ViewsService +{ + + public virtual async System.Threading.Tasks.Task OpenViewAsync(OpenViewRequest request, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("Views.openView is not implemented"); + } +} + +public sealed class ViewsServiceImpl : ViewsService { private readonly string _baseUrl; private readonly HttpClient _client; - internal ViewsService(string baseUrl, HttpClient client) + internal ViewsServiceImpl(string baseUrl, HttpClient client) { _baseUrl = baseUrl; _client = client; } - public async System.Threading.Tasks.Task OpenViewAsync(OpenViewRequest request, CancellationToken cancellationToken = default) + public override async System.Threading.Tasks.Task OpenViewAsync(OpenViewRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/views/open"; using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); @@ -1846,6 +2494,26 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; + public sealed class Services + { + public BotsService? Bots { get; init; } + public ChatsService? Chats { get; init; } + public CommonService? Common { get; init; } + public GroupTagsService? GroupTags { get; init; } + public LinkPreviewsService? LinkPreviews { get; init; } + public MembersService? Members { get; init; } + public MessagesService? Messages { get; init; } + public ProfileService? Profile { get; init; } + public ReactionsService? Reactions { get; init; } + public ReadMembersService? ReadMembers { get; init; } + public SearchService? Search { get; init; } + public SecurityService? Security { get; init; } + public TasksService? Tasks { get; init; } + public ThreadsService? Threads { get; init; } + public UsersService? Users { get; init; } + public ViewsService? Views { get; init; } + } + public BotsService Bots { get; } public ChatsService Chats { get; } public CommonService Common { get; } @@ -1863,8 +2531,9 @@ public sealed class PachcaClient : IDisposable public UsersService Users { get; } public ViewsService Views { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) { + services ??= new Services(); var handler = new SocketsHttpHandler { AllowAutoRedirect = false, @@ -1873,22 +2542,22 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Bots = new BotsService(baseUrl, _client); - Chats = new ChatsService(baseUrl, _client); - Common = new CommonService(baseUrl, _client); - GroupTags = new GroupTagsService(baseUrl, _client); - LinkPreviews = new LinkPreviewsService(baseUrl, _client); - Members = new MembersService(baseUrl, _client); - Messages = new MessagesService(baseUrl, _client); - Profile = new ProfileService(baseUrl, _client); - Reactions = new ReactionsService(baseUrl, _client); - ReadMembers = new ReadMembersService(baseUrl, _client); - Search = new SearchService(baseUrl, _client); - Security = new SecurityService(baseUrl, _client); - Tasks = new TasksService(baseUrl, _client); - Threads = new ThreadsService(baseUrl, _client); - Users = new UsersService(baseUrl, _client); - Views = new ViewsService(baseUrl, _client); + Bots = services.Bots ?? new BotsServiceImpl(baseUrl, _client); + Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); + Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); + GroupTags = services.GroupTags ?? new GroupTagsServiceImpl(baseUrl, _client); + LinkPreviews = services.LinkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); + Members = services.Members ?? new MembersServiceImpl(baseUrl, _client); + Messages = services.Messages ?? new MessagesServiceImpl(baseUrl, _client); + Profile = services.Profile ?? new ProfileServiceImpl(baseUrl, _client); + Reactions = services.Reactions ?? new ReactionsServiceImpl(baseUrl, _client); + ReadMembers = services.ReadMembers ?? new ReadMembersServiceImpl(baseUrl, _client); + Search = services.Search ?? new SearchServiceImpl(baseUrl, _client); + Security = services.Security ?? new SecurityServiceImpl(baseUrl, _client); + Tasks = services.Tasks ?? new TasksServiceImpl(baseUrl, _client); + Threads = services.Threads ?? new ThreadsServiceImpl(baseUrl, _client); + Users = services.Users ?? new UsersServiceImpl(baseUrl, _client); + Views = services.Views ?? new ViewsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/sdk/go/generated/client.go b/sdk/go/generated/client.go index 20316165..fdca0df4 100644 --- a/sdk/go/generated/client.go +++ b/sdk/go/generated/client.go @@ -50,12 +50,27 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) } } -type SecurityService struct { +type SecurityService interface { + GetAuditEvents(ctx context.Context, params *GetAuditEventsParams) (*GetAuditEventsResponse, error) + GetAuditEventsAll(ctx context.Context, params *GetAuditEventsParams) ([]AuditEvent, error) +} + +type SecurityServiceStub struct{} + +func (s *SecurityServiceStub) GetAuditEvents(ctx context.Context, params *GetAuditEventsParams) (*GetAuditEventsResponse, error) { + return nil, fmt.Errorf("Security.getAuditEvents is not implemented") +} + +func (s *SecurityServiceStub) GetAuditEventsAll(ctx context.Context, params *GetAuditEventsParams) ([]AuditEvent, error) { + return nil, fmt.Errorf("Security.getAuditEventsAll is not implemented") +} + +type SecurityServiceImpl struct { baseURL string client *http.Client } -func (s *SecurityService) GetAuditEvents(ctx context.Context, params *GetAuditEventsParams) (*GetAuditEventsResponse, error) { +func (s *SecurityServiceImpl) GetAuditEvents(ctx context.Context, params *GetAuditEventsParams) (*GetAuditEventsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/audit_events", s.baseURL)) if err != nil { return nil, err @@ -116,7 +131,7 @@ func (s *SecurityService) GetAuditEvents(ctx context.Context, params *GetAuditEv } } -func (s *SecurityService) GetAuditEventsAll(ctx context.Context, params *GetAuditEventsParams) ([]AuditEvent, error) { +func (s *SecurityServiceImpl) GetAuditEventsAll(ctx context.Context, params *GetAuditEventsParams) ([]AuditEvent, error) { if params == nil { params = &GetAuditEventsParams{} } @@ -136,12 +151,37 @@ func (s *SecurityService) GetAuditEventsAll(ctx context.Context, params *GetAudi } } -type BotsService struct { +type BotsService interface { + GetWebhookEvents(ctx context.Context, params *GetWebhookEventsParams) (*GetWebhookEventsResponse, error) + GetWebhookEventsAll(ctx context.Context, params *GetWebhookEventsParams) ([]WebhookEvent, error) + UpdateBot(ctx context.Context, id int32, request BotUpdateRequest) (*BotResponse, error) + DeleteWebhookEvent(ctx context.Context, id string) error +} + +type BotsServiceStub struct{} + +func (s *BotsServiceStub) GetWebhookEvents(ctx context.Context, params *GetWebhookEventsParams) (*GetWebhookEventsResponse, error) { + return nil, fmt.Errorf("Bots.getWebhookEvents is not implemented") +} + +func (s *BotsServiceStub) GetWebhookEventsAll(ctx context.Context, params *GetWebhookEventsParams) ([]WebhookEvent, error) { + return nil, fmt.Errorf("Bots.getWebhookEventsAll is not implemented") +} + +func (s *BotsServiceStub) UpdateBot(ctx context.Context, id int32, request BotUpdateRequest) (*BotResponse, error) { + return nil, fmt.Errorf("Bots.updateBot is not implemented") +} + +func (s *BotsServiceStub) DeleteWebhookEvent(ctx context.Context, id string) error { + return fmt.Errorf("Bots.deleteWebhookEvent is not implemented") +} + +type BotsServiceImpl struct { baseURL string client *http.Client } -func (s *BotsService) GetWebhookEvents(ctx context.Context, params *GetWebhookEventsParams) (*GetWebhookEventsResponse, error) { +func (s *BotsServiceImpl) GetWebhookEvents(ctx context.Context, params *GetWebhookEventsParams) (*GetWebhookEventsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/webhooks/events", s.baseURL)) if err != nil { return nil, err @@ -181,7 +221,7 @@ func (s *BotsService) GetWebhookEvents(ctx context.Context, params *GetWebhookEv } } -func (s *BotsService) GetWebhookEventsAll(ctx context.Context, params *GetWebhookEventsParams) ([]WebhookEvent, error) { +func (s *BotsServiceImpl) GetWebhookEventsAll(ctx context.Context, params *GetWebhookEventsParams) ([]WebhookEvent, error) { if params == nil { params = &GetWebhookEventsParams{} } @@ -201,7 +241,7 @@ func (s *BotsService) GetWebhookEventsAll(ctx context.Context, params *GetWebhoo } } -func (s *BotsService) UpdateBot(ctx context.Context, id int32, request BotUpdateRequest) (*BotResponse, error) { +func (s *BotsServiceImpl) UpdateBot(ctx context.Context, id int32, request BotUpdateRequest) (*BotResponse, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -236,7 +276,7 @@ func (s *BotsService) UpdateBot(ctx context.Context, id int32, request BotUpdate } } -func (s *BotsService) DeleteWebhookEvent(ctx context.Context, id string) error { +func (s *BotsServiceImpl) DeleteWebhookEvent(ctx context.Context, id string) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/webhooks/events/%v", s.baseURL, id), nil) if err != nil { return err @@ -260,12 +300,52 @@ func (s *BotsService) DeleteWebhookEvent(ctx context.Context, id string) error { } } -type ChatsService struct { +type ChatsService interface { + ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) + ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) + GetChat(ctx context.Context, id int32) (*Chat, error) + CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) + UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) + ArchiveChat(ctx context.Context, id int32) error + UnarchiveChat(ctx context.Context, id int32) error +} + +type ChatsServiceStub struct{} + +func (s *ChatsServiceStub) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { + return nil, fmt.Errorf("Chats.listChats is not implemented") +} + +func (s *ChatsServiceStub) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { + return nil, fmt.Errorf("Chats.listChatsAll is not implemented") +} + +func (s *ChatsServiceStub) GetChat(ctx context.Context, id int32) (*Chat, error) { + return nil, fmt.Errorf("Chats.getChat is not implemented") +} + +func (s *ChatsServiceStub) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { + return nil, fmt.Errorf("Chats.createChat is not implemented") +} + +func (s *ChatsServiceStub) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { + return nil, fmt.Errorf("Chats.updateChat is not implemented") +} + +func (s *ChatsServiceStub) ArchiveChat(ctx context.Context, id int32) error { + return fmt.Errorf("Chats.archiveChat is not implemented") +} + +func (s *ChatsServiceStub) UnarchiveChat(ctx context.Context, id int32) error { + return fmt.Errorf("Chats.unarchiveChat is not implemented") +} + +type ChatsServiceImpl struct { baseURL string client *http.Client } -func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { +func (s *ChatsServiceImpl) ListChats(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/chats", s.baseURL)) if err != nil { return nil, err @@ -320,7 +400,7 @@ func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) ( } } -func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { +func (s *ChatsServiceImpl) ListChatsAll(ctx context.Context, params *ListChatsParams) ([]Chat, error) { if params == nil { params = &ListChatsParams{} } @@ -340,7 +420,7 @@ func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams } } -func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { +func (s *ChatsServiceImpl) GetChat(ctx context.Context, id int32) (*Chat, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/chats/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -370,7 +450,7 @@ func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { } } -func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { +func (s *ChatsServiceImpl) CreateChat(ctx context.Context, request ChatCreateRequest) (*Chat, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -405,7 +485,7 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest } } -func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { +func (s *ChatsServiceImpl) UpdateChat(ctx context.Context, id int32, request ChatUpdateRequest) (*Chat, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -440,7 +520,7 @@ func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpd } } -func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { +func (s *ChatsServiceImpl) ArchiveChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/chats/%v/archive", s.baseURL, id), nil) if err != nil { return err @@ -464,7 +544,7 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { } } -func (s *ChatsService) UnarchiveChat(ctx context.Context, id int32) error { +func (s *ChatsServiceImpl) UnarchiveChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/chats/%v/unarchive", s.baseURL, id), nil) if err != nil { return err @@ -488,12 +568,42 @@ func (s *ChatsService) UnarchiveChat(ctx context.Context, id int32) error { } } -type CommonService struct { +type CommonService interface { + DownloadExport(ctx context.Context, id int32) (string, error) + ListProperties(ctx context.Context, params ListPropertiesParams) (*ListPropertiesResponse, error) + RequestExport(ctx context.Context, request ExportRequest) error + UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error + GetUploadParams(ctx context.Context) (*UploadParams, error) +} + +type CommonServiceStub struct{} + +func (s *CommonServiceStub) DownloadExport(ctx context.Context, id int32) (string, error) { + return "", fmt.Errorf("Common.downloadExport is not implemented") +} + +func (s *CommonServiceStub) ListProperties(ctx context.Context, params ListPropertiesParams) (*ListPropertiesResponse, error) { + return nil, fmt.Errorf("Common.listProperties is not implemented") +} + +func (s *CommonServiceStub) RequestExport(ctx context.Context, request ExportRequest) error { + return fmt.Errorf("Common.requestExport is not implemented") +} + +func (s *CommonServiceStub) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { + return fmt.Errorf("Common.uploadFile is not implemented") +} + +func (s *CommonServiceStub) GetUploadParams(ctx context.Context) (*UploadParams, error) { + return nil, fmt.Errorf("Common.getUploadParams is not implemented") +} + +type CommonServiceImpl struct { baseURL string client *http.Client } -func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, error) { +func (s *CommonServiceImpl) DownloadExport(ctx context.Context, id int32) (string, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/chats/exports/%v", s.baseURL, id), nil) if err != nil { return "", err @@ -521,7 +631,7 @@ func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, e } } -func (s *CommonService) ListProperties(ctx context.Context, params ListPropertiesParams) (*ListPropertiesResponse, error) { +func (s *CommonServiceImpl) ListProperties(ctx context.Context, params ListPropertiesParams) (*ListPropertiesResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/custom_properties", s.baseURL)) if err != nil { return nil, err @@ -556,7 +666,7 @@ func (s *CommonService) ListProperties(ctx context.Context, params ListPropertie } } -func (s *CommonService) RequestExport(ctx context.Context, request ExportRequest) error { +func (s *CommonServiceImpl) RequestExport(ctx context.Context, request ExportRequest) error { body, err := json.Marshal(request) if err != nil { return err @@ -585,7 +695,7 @@ func (s *CommonService) RequestExport(ctx context.Context, request ExportRequest } } -func (s *CommonService) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { +func (s *CommonServiceImpl) UploadFile(ctx context.Context, directUrl string, request FileUploadRequest) error { pr, pw := io.Pipe() writer := multipart.NewWriter(pw) go func() { @@ -629,7 +739,7 @@ func (s *CommonService) UploadFile(ctx context.Context, directUrl string, reques } } -func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, error) { +func (s *CommonServiceImpl) GetUploadParams(ctx context.Context) (*UploadParams, error) { req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/uploads", s.baseURL), nil) if err != nil { return nil, err @@ -657,12 +767,57 @@ func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, err } } -type MembersService struct { +type MembersService interface { + ListMembers(ctx context.Context, id int32, params *ListMembersParams) (*ListMembersResponse, error) + ListMembersAll(ctx context.Context, id int32, params *ListMembersParams) ([]User, error) + AddTags(ctx context.Context, id int32, groupTagIds []int32) error + AddMembers(ctx context.Context, id int32, request AddMembersRequest) error + UpdateMemberRole(ctx context.Context, id int32, userId int32, role ChatMemberRole) error + RemoveTag(ctx context.Context, id int32, tagId int32) error + LeaveChat(ctx context.Context, id int32) error + RemoveMember(ctx context.Context, id int32, userId int32) error +} + +type MembersServiceStub struct{} + +func (s *MembersServiceStub) ListMembers(ctx context.Context, id int32, params *ListMembersParams) (*ListMembersResponse, error) { + return nil, fmt.Errorf("Members.listMembers is not implemented") +} + +func (s *MembersServiceStub) ListMembersAll(ctx context.Context, id int32, params *ListMembersParams) ([]User, error) { + return nil, fmt.Errorf("Members.listMembersAll is not implemented") +} + +func (s *MembersServiceStub) AddTags(ctx context.Context, id int32, groupTagIds []int32) error { + return fmt.Errorf("Members.addTags is not implemented") +} + +func (s *MembersServiceStub) AddMembers(ctx context.Context, id int32, request AddMembersRequest) error { + return fmt.Errorf("Members.addMembers is not implemented") +} + +func (s *MembersServiceStub) UpdateMemberRole(ctx context.Context, id int32, userId int32, role ChatMemberRole) error { + return fmt.Errorf("Members.updateMemberRole is not implemented") +} + +func (s *MembersServiceStub) RemoveTag(ctx context.Context, id int32, tagId int32) error { + return fmt.Errorf("Members.removeTag is not implemented") +} + +func (s *MembersServiceStub) LeaveChat(ctx context.Context, id int32) error { + return fmt.Errorf("Members.leaveChat is not implemented") +} + +func (s *MembersServiceStub) RemoveMember(ctx context.Context, id int32, userId int32) error { + return fmt.Errorf("Members.removeMember is not implemented") +} + +type MembersServiceImpl struct { baseURL string client *http.Client } -func (s *MembersService) ListMembers(ctx context.Context, id int32, params *ListMembersParams) (*ListMembersResponse, error) { +func (s *MembersServiceImpl) ListMembers(ctx context.Context, id int32, params *ListMembersParams) (*ListMembersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/chats/%v/members", s.baseURL, id)) if err != nil { return nil, err @@ -705,7 +860,7 @@ func (s *MembersService) ListMembers(ctx context.Context, id int32, params *List } } -func (s *MembersService) ListMembersAll(ctx context.Context, id int32, params *ListMembersParams) ([]User, error) { +func (s *MembersServiceImpl) ListMembersAll(ctx context.Context, id int32, params *ListMembersParams) ([]User, error) { if params == nil { params = &ListMembersParams{} } @@ -725,7 +880,7 @@ func (s *MembersService) ListMembersAll(ctx context.Context, id int32, params *L } } -func (s *MembersService) AddTags(ctx context.Context, id int32, groupTagIds []int32) error { +func (s *MembersServiceImpl) AddTags(ctx context.Context, id int32, groupTagIds []int32) error { body, err := json.Marshal(map[string]any{"group_tag_ids": groupTagIds}) if err != nil { return err @@ -754,7 +909,7 @@ func (s *MembersService) AddTags(ctx context.Context, id int32, groupTagIds []in } } -func (s *MembersService) AddMembers(ctx context.Context, id int32, request AddMembersRequest) error { +func (s *MembersServiceImpl) AddMembers(ctx context.Context, id int32, request AddMembersRequest) error { body, err := json.Marshal(request) if err != nil { return err @@ -783,7 +938,7 @@ func (s *MembersService) AddMembers(ctx context.Context, id int32, request AddMe } } -func (s *MembersService) UpdateMemberRole(ctx context.Context, id int32, userId int32, role ChatMemberRole) error { +func (s *MembersServiceImpl) UpdateMemberRole(ctx context.Context, id int32, userId int32, role ChatMemberRole) error { body, err := json.Marshal(map[string]any{"role": role}) if err != nil { return err @@ -812,7 +967,7 @@ func (s *MembersService) UpdateMemberRole(ctx context.Context, id int32, userId } } -func (s *MembersService) RemoveTag(ctx context.Context, id int32, tagId int32) error { +func (s *MembersServiceImpl) RemoveTag(ctx context.Context, id int32, tagId int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/chats/%v/group_tags/%v", s.baseURL, id, tagId), nil) if err != nil { return err @@ -836,7 +991,7 @@ func (s *MembersService) RemoveTag(ctx context.Context, id int32, tagId int32) e } } -func (s *MembersService) LeaveChat(ctx context.Context, id int32) error { +func (s *MembersServiceImpl) LeaveChat(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/chats/%v/leave", s.baseURL, id), nil) if err != nil { return err @@ -860,7 +1015,7 @@ func (s *MembersService) LeaveChat(ctx context.Context, id int32) error { } } -func (s *MembersService) RemoveMember(ctx context.Context, id int32, userId int32) error { +func (s *MembersServiceImpl) RemoveMember(ctx context.Context, id int32, userId int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/chats/%v/members/%v", s.baseURL, id, userId), nil) if err != nil { return err @@ -884,12 +1039,57 @@ func (s *MembersService) RemoveMember(ctx context.Context, id int32, userId int3 } } -type GroupTagsService struct { +type GroupTagsService interface { + ListTags(ctx context.Context, params *ListTagsParams) (*ListTagsResponse, error) + ListTagsAll(ctx context.Context, params *ListTagsParams) ([]GroupTag, error) + GetTag(ctx context.Context, id int32) (*GroupTag, error) + GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*ListMembersResponse, error) + GetTagUsersAll(ctx context.Context, id int32, params *GetTagUsersParams) ([]User, error) + CreateTag(ctx context.Context, request GroupTagRequest) (*GroupTag, error) + UpdateTag(ctx context.Context, id int32, request GroupTagRequest) (*GroupTag, error) + DeleteTag(ctx context.Context, id int32) error +} + +type GroupTagsServiceStub struct{} + +func (s *GroupTagsServiceStub) ListTags(ctx context.Context, params *ListTagsParams) (*ListTagsResponse, error) { + return nil, fmt.Errorf("Group tags.listTags is not implemented") +} + +func (s *GroupTagsServiceStub) ListTagsAll(ctx context.Context, params *ListTagsParams) ([]GroupTag, error) { + return nil, fmt.Errorf("Group tags.listTagsAll is not implemented") +} + +func (s *GroupTagsServiceStub) GetTag(ctx context.Context, id int32) (*GroupTag, error) { + return nil, fmt.Errorf("Group tags.getTag is not implemented") +} + +func (s *GroupTagsServiceStub) GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*ListMembersResponse, error) { + return nil, fmt.Errorf("Group tags.getTagUsers is not implemented") +} + +func (s *GroupTagsServiceStub) GetTagUsersAll(ctx context.Context, id int32, params *GetTagUsersParams) ([]User, error) { + return nil, fmt.Errorf("Group tags.getTagUsersAll is not implemented") +} + +func (s *GroupTagsServiceStub) CreateTag(ctx context.Context, request GroupTagRequest) (*GroupTag, error) { + return nil, fmt.Errorf("Group tags.createTag is not implemented") +} + +func (s *GroupTagsServiceStub) UpdateTag(ctx context.Context, id int32, request GroupTagRequest) (*GroupTag, error) { + return nil, fmt.Errorf("Group tags.updateTag is not implemented") +} + +func (s *GroupTagsServiceStub) DeleteTag(ctx context.Context, id int32) error { + return fmt.Errorf("Group tags.deleteTag is not implemented") +} + +type GroupTagsServiceImpl struct { baseURL string client *http.Client } -func (s *GroupTagsService) ListTags(ctx context.Context, params *ListTagsParams) (*ListTagsResponse, error) { +func (s *GroupTagsServiceImpl) ListTags(ctx context.Context, params *ListTagsParams) (*ListTagsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/group_tags", s.baseURL)) if err != nil { return nil, err @@ -932,7 +1132,7 @@ func (s *GroupTagsService) ListTags(ctx context.Context, params *ListTagsParams) } } -func (s *GroupTagsService) ListTagsAll(ctx context.Context, params *ListTagsParams) ([]GroupTag, error) { +func (s *GroupTagsServiceImpl) ListTagsAll(ctx context.Context, params *ListTagsParams) ([]GroupTag, error) { if params == nil { params = &ListTagsParams{} } @@ -952,7 +1152,7 @@ func (s *GroupTagsService) ListTagsAll(ctx context.Context, params *ListTagsPara } } -func (s *GroupTagsService) GetTag(ctx context.Context, id int32) (*GroupTag, error) { +func (s *GroupTagsServiceImpl) GetTag(ctx context.Context, id int32) (*GroupTag, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/group_tags/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -982,7 +1182,7 @@ func (s *GroupTagsService) GetTag(ctx context.Context, id int32) (*GroupTag, err } } -func (s *GroupTagsService) GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*ListMembersResponse, error) { +func (s *GroupTagsServiceImpl) GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*ListMembersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/group_tags/%v/users", s.baseURL, id)) if err != nil { return nil, err @@ -1022,7 +1222,7 @@ func (s *GroupTagsService) GetTagUsers(ctx context.Context, id int32, params *Ge } } -func (s *GroupTagsService) GetTagUsersAll(ctx context.Context, id int32, params *GetTagUsersParams) ([]User, error) { +func (s *GroupTagsServiceImpl) GetTagUsersAll(ctx context.Context, id int32, params *GetTagUsersParams) ([]User, error) { if params == nil { params = &GetTagUsersParams{} } @@ -1042,7 +1242,7 @@ func (s *GroupTagsService) GetTagUsersAll(ctx context.Context, id int32, params } } -func (s *GroupTagsService) CreateTag(ctx context.Context, request GroupTagRequest) (*GroupTag, error) { +func (s *GroupTagsServiceImpl) CreateTag(ctx context.Context, request GroupTagRequest) (*GroupTag, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1077,7 +1277,7 @@ func (s *GroupTagsService) CreateTag(ctx context.Context, request GroupTagReques } } -func (s *GroupTagsService) UpdateTag(ctx context.Context, id int32, request GroupTagRequest) (*GroupTag, error) { +func (s *GroupTagsServiceImpl) UpdateTag(ctx context.Context, id int32, request GroupTagRequest) (*GroupTag, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1112,7 +1312,7 @@ func (s *GroupTagsService) UpdateTag(ctx context.Context, id int32, request Grou } } -func (s *GroupTagsService) DeleteTag(ctx context.Context, id int32) error { +func (s *GroupTagsServiceImpl) DeleteTag(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/group_tags/%v", s.baseURL, id), nil) if err != nil { return err @@ -1136,12 +1336,57 @@ func (s *GroupTagsService) DeleteTag(ctx context.Context, id int32) error { } } -type MessagesService struct { +type MessagesService interface { + ListChatMessages(ctx context.Context, params ListChatMessagesParams) (*ListChatMessagesResponse, error) + ListChatMessagesAll(ctx context.Context, params *ListChatMessagesParams) ([]Message, error) + GetMessage(ctx context.Context, id int32) (*Message, error) + CreateMessage(ctx context.Context, request MessageCreateRequest) (*Message, error) + PinMessage(ctx context.Context, id int32) error + UpdateMessage(ctx context.Context, id int32, request MessageUpdateRequest) (*Message, error) + DeleteMessage(ctx context.Context, id int32) error + UnpinMessage(ctx context.Context, id int32) error +} + +type MessagesServiceStub struct{} + +func (s *MessagesServiceStub) ListChatMessages(ctx context.Context, params ListChatMessagesParams) (*ListChatMessagesResponse, error) { + return nil, fmt.Errorf("Messages.listChatMessages is not implemented") +} + +func (s *MessagesServiceStub) ListChatMessagesAll(ctx context.Context, params *ListChatMessagesParams) ([]Message, error) { + return nil, fmt.Errorf("Messages.listChatMessagesAll is not implemented") +} + +func (s *MessagesServiceStub) GetMessage(ctx context.Context, id int32) (*Message, error) { + return nil, fmt.Errorf("Messages.getMessage is not implemented") +} + +func (s *MessagesServiceStub) CreateMessage(ctx context.Context, request MessageCreateRequest) (*Message, error) { + return nil, fmt.Errorf("Messages.createMessage is not implemented") +} + +func (s *MessagesServiceStub) PinMessage(ctx context.Context, id int32) error { + return fmt.Errorf("Messages.pinMessage is not implemented") +} + +func (s *MessagesServiceStub) UpdateMessage(ctx context.Context, id int32, request MessageUpdateRequest) (*Message, error) { + return nil, fmt.Errorf("Messages.updateMessage is not implemented") +} + +func (s *MessagesServiceStub) DeleteMessage(ctx context.Context, id int32) error { + return fmt.Errorf("Messages.deleteMessage is not implemented") +} + +func (s *MessagesServiceStub) UnpinMessage(ctx context.Context, id int32) error { + return fmt.Errorf("Messages.unpinMessage is not implemented") +} + +type MessagesServiceImpl struct { baseURL string client *http.Client } -func (s *MessagesService) ListChatMessages(ctx context.Context, params ListChatMessagesParams) (*ListChatMessagesResponse, error) { +func (s *MessagesServiceImpl) ListChatMessages(ctx context.Context, params ListChatMessagesParams) (*ListChatMessagesResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/messages", s.baseURL)) if err != nil { return nil, err @@ -1185,7 +1430,7 @@ func (s *MessagesService) ListChatMessages(ctx context.Context, params ListChatM } } -func (s *MessagesService) ListChatMessagesAll(ctx context.Context, params *ListChatMessagesParams) ([]Message, error) { +func (s *MessagesServiceImpl) ListChatMessagesAll(ctx context.Context, params *ListChatMessagesParams) ([]Message, error) { if params == nil { params = &ListChatMessagesParams{} } @@ -1205,7 +1450,7 @@ func (s *MessagesService) ListChatMessagesAll(ctx context.Context, params *ListC } } -func (s *MessagesService) GetMessage(ctx context.Context, id int32) (*Message, error) { +func (s *MessagesServiceImpl) GetMessage(ctx context.Context, id int32) (*Message, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/messages/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -1235,7 +1480,7 @@ func (s *MessagesService) GetMessage(ctx context.Context, id int32) (*Message, e } } -func (s *MessagesService) CreateMessage(ctx context.Context, request MessageCreateRequest) (*Message, error) { +func (s *MessagesServiceImpl) CreateMessage(ctx context.Context, request MessageCreateRequest) (*Message, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1270,7 +1515,7 @@ func (s *MessagesService) CreateMessage(ctx context.Context, request MessageCrea } } -func (s *MessagesService) PinMessage(ctx context.Context, id int32) error { +func (s *MessagesServiceImpl) PinMessage(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/messages/%v/pin", s.baseURL, id), nil) if err != nil { return err @@ -1294,7 +1539,7 @@ func (s *MessagesService) PinMessage(ctx context.Context, id int32) error { } } -func (s *MessagesService) UpdateMessage(ctx context.Context, id int32, request MessageUpdateRequest) (*Message, error) { +func (s *MessagesServiceImpl) UpdateMessage(ctx context.Context, id int32, request MessageUpdateRequest) (*Message, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1329,7 +1574,7 @@ func (s *MessagesService) UpdateMessage(ctx context.Context, id int32, request M } } -func (s *MessagesService) DeleteMessage(ctx context.Context, id int32) error { +func (s *MessagesServiceImpl) DeleteMessage(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/messages/%v", s.baseURL, id), nil) if err != nil { return err @@ -1353,7 +1598,7 @@ func (s *MessagesService) DeleteMessage(ctx context.Context, id int32) error { } } -func (s *MessagesService) UnpinMessage(ctx context.Context, id int32) error { +func (s *MessagesServiceImpl) UnpinMessage(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/messages/%v/pin", s.baseURL, id), nil) if err != nil { return err @@ -1377,12 +1622,22 @@ func (s *MessagesService) UnpinMessage(ctx context.Context, id int32) error { } } -type LinkPreviewsService struct { +type LinkPreviewsService interface { + CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error +} + +type LinkPreviewsServiceStub struct{} + +func (s *LinkPreviewsServiceStub) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { + return fmt.Errorf("Link Previews.createLinkPreviews is not implemented") +} + +type LinkPreviewsServiceImpl struct { baseURL string client *http.Client } -func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { +func (s *LinkPreviewsServiceImpl) CreateLinkPreviews(ctx context.Context, id int32, request LinkPreviewsRequest) error { body, err := json.Marshal(request) if err != nil { return err @@ -1411,12 +1666,37 @@ func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, } } -type ReactionsService struct { +type ReactionsService interface { + ListReactions(ctx context.Context, id int32, params *ListReactionsParams) (*ListReactionsResponse, error) + ListReactionsAll(ctx context.Context, id int32, params *ListReactionsParams) ([]Reaction, error) + AddReaction(ctx context.Context, id int32, request ReactionRequest) (*Reaction, error) + RemoveReaction(ctx context.Context, id int32, params RemoveReactionParams) error +} + +type ReactionsServiceStub struct{} + +func (s *ReactionsServiceStub) ListReactions(ctx context.Context, id int32, params *ListReactionsParams) (*ListReactionsResponse, error) { + return nil, fmt.Errorf("Reactions.listReactions is not implemented") +} + +func (s *ReactionsServiceStub) ListReactionsAll(ctx context.Context, id int32, params *ListReactionsParams) ([]Reaction, error) { + return nil, fmt.Errorf("Reactions.listReactionsAll is not implemented") +} + +func (s *ReactionsServiceStub) AddReaction(ctx context.Context, id int32, request ReactionRequest) (*Reaction, error) { + return nil, fmt.Errorf("Reactions.addReaction is not implemented") +} + +func (s *ReactionsServiceStub) RemoveReaction(ctx context.Context, id int32, params RemoveReactionParams) error { + return fmt.Errorf("Reactions.removeReaction is not implemented") +} + +type ReactionsServiceImpl struct { baseURL string client *http.Client } -func (s *ReactionsService) ListReactions(ctx context.Context, id int32, params *ListReactionsParams) (*ListReactionsResponse, error) { +func (s *ReactionsServiceImpl) ListReactions(ctx context.Context, id int32, params *ListReactionsParams) (*ListReactionsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/messages/%v/reactions", s.baseURL, id)) if err != nil { return nil, err @@ -1456,7 +1736,7 @@ func (s *ReactionsService) ListReactions(ctx context.Context, id int32, params * } } -func (s *ReactionsService) ListReactionsAll(ctx context.Context, id int32, params *ListReactionsParams) ([]Reaction, error) { +func (s *ReactionsServiceImpl) ListReactionsAll(ctx context.Context, id int32, params *ListReactionsParams) ([]Reaction, error) { if params == nil { params = &ListReactionsParams{} } @@ -1476,7 +1756,7 @@ func (s *ReactionsService) ListReactionsAll(ctx context.Context, id int32, param } } -func (s *ReactionsService) AddReaction(ctx context.Context, id int32, request ReactionRequest) (*Reaction, error) { +func (s *ReactionsServiceImpl) AddReaction(ctx context.Context, id int32, request ReactionRequest) (*Reaction, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1509,7 +1789,7 @@ func (s *ReactionsService) AddReaction(ctx context.Context, id int32, request Re } } -func (s *ReactionsService) RemoveReaction(ctx context.Context, id int32, params RemoveReactionParams) error { +func (s *ReactionsServiceImpl) RemoveReaction(ctx context.Context, id int32, params RemoveReactionParams) error { u, err := url.Parse(fmt.Sprintf("%s/messages/%v/reactions", s.baseURL, id)) if err != nil { return err @@ -1543,12 +1823,22 @@ func (s *ReactionsService) RemoveReaction(ctx context.Context, id int32, params } } -type ReadMembersService struct { +type ReadMembersService interface { + ListReadMembers(ctx context.Context, id int32, params *ListReadMembersParams) (*any, error) +} + +type ReadMembersServiceStub struct{} + +func (s *ReadMembersServiceStub) ListReadMembers(ctx context.Context, id int32, params *ListReadMembersParams) (*any, error) { + return nil, fmt.Errorf("Read members.listReadMembers is not implemented") +} + +type ReadMembersServiceImpl struct { baseURL string client *http.Client } -func (s *ReadMembersService) ListReadMembers(ctx context.Context, id int32, params *ListReadMembersParams) (*any, error) { +func (s *ReadMembersServiceImpl) ListReadMembers(ctx context.Context, id int32, params *ListReadMembersParams) (*any, error) { u, err := url.Parse(fmt.Sprintf("%s/messages/%v/read_member_ids", s.baseURL, id)) if err != nil { return nil, err @@ -1588,12 +1878,27 @@ func (s *ReadMembersService) ListReadMembers(ctx context.Context, id int32, para } } -type ThreadsService struct { +type ThreadsService interface { + GetThread(ctx context.Context, id int32) (*Thread, error) + CreateThread(ctx context.Context, id int32) (*Thread, error) +} + +type ThreadsServiceStub struct{} + +func (s *ThreadsServiceStub) GetThread(ctx context.Context, id int32) (*Thread, error) { + return nil, fmt.Errorf("Threads.getThread is not implemented") +} + +func (s *ThreadsServiceStub) CreateThread(ctx context.Context, id int32) (*Thread, error) { + return nil, fmt.Errorf("Threads.createThread is not implemented") +} + +type ThreadsServiceImpl struct { baseURL string client *http.Client } -func (s *ThreadsService) GetThread(ctx context.Context, id int32) (*Thread, error) { +func (s *ThreadsServiceImpl) GetThread(ctx context.Context, id int32) (*Thread, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/threads/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -1623,7 +1928,7 @@ func (s *ThreadsService) GetThread(ctx context.Context, id int32) (*Thread, erro } } -func (s *ThreadsService) CreateThread(ctx context.Context, id int32) (*Thread, error) { +func (s *ThreadsServiceImpl) CreateThread(ctx context.Context, id int32) (*Thread, error) { req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/messages/%v/thread", s.baseURL, id), nil) if err != nil { return nil, err @@ -1653,12 +1958,42 @@ func (s *ThreadsService) CreateThread(ctx context.Context, id int32) (*Thread, e } } -type ProfileService struct { +type ProfileService interface { + GetTokenInfo(ctx context.Context) (*AccessTokenInfo, error) + GetProfile(ctx context.Context) (*User, error) + GetStatus(ctx context.Context) (*any, error) + UpdateStatus(ctx context.Context, request StatusUpdateRequest) (*UserStatus, error) + DeleteStatus(ctx context.Context) error +} + +type ProfileServiceStub struct{} + +func (s *ProfileServiceStub) GetTokenInfo(ctx context.Context) (*AccessTokenInfo, error) { + return nil, fmt.Errorf("Profile.getTokenInfo is not implemented") +} + +func (s *ProfileServiceStub) GetProfile(ctx context.Context) (*User, error) { + return nil, fmt.Errorf("Profile.getProfile is not implemented") +} + +func (s *ProfileServiceStub) GetStatus(ctx context.Context) (*any, error) { + return nil, fmt.Errorf("Profile.getStatus is not implemented") +} + +func (s *ProfileServiceStub) UpdateStatus(ctx context.Context, request StatusUpdateRequest) (*UserStatus, error) { + return nil, fmt.Errorf("Profile.updateStatus is not implemented") +} + +func (s *ProfileServiceStub) DeleteStatus(ctx context.Context) error { + return fmt.Errorf("Profile.deleteStatus is not implemented") +} + +type ProfileServiceImpl struct { baseURL string client *http.Client } -func (s *ProfileService) GetTokenInfo(ctx context.Context) (*AccessTokenInfo, error) { +func (s *ProfileServiceImpl) GetTokenInfo(ctx context.Context) (*AccessTokenInfo, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/oauth/token/info", s.baseURL), nil) if err != nil { return nil, err @@ -1688,7 +2023,7 @@ func (s *ProfileService) GetTokenInfo(ctx context.Context) (*AccessTokenInfo, er } } -func (s *ProfileService) GetProfile(ctx context.Context) (*User, error) { +func (s *ProfileServiceImpl) GetProfile(ctx context.Context) (*User, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/profile", s.baseURL), nil) if err != nil { return nil, err @@ -1718,7 +2053,7 @@ func (s *ProfileService) GetProfile(ctx context.Context) (*User, error) { } } -func (s *ProfileService) GetStatus(ctx context.Context) (*any, error) { +func (s *ProfileServiceImpl) GetStatus(ctx context.Context) (*any, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/profile/status", s.baseURL), nil) if err != nil { return nil, err @@ -1746,7 +2081,7 @@ func (s *ProfileService) GetStatus(ctx context.Context) (*any, error) { } } -func (s *ProfileService) UpdateStatus(ctx context.Context, request StatusUpdateRequest) (*UserStatus, error) { +func (s *ProfileServiceImpl) UpdateStatus(ctx context.Context, request StatusUpdateRequest) (*UserStatus, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -1781,7 +2116,7 @@ func (s *ProfileService) UpdateStatus(ctx context.Context, request StatusUpdateR } } -func (s *ProfileService) DeleteStatus(ctx context.Context) error { +func (s *ProfileServiceImpl) DeleteStatus(ctx context.Context) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/profile/status", s.baseURL), nil) if err != nil { return err @@ -1805,12 +2140,47 @@ func (s *ProfileService) DeleteStatus(ctx context.Context) error { } } -type SearchService struct { +type SearchService interface { + SearchChats(ctx context.Context, params *SearchChatsParams) (*ListChatsResponse, error) + SearchChatsAll(ctx context.Context, params *SearchChatsParams) ([]Chat, error) + SearchMessages(ctx context.Context, params *SearchMessagesParams) (*ListChatMessagesResponse, error) + SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]Message, error) + SearchUsers(ctx context.Context, params *SearchUsersParams) (*ListMembersResponse, error) + SearchUsersAll(ctx context.Context, params *SearchUsersParams) ([]User, error) +} + +type SearchServiceStub struct{} + +func (s *SearchServiceStub) SearchChats(ctx context.Context, params *SearchChatsParams) (*ListChatsResponse, error) { + return nil, fmt.Errorf("Search.searchChats is not implemented") +} + +func (s *SearchServiceStub) SearchChatsAll(ctx context.Context, params *SearchChatsParams) ([]Chat, error) { + return nil, fmt.Errorf("Search.searchChatsAll is not implemented") +} + +func (s *SearchServiceStub) SearchMessages(ctx context.Context, params *SearchMessagesParams) (*ListChatMessagesResponse, error) { + return nil, fmt.Errorf("Search.searchMessages is not implemented") +} + +func (s *SearchServiceStub) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]Message, error) { + return nil, fmt.Errorf("Search.searchMessagesAll is not implemented") +} + +func (s *SearchServiceStub) SearchUsers(ctx context.Context, params *SearchUsersParams) (*ListMembersResponse, error) { + return nil, fmt.Errorf("Search.searchUsers is not implemented") +} + +func (s *SearchServiceStub) SearchUsersAll(ctx context.Context, params *SearchUsersParams) ([]User, error) { + return nil, fmt.Errorf("Search.searchUsersAll is not implemented") +} + +type SearchServiceImpl struct { baseURL string client *http.Client } -func (s *SearchService) SearchChats(ctx context.Context, params *SearchChatsParams) (*ListChatsResponse, error) { +func (s *SearchServiceImpl) SearchChats(ctx context.Context, params *SearchChatsParams) (*ListChatsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/chats", s.baseURL)) if err != nil { return nil, err @@ -1871,7 +2241,7 @@ func (s *SearchService) SearchChats(ctx context.Context, params *SearchChatsPara } } -func (s *SearchService) SearchChatsAll(ctx context.Context, params *SearchChatsParams) ([]Chat, error) { +func (s *SearchServiceImpl) SearchChatsAll(ctx context.Context, params *SearchChatsParams) ([]Chat, error) { if params == nil { params = &SearchChatsParams{} } @@ -1891,7 +2261,7 @@ func (s *SearchService) SearchChatsAll(ctx context.Context, params *SearchChatsP } } -func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessagesParams) (*ListChatMessagesResponse, error) { +func (s *SearchServiceImpl) SearchMessages(ctx context.Context, params *SearchMessagesParams) (*ListChatMessagesResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/messages", s.baseURL)) if err != nil { return nil, err @@ -1952,7 +2322,7 @@ func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessag } } -func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]Message, error) { +func (s *SearchServiceImpl) SearchMessagesAll(ctx context.Context, params *SearchMessagesParams) ([]Message, error) { if params == nil { params = &SearchMessagesParams{} } @@ -1972,7 +2342,7 @@ func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMes } } -func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersParams) (*ListMembersResponse, error) { +func (s *SearchServiceImpl) SearchUsers(ctx context.Context, params *SearchUsersParams) (*ListMembersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/users", s.baseURL)) if err != nil { return nil, err @@ -2030,7 +2400,7 @@ func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersPara } } -func (s *SearchService) SearchUsersAll(ctx context.Context, params *SearchUsersParams) ([]User, error) { +func (s *SearchServiceImpl) SearchUsersAll(ctx context.Context, params *SearchUsersParams) ([]User, error) { if params == nil { params = &SearchUsersParams{} } @@ -2050,12 +2420,47 @@ func (s *SearchService) SearchUsersAll(ctx context.Context, params *SearchUsersP } } -type TasksService struct { +type TasksService interface { + ListTasks(ctx context.Context, params *ListTasksParams) (*ListTasksResponse, error) + ListTasksAll(ctx context.Context, params *ListTasksParams) ([]Task, error) + GetTask(ctx context.Context, id int32) (*Task, error) + CreateTask(ctx context.Context, request TaskCreateRequest) (*Task, error) + UpdateTask(ctx context.Context, id int32, request TaskUpdateRequest) (*Task, error) + DeleteTask(ctx context.Context, id int32) error +} + +type TasksServiceStub struct{} + +func (s *TasksServiceStub) ListTasks(ctx context.Context, params *ListTasksParams) (*ListTasksResponse, error) { + return nil, fmt.Errorf("Tasks.listTasks is not implemented") +} + +func (s *TasksServiceStub) ListTasksAll(ctx context.Context, params *ListTasksParams) ([]Task, error) { + return nil, fmt.Errorf("Tasks.listTasksAll is not implemented") +} + +func (s *TasksServiceStub) GetTask(ctx context.Context, id int32) (*Task, error) { + return nil, fmt.Errorf("Tasks.getTask is not implemented") +} + +func (s *TasksServiceStub) CreateTask(ctx context.Context, request TaskCreateRequest) (*Task, error) { + return nil, fmt.Errorf("Tasks.createTask is not implemented") +} + +func (s *TasksServiceStub) UpdateTask(ctx context.Context, id int32, request TaskUpdateRequest) (*Task, error) { + return nil, fmt.Errorf("Tasks.updateTask is not implemented") +} + +func (s *TasksServiceStub) DeleteTask(ctx context.Context, id int32) error { + return fmt.Errorf("Tasks.deleteTask is not implemented") +} + +type TasksServiceImpl struct { baseURL string client *http.Client } -func (s *TasksService) ListTasks(ctx context.Context, params *ListTasksParams) (*ListTasksResponse, error) { +func (s *TasksServiceImpl) ListTasks(ctx context.Context, params *ListTasksParams) (*ListTasksResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/tasks", s.baseURL)) if err != nil { return nil, err @@ -2095,7 +2500,7 @@ func (s *TasksService) ListTasks(ctx context.Context, params *ListTasksParams) ( } } -func (s *TasksService) ListTasksAll(ctx context.Context, params *ListTasksParams) ([]Task, error) { +func (s *TasksServiceImpl) ListTasksAll(ctx context.Context, params *ListTasksParams) ([]Task, error) { if params == nil { params = &ListTasksParams{} } @@ -2115,7 +2520,7 @@ func (s *TasksService) ListTasksAll(ctx context.Context, params *ListTasksParams } } -func (s *TasksService) GetTask(ctx context.Context, id int32) (*Task, error) { +func (s *TasksServiceImpl) GetTask(ctx context.Context, id int32) (*Task, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -2145,7 +2550,7 @@ func (s *TasksService) GetTask(ctx context.Context, id int32) (*Task, error) { } } -func (s *TasksService) CreateTask(ctx context.Context, request TaskCreateRequest) (*Task, error) { +func (s *TasksServiceImpl) CreateTask(ctx context.Context, request TaskCreateRequest) (*Task, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -2180,7 +2585,7 @@ func (s *TasksService) CreateTask(ctx context.Context, request TaskCreateRequest } } -func (s *TasksService) UpdateTask(ctx context.Context, id int32, request TaskUpdateRequest) (*Task, error) { +func (s *TasksServiceImpl) UpdateTask(ctx context.Context, id int32, request TaskUpdateRequest) (*Task, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -2215,7 +2620,7 @@ func (s *TasksService) UpdateTask(ctx context.Context, id int32, request TaskUpd } } -func (s *TasksService) DeleteTask(ctx context.Context, id int32) error { +func (s *TasksServiceImpl) DeleteTask(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/tasks/%v", s.baseURL, id), nil) if err != nil { return err @@ -2239,12 +2644,62 @@ func (s *TasksService) DeleteTask(ctx context.Context, id int32) error { } } -type UsersService struct { +type UsersService interface { + ListUsers(ctx context.Context, params *ListUsersParams) (*ListMembersResponse, error) + ListUsersAll(ctx context.Context, params *ListUsersParams) ([]User, error) + GetUser(ctx context.Context, id int32) (*User, error) + GetUserStatus(ctx context.Context, userId int32) (*any, error) + CreateUser(ctx context.Context, request UserCreateRequest) (*User, error) + UpdateUser(ctx context.Context, id int32, request UserUpdateRequest) (*User, error) + UpdateUserStatus(ctx context.Context, userId int32, request StatusUpdateRequest) (*UserStatus, error) + DeleteUser(ctx context.Context, id int32) error + DeleteUserStatus(ctx context.Context, userId int32) error +} + +type UsersServiceStub struct{} + +func (s *UsersServiceStub) ListUsers(ctx context.Context, params *ListUsersParams) (*ListMembersResponse, error) { + return nil, fmt.Errorf("Users.listUsers is not implemented") +} + +func (s *UsersServiceStub) ListUsersAll(ctx context.Context, params *ListUsersParams) ([]User, error) { + return nil, fmt.Errorf("Users.listUsersAll is not implemented") +} + +func (s *UsersServiceStub) GetUser(ctx context.Context, id int32) (*User, error) { + return nil, fmt.Errorf("Users.getUser is not implemented") +} + +func (s *UsersServiceStub) GetUserStatus(ctx context.Context, userId int32) (*any, error) { + return nil, fmt.Errorf("Users.getUserStatus is not implemented") +} + +func (s *UsersServiceStub) CreateUser(ctx context.Context, request UserCreateRequest) (*User, error) { + return nil, fmt.Errorf("Users.createUser is not implemented") +} + +func (s *UsersServiceStub) UpdateUser(ctx context.Context, id int32, request UserUpdateRequest) (*User, error) { + return nil, fmt.Errorf("Users.updateUser is not implemented") +} + +func (s *UsersServiceStub) UpdateUserStatus(ctx context.Context, userId int32, request StatusUpdateRequest) (*UserStatus, error) { + return nil, fmt.Errorf("Users.updateUserStatus is not implemented") +} + +func (s *UsersServiceStub) DeleteUser(ctx context.Context, id int32) error { + return fmt.Errorf("Users.deleteUser is not implemented") +} + +func (s *UsersServiceStub) DeleteUserStatus(ctx context.Context, userId int32) error { + return fmt.Errorf("Users.deleteUserStatus is not implemented") +} + +type UsersServiceImpl struct { baseURL string client *http.Client } -func (s *UsersService) ListUsers(ctx context.Context, params *ListUsersParams) (*ListMembersResponse, error) { +func (s *UsersServiceImpl) ListUsers(ctx context.Context, params *ListUsersParams) (*ListMembersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/users", s.baseURL)) if err != nil { return nil, err @@ -2287,7 +2742,7 @@ func (s *UsersService) ListUsers(ctx context.Context, params *ListUsersParams) ( } } -func (s *UsersService) ListUsersAll(ctx context.Context, params *ListUsersParams) ([]User, error) { +func (s *UsersServiceImpl) ListUsersAll(ctx context.Context, params *ListUsersParams) ([]User, error) { if params == nil { params = &ListUsersParams{} } @@ -2307,7 +2762,7 @@ func (s *UsersService) ListUsersAll(ctx context.Context, params *ListUsersParams } } -func (s *UsersService) GetUser(ctx context.Context, id int32) (*User, error) { +func (s *UsersServiceImpl) GetUser(ctx context.Context, id int32) (*User, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/users/%v", s.baseURL, id), nil) if err != nil { return nil, err @@ -2337,7 +2792,7 @@ func (s *UsersService) GetUser(ctx context.Context, id int32) (*User, error) { } } -func (s *UsersService) GetUserStatus(ctx context.Context, userId int32) (*any, error) { +func (s *UsersServiceImpl) GetUserStatus(ctx context.Context, userId int32) (*any, error) { req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/users/%v/status", s.baseURL, userId), nil) if err != nil { return nil, err @@ -2365,7 +2820,7 @@ func (s *UsersService) GetUserStatus(ctx context.Context, userId int32) (*any, e } } -func (s *UsersService) CreateUser(ctx context.Context, request UserCreateRequest) (*User, error) { +func (s *UsersServiceImpl) CreateUser(ctx context.Context, request UserCreateRequest) (*User, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -2400,7 +2855,7 @@ func (s *UsersService) CreateUser(ctx context.Context, request UserCreateRequest } } -func (s *UsersService) UpdateUser(ctx context.Context, id int32, request UserUpdateRequest) (*User, error) { +func (s *UsersServiceImpl) UpdateUser(ctx context.Context, id int32, request UserUpdateRequest) (*User, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -2435,7 +2890,7 @@ func (s *UsersService) UpdateUser(ctx context.Context, id int32, request UserUpd } } -func (s *UsersService) UpdateUserStatus(ctx context.Context, userId int32, request StatusUpdateRequest) (*UserStatus, error) { +func (s *UsersServiceImpl) UpdateUserStatus(ctx context.Context, userId int32, request StatusUpdateRequest) (*UserStatus, error) { body, err := json.Marshal(request) if err != nil { return nil, err @@ -2470,7 +2925,7 @@ func (s *UsersService) UpdateUserStatus(ctx context.Context, userId int32, reque } } -func (s *UsersService) DeleteUser(ctx context.Context, id int32) error { +func (s *UsersServiceImpl) DeleteUser(ctx context.Context, id int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/users/%v", s.baseURL, id), nil) if err != nil { return err @@ -2494,7 +2949,7 @@ func (s *UsersService) DeleteUser(ctx context.Context, id int32) error { } } -func (s *UsersService) DeleteUserStatus(ctx context.Context, userId int32) error { +func (s *UsersServiceImpl) DeleteUserStatus(ctx context.Context, userId int32) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/users/%v/status", s.baseURL, userId), nil) if err != nil { return err @@ -2518,12 +2973,22 @@ func (s *UsersService) DeleteUserStatus(ctx context.Context, userId int32) error } } -type ViewsService struct { +type ViewsService interface { + OpenView(ctx context.Context, request OpenViewRequest) error +} + +type ViewsServiceStub struct{} + +func (s *ViewsServiceStub) OpenView(ctx context.Context, request OpenViewRequest) error { + return fmt.Errorf("Views.openView is not implemented") +} + +type ViewsServiceImpl struct { baseURL string client *http.Client } -func (s *ViewsService) OpenView(ctx context.Context, request OpenViewRequest) error { +func (s *ViewsServiceImpl) OpenView(ctx context.Context, request OpenViewRequest) error { body, err := json.Marshal(request) if err != nil { return err @@ -2553,29 +3018,121 @@ func (s *ViewsService) OpenView(ctx context.Context, request OpenViewRequest) er } type PachcaClient struct { - Bots *BotsService - Chats *ChatsService - Common *CommonService - GroupTags *GroupTagsService - LinkPreviews *LinkPreviewsService - Members *MembersService - Messages *MessagesService - Profile *ProfileService - Reactions *ReactionsService - ReadMembers *ReadMembersService - Search *SearchService - Security *SecurityService - Tasks *TasksService - Threads *ThreadsService - Users *UsersService - Views *ViewsService -} + Bots BotsService + Chats ChatsService + Common CommonService + GroupTags GroupTagsService + LinkPreviews LinkPreviewsService + Members MembersService + Messages MessagesService + Profile ProfileService + Reactions ReactionsService + ReadMembers ReadMembersService + Search SearchService + Security SecurityService + Tasks TasksService + Threads ThreadsService + Users UsersService + Views ViewsService +} + +type clientConfig struct { + baseURL string + bots BotsService + chats ChatsService + common CommonService + groupTags GroupTagsService + linkPreviews LinkPreviewsService + members MembersService + messages MessagesService + profile ProfileService + reactions ReactionsService + readMembers ReadMembersService + search SearchService + security SecurityService + tasks TasksService + threads ThreadsService + users UsersService + views ViewsService +} + +type ClientOption func(*clientConfig) const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" -func NewPachcaClient(token string, baseURL ...string) *PachcaClient { - url := DefaultBaseURL - if len(baseURL) > 0 { url = baseURL[0] } +func WithBaseURL(baseURL string) ClientOption { + return func(cfg *clientConfig) { cfg.baseURL = baseURL } +} + +func WithBots(service BotsService) ClientOption { + return func(cfg *clientConfig) { cfg.bots = service } +} + +func WithChats(service ChatsService) ClientOption { + return func(cfg *clientConfig) { cfg.chats = service } +} + +func WithCommon(service CommonService) ClientOption { + return func(cfg *clientConfig) { cfg.common = service } +} + +func WithGroupTags(service GroupTagsService) ClientOption { + return func(cfg *clientConfig) { cfg.groupTags = service } +} + +func WithLinkPreviews(service LinkPreviewsService) ClientOption { + return func(cfg *clientConfig) { cfg.linkPreviews = service } +} + +func WithMembers(service MembersService) ClientOption { + return func(cfg *clientConfig) { cfg.members = service } +} + +func WithMessages(service MessagesService) ClientOption { + return func(cfg *clientConfig) { cfg.messages = service } +} + +func WithProfile(service ProfileService) ClientOption { + return func(cfg *clientConfig) { cfg.profile = service } +} + +func WithReactions(service ReactionsService) ClientOption { + return func(cfg *clientConfig) { cfg.reactions = service } +} + +func WithReadMembers(service ReadMembersService) ClientOption { + return func(cfg *clientConfig) { cfg.readMembers = service } +} + +func WithSearch(service SearchService) ClientOption { + return func(cfg *clientConfig) { cfg.search = service } +} + +func WithSecurity(service SecurityService) ClientOption { + return func(cfg *clientConfig) { cfg.security = service } +} + +func WithTasks(service TasksService) ClientOption { + return func(cfg *clientConfig) { cfg.tasks = service } +} + +func WithThreads(service ThreadsService) ClientOption { + return func(cfg *clientConfig) { cfg.threads = service } +} + +func WithUsers(service UsersService) ClientOption { + return func(cfg *clientConfig) { cfg.users = service } +} + +func WithViews(service ViewsService) ClientOption { + return func(cfg *clientConfig) { cfg.views = service } +} + +func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { + cfg := clientConfig{baseURL: DefaultBaseURL} + for _, opt := range opts { + opt(&cfg) + } client := &http.Client{ Transport: &authTransport{token: token, base: http.DefaultTransport}, CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -2583,21 +3140,21 @@ func NewPachcaClient(token string, baseURL ...string) *PachcaClient { }, } return &PachcaClient{ - Bots : &BotsService{baseURL: url, client: client}, - Chats : &ChatsService{baseURL: url, client: client}, - Common : &CommonService{baseURL: url, client: client}, - GroupTags : &GroupTagsService{baseURL: url, client: client}, - LinkPreviews: &LinkPreviewsService{baseURL: url, client: client}, - Members : &MembersService{baseURL: url, client: client}, - Messages : &MessagesService{baseURL: url, client: client}, - Profile : &ProfileService{baseURL: url, client: client}, - Reactions : &ReactionsService{baseURL: url, client: client}, - ReadMembers : &ReadMembersService{baseURL: url, client: client}, - Search : &SearchService{baseURL: url, client: client}, - Security : &SecurityService{baseURL: url, client: client}, - Tasks : &TasksService{baseURL: url, client: client}, - Threads : &ThreadsService{baseURL: url, client: client}, - Users : &UsersService{baseURL: url, client: client}, - Views : &ViewsService{baseURL: url, client: client}, + Bots : func() BotsService { if cfg.bots != nil { return cfg.bots }; return &BotsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Chats : func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Common : func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceImpl{baseURL: cfg.baseURL, client: client} }(), + GroupTags : func() GroupTagsService { if cfg.groupTags != nil { return cfg.groupTags }; return &GroupTagsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + LinkPreviews: func() LinkPreviewsService { if cfg.linkPreviews != nil { return cfg.linkPreviews }; return &LinkPreviewsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Members : func() MembersService { if cfg.members != nil { return cfg.members }; return &MembersServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Messages : func() MessagesService { if cfg.messages != nil { return cfg.messages }; return &MessagesServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Profile : func() ProfileService { if cfg.profile != nil { return cfg.profile }; return &ProfileServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Reactions : func() ReactionsService { if cfg.reactions != nil { return cfg.reactions }; return &ReactionsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + ReadMembers : func() ReadMembersService { if cfg.readMembers != nil { return cfg.readMembers }; return &ReadMembersServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Search : func() SearchService { if cfg.search != nil { return cfg.search }; return &SearchServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Security : func() SecurityService { if cfg.security != nil { return cfg.security }; return &SecurityServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Tasks : func() TasksService { if cfg.tasks != nil { return cfg.tasks }; return &TasksServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Threads : func() ThreadsService { if cfg.threads != nil { return cfg.threads }; return &ThreadsServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Users : func() UsersService { if cfg.users != nil { return cfg.users }; return &UsersServiceImpl{baseURL: cfg.baseURL, client: client} }(), + Views : func() ViewsService { if cfg.views != nil { return cfg.views }; return &ViewsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt index 9f968882..3fb786e7 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt @@ -14,11 +14,8 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -class SecurityService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun getAuditEvents( +abstract class SecurityService { + open suspend fun getAuditEvents( startTime: String? = null, endTime: String? = null, eventKey: AuditEventKey? = null, @@ -28,6 +25,38 @@ class SecurityService internal constructor( entityType: String? = null, limit: Int? = null, cursor: String? = null, + ): GetAuditEventsResponse { + throw NotImplementedError("Security.getAuditEvents is not implemented") + } + + open suspend fun getAuditEventsAll( + startTime: String? = null, + endTime: String? = null, + eventKey: AuditEventKey? = null, + actorId: String? = null, + actorType: String? = null, + entityId: String? = null, + entityType: String? = null, + limit: Int? = null, + ): List { + throw NotImplementedError("Security.getAuditEventsAll is not implemented") + } +} + +class SecurityServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : SecurityService() { + override suspend fun getAuditEvents( + startTime: String?, + endTime: String?, + eventKey: AuditEventKey?, + actorId: String?, + actorType: String?, + entityId: String?, + entityType: String?, + limit: Int?, + cursor: String?, ): GetAuditEventsResponse { val response = client.get("$baseUrl/audit_events") { startTime?.let { parameter("start_time", it) } @@ -47,15 +76,15 @@ class SecurityService internal constructor( } } - suspend fun getAuditEventsAll( - startTime: String? = null, - endTime: String? = null, - eventKey: AuditEventKey? = null, - actorId: String? = null, - actorType: String? = null, - entityId: String? = null, - entityType: String? = null, - limit: Int? = null, + override suspend fun getAuditEventsAll( + startTime: String?, + endTime: String?, + eventKey: AuditEventKey?, + actorId: String?, + actorType: String?, + entityId: String?, + entityType: String?, + limit: Int?, ): List { val items = mutableListOf() var cursor: String? = null @@ -78,11 +107,29 @@ class SecurityService internal constructor( } } -class BotsService internal constructor( +abstract class BotsService { + open suspend fun getWebhookEvents(limit: Int? = null, cursor: String? = null): GetWebhookEventsResponse { + throw NotImplementedError("Bots.getWebhookEvents is not implemented") + } + + open suspend fun getWebhookEventsAll(limit: Int? = null): List { + throw NotImplementedError("Bots.getWebhookEventsAll is not implemented") + } + + open suspend fun updateBot(id: Int, request: BotUpdateRequest): BotResponse { + throw NotImplementedError("Bots.updateBot is not implemented") + } + + open suspend fun deleteWebhookEvent(id: String) { + throw NotImplementedError("Bots.deleteWebhookEvent is not implemented") + } +} + +class BotsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun getWebhookEvents(limit: Int? = null, cursor: String? = null): GetWebhookEventsResponse { +) : BotsService() { + override suspend fun getWebhookEvents(limit: Int?, cursor: String?): GetWebhookEventsResponse { val response = client.get("$baseUrl/webhooks/events") { limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } @@ -94,7 +141,7 @@ class BotsService internal constructor( } } - suspend fun getWebhookEventsAll(limit: Int? = null): List { + override suspend fun getWebhookEventsAll(limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -105,7 +152,7 @@ class BotsService internal constructor( return items } - suspend fun updateBot(id: Int, request: BotUpdateRequest): BotResponse { + override suspend fun updateBot(id: Int, request: BotUpdateRequest): BotResponse { val response = client.put("$baseUrl/bots/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -117,7 +164,7 @@ class BotsService internal constructor( } } - suspend fun deleteWebhookEvent(id: String) { + override suspend fun deleteWebhookEvent(id: String) { val response = client.delete("$baseUrl/webhooks/events/$id") when (response.status.value) { 204 -> return @@ -127,11 +174,8 @@ class BotsService internal constructor( } } -class ChatsService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listChats( +abstract class ChatsService { + open suspend fun listChats( sortId: SortOrder? = null, availability: ChatAvailability? = null, lastMessageAtAfter: String? = null, @@ -139,6 +183,54 @@ class ChatsService internal constructor( personal: Boolean? = null, limit: Int? = null, cursor: String? = null, + ): ListChatsResponse { + throw NotImplementedError("Chats.listChats is not implemented") + } + + open suspend fun listChatsAll( + sortId: SortOrder? = null, + availability: ChatAvailability? = null, + lastMessageAtAfter: String? = null, + lastMessageAtBefore: String? = null, + personal: Boolean? = null, + limit: Int? = null, + ): List { + throw NotImplementedError("Chats.listChatsAll is not implemented") + } + + open suspend fun getChat(id: Int): Chat { + throw NotImplementedError("Chats.getChat is not implemented") + } + + open suspend fun createChat(request: ChatCreateRequest): Chat { + throw NotImplementedError("Chats.createChat is not implemented") + } + + open suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + throw NotImplementedError("Chats.updateChat is not implemented") + } + + open suspend fun archiveChat(id: Int) { + throw NotImplementedError("Chats.archiveChat is not implemented") + } + + open suspend fun unarchiveChat(id: Int) { + throw NotImplementedError("Chats.unarchiveChat is not implemented") + } +} + +class ChatsServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : ChatsService() { + override suspend fun listChats( + sortId: SortOrder?, + availability: ChatAvailability?, + lastMessageAtAfter: String?, + lastMessageAtBefore: String?, + personal: Boolean?, + limit: Int?, + cursor: String?, ): ListChatsResponse { val response = client.get("$baseUrl/chats") { sortId?.let { parameter("sort[{field}]", it.value) } @@ -156,13 +248,13 @@ class ChatsService internal constructor( } } - suspend fun listChatsAll( - sortId: SortOrder? = null, - availability: ChatAvailability? = null, - lastMessageAtAfter: String? = null, - lastMessageAtBefore: String? = null, - personal: Boolean? = null, - limit: Int? = null, + override suspend fun listChatsAll( + sortId: SortOrder?, + availability: ChatAvailability?, + lastMessageAtAfter: String?, + lastMessageAtBefore: String?, + personal: Boolean?, + limit: Int?, ): List { val items = mutableListOf() var cursor: String? = null @@ -182,7 +274,7 @@ class ChatsService internal constructor( return items } - suspend fun getChat(id: Int): Chat { + override suspend fun getChat(id: Int): Chat { val response = client.get("$baseUrl/chats/$id") return when (response.status.value) { 200 -> response.body().data @@ -191,7 +283,7 @@ class ChatsService internal constructor( } } - suspend fun createChat(request: ChatCreateRequest): Chat { + override suspend fun createChat(request: ChatCreateRequest): Chat { val response = client.post("$baseUrl/chats") { contentType(ContentType.Application.Json) setBody(request) @@ -203,7 +295,7 @@ class ChatsService internal constructor( } } - suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + override suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { val response = client.put("$baseUrl/chats/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -215,7 +307,7 @@ class ChatsService internal constructor( } } - suspend fun archiveChat(id: Int) { + override suspend fun archiveChat(id: Int) { val response = client.put("$baseUrl/chats/$id/archive") when (response.status.value) { 204 -> return @@ -224,7 +316,7 @@ class ChatsService internal constructor( } } - suspend fun unarchiveChat(id: Int) { + override suspend fun unarchiveChat(id: Int) { val response = client.put("$baseUrl/chats/$id/unarchive") when (response.status.value) { 204 -> return @@ -234,11 +326,33 @@ class ChatsService internal constructor( } } -class CommonService internal constructor( +abstract class CommonService { + open suspend fun downloadExport(id: Int): String { + throw NotImplementedError("Common.downloadExport is not implemented") + } + + open suspend fun listProperties(entityType: SearchEntityType): ListPropertiesResponse { + throw NotImplementedError("Common.listProperties is not implemented") + } + + open suspend fun requestExport(request: ExportRequest) { + throw NotImplementedError("Common.requestExport is not implemented") + } + + open suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { + throw NotImplementedError("Common.uploadFile is not implemented") + } + + open suspend fun getUploadParams(): UploadParams { + throw NotImplementedError("Common.getUploadParams is not implemented") + } +} + +class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun downloadExport(id: Int): String { +) : CommonService() { + override suspend fun downloadExport(id: Int): String { val response = client.get("$baseUrl/chats/exports/$id") return when (response.status.value) { 302 -> response.headers[HttpHeaders.Location] @@ -248,7 +362,7 @@ class CommonService internal constructor( } } - suspend fun listProperties(entityType: SearchEntityType): ListPropertiesResponse { + override suspend fun listProperties(entityType: SearchEntityType): ListPropertiesResponse { val response = client.get("$baseUrl/custom_properties") { parameter("entity_type", entityType.value) } @@ -259,7 +373,7 @@ class CommonService internal constructor( } } - suspend fun requestExport(request: ExportRequest) { + override suspend fun requestExport(request: ExportRequest) { val response = client.post("$baseUrl/chats/exports") { contentType(ContentType.Application.Json) setBody(request) @@ -271,7 +385,7 @@ class CommonService internal constructor( } } - suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { + override suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { val response = client.submitFormWithBinaryData( directUrl, formData { @@ -296,7 +410,7 @@ class CommonService internal constructor( } } - suspend fun getUploadParams(): UploadParams { + override suspend fun getUploadParams(): UploadParams { val response = client.post("$baseUrl/uploads") return when (response.status.value) { 201 -> response.body() @@ -306,15 +420,62 @@ class CommonService internal constructor( } } -class MembersService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listMembers( +abstract class MembersService { + open suspend fun listMembers( id: Int, role: ChatMemberRoleFilter? = null, limit: Int? = null, cursor: String? = null, + ): ListMembersResponse { + throw NotImplementedError("Members.listMembers is not implemented") + } + + open suspend fun listMembersAll( + id: Int, + role: ChatMemberRoleFilter? = null, + limit: Int? = null, + ): List { + throw NotImplementedError("Members.listMembersAll is not implemented") + } + + open suspend fun addTags(id: Int, groupTagIds: List) { + throw NotImplementedError("Members.addTags is not implemented") + } + + open suspend fun addMembers(id: Int, request: AddMembersRequest) { + throw NotImplementedError("Members.addMembers is not implemented") + } + + open suspend fun updateMemberRole( + id: Int, + userId: Int, + role: ChatMemberRole, + ) { + throw NotImplementedError("Members.updateMemberRole is not implemented") + } + + open suspend fun removeTag(id: Int, tagId: Int) { + throw NotImplementedError("Members.removeTag is not implemented") + } + + open suspend fun leaveChat(id: Int) { + throw NotImplementedError("Members.leaveChat is not implemented") + } + + open suspend fun removeMember(id: Int, userId: Int) { + throw NotImplementedError("Members.removeMember is not implemented") + } +} + +class MembersServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : MembersService() { + override suspend fun listMembers( + id: Int, + role: ChatMemberRoleFilter?, + limit: Int?, + cursor: String?, ): ListMembersResponse { val response = client.get("$baseUrl/chats/$id/members") { role?.let { parameter("role", it.value) } @@ -328,10 +489,10 @@ class MembersService internal constructor( } } - suspend fun listMembersAll( + override suspend fun listMembersAll( id: Int, - role: ChatMemberRoleFilter? = null, - limit: Int? = null, + role: ChatMemberRoleFilter?, + limit: Int?, ): List { val items = mutableListOf() var cursor: String? = null @@ -348,7 +509,7 @@ class MembersService internal constructor( return items } - suspend fun addTags(id: Int, groupTagIds: List) { + override suspend fun addTags(id: Int, groupTagIds: List) { val response = client.post("$baseUrl/chats/$id/group_tags") { contentType(ContentType.Application.Json) setBody(AddTagsRequest(groupTagIds = groupTagIds)) @@ -360,7 +521,7 @@ class MembersService internal constructor( } } - suspend fun addMembers(id: Int, request: AddMembersRequest) { + override suspend fun addMembers(id: Int, request: AddMembersRequest) { val response = client.post("$baseUrl/chats/$id/members") { contentType(ContentType.Application.Json) setBody(request) @@ -372,7 +533,7 @@ class MembersService internal constructor( } } - suspend fun updateMemberRole( + override suspend fun updateMemberRole( id: Int, userId: Int, role: ChatMemberRole, @@ -388,7 +549,7 @@ class MembersService internal constructor( } } - suspend fun removeTag(id: Int, tagId: Int) { + override suspend fun removeTag(id: Int, tagId: Int) { val response = client.delete("$baseUrl/chats/$id/group_tags/$tagId") when (response.status.value) { 204 -> return @@ -397,7 +558,7 @@ class MembersService internal constructor( } } - suspend fun leaveChat(id: Int) { + override suspend fun leaveChat(id: Int) { val response = client.delete("$baseUrl/chats/$id/leave") when (response.status.value) { 204 -> return @@ -406,7 +567,7 @@ class MembersService internal constructor( } } - suspend fun removeMember(id: Int, userId: Int) { + override suspend fun removeMember(id: Int, userId: Int) { val response = client.delete("$baseUrl/chats/$id/members/$userId") when (response.status.value) { 204 -> return @@ -416,14 +577,56 @@ class MembersService internal constructor( } } -class GroupTagsService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listTags( +abstract class GroupTagsService { + open suspend fun listTags( names: TagNamesFilter? = null, limit: Int? = null, cursor: String? = null, + ): ListTagsResponse { + throw NotImplementedError("Group tags.listTags is not implemented") + } + + open suspend fun listTagsAll(names: TagNamesFilter? = null, limit: Int? = null): List { + throw NotImplementedError("Group tags.listTagsAll is not implemented") + } + + open suspend fun getTag(id: Int): GroupTag { + throw NotImplementedError("Group tags.getTag is not implemented") + } + + open suspend fun getTagUsers( + id: Int, + limit: Int? = null, + cursor: String? = null, + ): ListMembersResponse { + throw NotImplementedError("Group tags.getTagUsers is not implemented") + } + + open suspend fun getTagUsersAll(id: Int, limit: Int? = null): List { + throw NotImplementedError("Group tags.getTagUsersAll is not implemented") + } + + open suspend fun createTag(request: GroupTagRequest): GroupTag { + throw NotImplementedError("Group tags.createTag is not implemented") + } + + open suspend fun updateTag(id: Int, request: GroupTagRequest): GroupTag { + throw NotImplementedError("Group tags.updateTag is not implemented") + } + + open suspend fun deleteTag(id: Int) { + throw NotImplementedError("Group tags.deleteTag is not implemented") + } +} + +class GroupTagsServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : GroupTagsService() { + override suspend fun listTags( + names: TagNamesFilter?, + limit: Int?, + cursor: String?, ): ListTagsResponse { val response = client.get("$baseUrl/group_tags") { names?.let { parameter("names", it) } @@ -437,7 +640,7 @@ class GroupTagsService internal constructor( } } - suspend fun listTagsAll(names: TagNamesFilter? = null, limit: Int? = null): List { + override suspend fun listTagsAll(names: TagNamesFilter?, limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -448,7 +651,7 @@ class GroupTagsService internal constructor( return items } - suspend fun getTag(id: Int): GroupTag { + override suspend fun getTag(id: Int): GroupTag { val response = client.get("$baseUrl/group_tags/$id") return when (response.status.value) { 200 -> response.body().data @@ -457,10 +660,10 @@ class GroupTagsService internal constructor( } } - suspend fun getTagUsers( + override suspend fun getTagUsers( id: Int, - limit: Int? = null, - cursor: String? = null, + limit: Int?, + cursor: String?, ): ListMembersResponse { val response = client.get("$baseUrl/group_tags/$id/users") { limit?.let { parameter("limit", it) } @@ -473,7 +676,7 @@ class GroupTagsService internal constructor( } } - suspend fun getTagUsersAll(id: Int, limit: Int? = null): List { + override suspend fun getTagUsersAll(id: Int, limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -484,7 +687,7 @@ class GroupTagsService internal constructor( return items } - suspend fun createTag(request: GroupTagRequest): GroupTag { + override suspend fun createTag(request: GroupTagRequest): GroupTag { val response = client.post("$baseUrl/group_tags") { contentType(ContentType.Application.Json) setBody(request) @@ -496,7 +699,7 @@ class GroupTagsService internal constructor( } } - suspend fun updateTag(id: Int, request: GroupTagRequest): GroupTag { + override suspend fun updateTag(id: Int, request: GroupTagRequest): GroupTag { val response = client.put("$baseUrl/group_tags/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -508,7 +711,7 @@ class GroupTagsService internal constructor( } } - suspend fun deleteTag(id: Int) { + override suspend fun deleteTag(id: Int) { val response = client.delete("$baseUrl/group_tags/$id") when (response.status.value) { 204 -> return @@ -518,15 +721,58 @@ class GroupTagsService internal constructor( } } -class MessagesService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listChatMessages( +abstract class MessagesService { + open suspend fun listChatMessages( chatId: Int, sortId: SortOrder? = null, limit: Int? = null, cursor: String? = null, + ): ListChatMessagesResponse { + throw NotImplementedError("Messages.listChatMessages is not implemented") + } + + open suspend fun listChatMessagesAll( + chatId: Int, + sortId: SortOrder? = null, + limit: Int? = null, + ): List { + throw NotImplementedError("Messages.listChatMessagesAll is not implemented") + } + + open suspend fun getMessage(id: Int): Message { + throw NotImplementedError("Messages.getMessage is not implemented") + } + + open suspend fun createMessage(request: MessageCreateRequest): Message { + throw NotImplementedError("Messages.createMessage is not implemented") + } + + open suspend fun pinMessage(id: Int) { + throw NotImplementedError("Messages.pinMessage is not implemented") + } + + open suspend fun updateMessage(id: Int, request: MessageUpdateRequest): Message { + throw NotImplementedError("Messages.updateMessage is not implemented") + } + + open suspend fun deleteMessage(id: Int) { + throw NotImplementedError("Messages.deleteMessage is not implemented") + } + + open suspend fun unpinMessage(id: Int) { + throw NotImplementedError("Messages.unpinMessage is not implemented") + } +} + +class MessagesServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : MessagesService() { + override suspend fun listChatMessages( + chatId: Int, + sortId: SortOrder?, + limit: Int?, + cursor: String?, ): ListChatMessagesResponse { val response = client.get("$baseUrl/messages") { parameter("chat_id", chatId) @@ -541,10 +787,10 @@ class MessagesService internal constructor( } } - suspend fun listChatMessagesAll( + override suspend fun listChatMessagesAll( chatId: Int, - sortId: SortOrder? = null, - limit: Int? = null, + sortId: SortOrder?, + limit: Int?, ): List { val items = mutableListOf() var cursor: String? = null @@ -561,7 +807,7 @@ class MessagesService internal constructor( return items } - suspend fun getMessage(id: Int): Message { + override suspend fun getMessage(id: Int): Message { val response = client.get("$baseUrl/messages/$id") return when (response.status.value) { 200 -> response.body().data @@ -570,7 +816,7 @@ class MessagesService internal constructor( } } - suspend fun createMessage(request: MessageCreateRequest): Message { + override suspend fun createMessage(request: MessageCreateRequest): Message { val response = client.post("$baseUrl/messages") { contentType(ContentType.Application.Json) setBody(request) @@ -582,7 +828,7 @@ class MessagesService internal constructor( } } - suspend fun pinMessage(id: Int) { + override suspend fun pinMessage(id: Int) { val response = client.post("$baseUrl/messages/$id/pin") when (response.status.value) { 204 -> return @@ -591,7 +837,7 @@ class MessagesService internal constructor( } } - suspend fun updateMessage(id: Int, request: MessageUpdateRequest): Message { + override suspend fun updateMessage(id: Int, request: MessageUpdateRequest): Message { val response = client.put("$baseUrl/messages/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -603,7 +849,7 @@ class MessagesService internal constructor( } } - suspend fun deleteMessage(id: Int) { + override suspend fun deleteMessage(id: Int) { val response = client.delete("$baseUrl/messages/$id") when (response.status.value) { 204 -> return @@ -612,7 +858,7 @@ class MessagesService internal constructor( } } - suspend fun unpinMessage(id: Int) { + override suspend fun unpinMessage(id: Int) { val response = client.delete("$baseUrl/messages/$id/pin") when (response.status.value) { 204 -> return @@ -622,11 +868,17 @@ class MessagesService internal constructor( } } -class LinkPreviewsService internal constructor( +abstract class LinkPreviewsService { + open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { + throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") + } +} + +class LinkPreviewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { +) : LinkPreviewsService() { + override suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { val response = client.post("$baseUrl/messages/$id/link_previews") { contentType(ContentType.Application.Json) setBody(request) @@ -639,14 +891,40 @@ class LinkPreviewsService internal constructor( } } -class ReactionsService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listReactions( +abstract class ReactionsService { + open suspend fun listReactions( id: Int, limit: Int? = null, cursor: String? = null, + ): ListReactionsResponse { + throw NotImplementedError("Reactions.listReactions is not implemented") + } + + open suspend fun listReactionsAll(id: Int, limit: Int? = null): List { + throw NotImplementedError("Reactions.listReactionsAll is not implemented") + } + + open suspend fun addReaction(id: Int, request: ReactionRequest): Reaction { + throw NotImplementedError("Reactions.addReaction is not implemented") + } + + open suspend fun removeReaction( + id: Int, + code: String, + name: String? = null, + ) { + throw NotImplementedError("Reactions.removeReaction is not implemented") + } +} + +class ReactionsServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : ReactionsService() { + override suspend fun listReactions( + id: Int, + limit: Int?, + cursor: String?, ): ListReactionsResponse { val response = client.get("$baseUrl/messages/$id/reactions") { limit?.let { parameter("limit", it) } @@ -659,7 +937,7 @@ class ReactionsService internal constructor( } } - suspend fun listReactionsAll(id: Int, limit: Int? = null): List { + override suspend fun listReactionsAll(id: Int, limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -670,7 +948,7 @@ class ReactionsService internal constructor( return items } - suspend fun addReaction(id: Int, request: ReactionRequest): Reaction { + override suspend fun addReaction(id: Int, request: ReactionRequest): Reaction { val response = client.post("$baseUrl/messages/$id/reactions") { contentType(ContentType.Application.Json) setBody(request) @@ -682,10 +960,10 @@ class ReactionsService internal constructor( } } - suspend fun removeReaction( + override suspend fun removeReaction( id: Int, code: String, - name: String? = null, + name: String?, ) { val response = client.delete("$baseUrl/messages/$id/reactions") { parameter("code", code) @@ -699,14 +977,24 @@ class ReactionsService internal constructor( } } -class ReadMembersService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listReadMembers( +abstract class ReadMembersService { + open suspend fun listReadMembers( id: Int, limit: Int? = null, cursor: String? = null, + ): Any { + throw NotImplementedError("Read members.listReadMembers is not implemented") + } +} + +class ReadMembersServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : ReadMembersService() { + override suspend fun listReadMembers( + id: Int, + limit: Int?, + cursor: String?, ): Any { val response = client.get("$baseUrl/messages/$id/read_member_ids") { limit?.let { parameter("limit", it) } @@ -720,11 +1008,21 @@ class ReadMembersService internal constructor( } } -class ThreadsService internal constructor( +abstract class ThreadsService { + open suspend fun getThread(id: Int): Thread { + throw NotImplementedError("Threads.getThread is not implemented") + } + + open suspend fun createThread(id: Int): Thread { + throw NotImplementedError("Threads.createThread is not implemented") + } +} + +class ThreadsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun getThread(id: Int): Thread { +) : ThreadsService() { + override suspend fun getThread(id: Int): Thread { val response = client.get("$baseUrl/threads/$id") return when (response.status.value) { 200 -> response.body().data @@ -733,7 +1031,7 @@ class ThreadsService internal constructor( } } - suspend fun createThread(id: Int): Thread { + override suspend fun createThread(id: Int): Thread { val response = client.post("$baseUrl/messages/$id/thread") return when (response.status.value) { 201 -> response.body().data @@ -743,11 +1041,33 @@ class ThreadsService internal constructor( } } -class ProfileService internal constructor( +abstract class ProfileService { + open suspend fun getTokenInfo(): AccessTokenInfo { + throw NotImplementedError("Profile.getTokenInfo is not implemented") + } + + open suspend fun getProfile(): User { + throw NotImplementedError("Profile.getProfile is not implemented") + } + + open suspend fun getStatus(): Any { + throw NotImplementedError("Profile.getStatus is not implemented") + } + + open suspend fun updateStatus(request: StatusUpdateRequest): UserStatus { + throw NotImplementedError("Profile.updateStatus is not implemented") + } + + open suspend fun deleteStatus() { + throw NotImplementedError("Profile.deleteStatus is not implemented") + } +} + +class ProfileServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun getTokenInfo(): AccessTokenInfo { +) : ProfileService() { + override suspend fun getTokenInfo(): AccessTokenInfo { val response = client.get("$baseUrl/oauth/token/info") return when (response.status.value) { 200 -> response.body().data @@ -756,7 +1076,7 @@ class ProfileService internal constructor( } } - suspend fun getProfile(): User { + override suspend fun getProfile(): User { val response = client.get("$baseUrl/profile") return when (response.status.value) { 200 -> response.body().data @@ -765,7 +1085,7 @@ class ProfileService internal constructor( } } - suspend fun getStatus(): Any { + override suspend fun getStatus(): Any { val response = client.get("$baseUrl/profile/status") return when (response.status.value) { 200 -> response.body() @@ -774,7 +1094,7 @@ class ProfileService internal constructor( } } - suspend fun updateStatus(request: StatusUpdateRequest): UserStatus { + override suspend fun updateStatus(request: StatusUpdateRequest): UserStatus { val response = client.put("$baseUrl/profile/status") { contentType(ContentType.Application.Json) setBody(request) @@ -786,7 +1106,7 @@ class ProfileService internal constructor( } } - suspend fun deleteStatus() { + override suspend fun deleteStatus() { val response = client.delete("$baseUrl/profile/status") when (response.status.value) { 204 -> return @@ -796,11 +1116,8 @@ class ProfileService internal constructor( } } -class SearchService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun searchChats( +abstract class SearchService { + open suspend fun searchChats( query: String? = null, limit: Int? = null, cursor: String? = null, @@ -810,6 +1127,90 @@ class SearchService internal constructor( active: Boolean? = null, chatSubtype: ChatSubtype? = null, personal: Boolean? = null, + ): ListChatsResponse { + throw NotImplementedError("Search.searchChats is not implemented") + } + + open suspend fun searchChatsAll( + query: String? = null, + limit: Int? = null, + order: SortOrder? = null, + createdFrom: String? = null, + createdTo: String? = null, + active: Boolean? = null, + chatSubtype: ChatSubtype? = null, + personal: Boolean? = null, + ): List { + throw NotImplementedError("Search.searchChatsAll is not implemented") + } + + open suspend fun searchMessages( + query: String? = null, + limit: Int? = null, + cursor: String? = null, + order: SortOrder? = null, + createdFrom: String? = null, + createdTo: String? = null, + chatIds: List? = null, + userIds: List? = null, + active: Boolean? = null, + ): ListChatMessagesResponse { + throw NotImplementedError("Search.searchMessages is not implemented") + } + + open suspend fun searchMessagesAll( + query: String? = null, + limit: Int? = null, + order: SortOrder? = null, + createdFrom: String? = null, + createdTo: String? = null, + chatIds: List? = null, + userIds: List? = null, + active: Boolean? = null, + ): List { + throw NotImplementedError("Search.searchMessagesAll is not implemented") + } + + open suspend fun searchUsers( + query: String? = null, + limit: Int? = null, + cursor: String? = null, + sort: SearchSortOrder? = null, + order: SortOrder? = null, + createdFrom: String? = null, + createdTo: String? = null, + companyRoles: List? = null, + ): ListMembersResponse { + throw NotImplementedError("Search.searchUsers is not implemented") + } + + open suspend fun searchUsersAll( + query: String? = null, + limit: Int? = null, + sort: SearchSortOrder? = null, + order: SortOrder? = null, + createdFrom: String? = null, + createdTo: String? = null, + companyRoles: List? = null, + ): List { + throw NotImplementedError("Search.searchUsersAll is not implemented") + } +} + +class SearchServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : SearchService() { + override suspend fun searchChats( + query: String?, + limit: Int?, + cursor: String?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + active: Boolean?, + chatSubtype: ChatSubtype?, + personal: Boolean?, ): ListChatsResponse { val response = client.get("$baseUrl/search/chats") { query?.let { parameter("query", it) } @@ -829,15 +1230,15 @@ class SearchService internal constructor( } } - suspend fun searchChatsAll( - query: String? = null, - limit: Int? = null, - order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, - active: Boolean? = null, - chatSubtype: ChatSubtype? = null, - personal: Boolean? = null, + override suspend fun searchChatsAll( + query: String?, + limit: Int?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + active: Boolean?, + chatSubtype: ChatSubtype?, + personal: Boolean?, ): List { val items = mutableListOf() var cursor: String? = null @@ -859,16 +1260,16 @@ class SearchService internal constructor( return items } - suspend fun searchMessages( - query: String? = null, - limit: Int? = null, - cursor: String? = null, - order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, - chatIds: List? = null, - userIds: List? = null, - active: Boolean? = null, + override suspend fun searchMessages( + query: String?, + limit: Int?, + cursor: String?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + chatIds: List?, + userIds: List?, + active: Boolean?, ): ListChatMessagesResponse { val response = client.get("$baseUrl/search/messages") { query?.let { parameter("query", it) } @@ -888,15 +1289,15 @@ class SearchService internal constructor( } } - suspend fun searchMessagesAll( - query: String? = null, - limit: Int? = null, - order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, - chatIds: List? = null, - userIds: List? = null, - active: Boolean? = null, + override suspend fun searchMessagesAll( + query: String?, + limit: Int?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + chatIds: List?, + userIds: List?, + active: Boolean?, ): List { val items = mutableListOf() var cursor: String? = null @@ -918,15 +1319,15 @@ class SearchService internal constructor( return items } - suspend fun searchUsers( - query: String? = null, - limit: Int? = null, - cursor: String? = null, - sort: SearchSortOrder? = null, - order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, - companyRoles: List? = null, + override suspend fun searchUsers( + query: String?, + limit: Int?, + cursor: String?, + sort: SearchSortOrder?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + companyRoles: List?, ): ListMembersResponse { val response = client.get("$baseUrl/search/users") { query?.let { parameter("query", it) } @@ -945,14 +1346,14 @@ class SearchService internal constructor( } } - suspend fun searchUsersAll( - query: String? = null, - limit: Int? = null, - sort: SearchSortOrder? = null, - order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, - companyRoles: List? = null, + override suspend fun searchUsersAll( + query: String?, + limit: Int?, + sort: SearchSortOrder?, + order: SortOrder?, + createdFrom: String?, + createdTo: String?, + companyRoles: List?, ): List { val items = mutableListOf() var cursor: String? = null @@ -974,11 +1375,37 @@ class SearchService internal constructor( } } -class TasksService internal constructor( +abstract class TasksService { + open suspend fun listTasks(limit: Int? = null, cursor: String? = null): ListTasksResponse { + throw NotImplementedError("Tasks.listTasks is not implemented") + } + + open suspend fun listTasksAll(limit: Int? = null): List { + throw NotImplementedError("Tasks.listTasksAll is not implemented") + } + + open suspend fun getTask(id: Int): Task { + throw NotImplementedError("Tasks.getTask is not implemented") + } + + open suspend fun createTask(request: TaskCreateRequest): Task { + throw NotImplementedError("Tasks.createTask is not implemented") + } + + open suspend fun updateTask(id: Int, request: TaskUpdateRequest): Task { + throw NotImplementedError("Tasks.updateTask is not implemented") + } + + open suspend fun deleteTask(id: Int) { + throw NotImplementedError("Tasks.deleteTask is not implemented") + } +} + +class TasksServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun listTasks(limit: Int? = null, cursor: String? = null): ListTasksResponse { +) : TasksService() { + override suspend fun listTasks(limit: Int?, cursor: String?): ListTasksResponse { val response = client.get("$baseUrl/tasks") { limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } @@ -990,7 +1417,7 @@ class TasksService internal constructor( } } - suspend fun listTasksAll(limit: Int? = null): List { + override suspend fun listTasksAll(limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -1001,7 +1428,7 @@ class TasksService internal constructor( return items } - suspend fun getTask(id: Int): Task { + override suspend fun getTask(id: Int): Task { val response = client.get("$baseUrl/tasks/$id") return when (response.status.value) { 200 -> response.body().data @@ -1010,7 +1437,7 @@ class TasksService internal constructor( } } - suspend fun createTask(request: TaskCreateRequest): Task { + override suspend fun createTask(request: TaskCreateRequest): Task { val response = client.post("$baseUrl/tasks") { contentType(ContentType.Application.Json) setBody(request) @@ -1022,7 +1449,7 @@ class TasksService internal constructor( } } - suspend fun updateTask(id: Int, request: TaskUpdateRequest): Task { + override suspend fun updateTask(id: Int, request: TaskUpdateRequest): Task { val response = client.put("$baseUrl/tasks/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -1034,7 +1461,7 @@ class TasksService internal constructor( } } - suspend fun deleteTask(id: Int) { + override suspend fun deleteTask(id: Int) { val response = client.delete("$baseUrl/tasks/$id") when (response.status.value) { 204 -> return @@ -1044,14 +1471,56 @@ class TasksService internal constructor( } } -class UsersService internal constructor( - private val baseUrl: String, - private val client: HttpClient, -) { - suspend fun listUsers( +abstract class UsersService { + open suspend fun listUsers( query: String? = null, limit: Int? = null, cursor: String? = null, + ): ListMembersResponse { + throw NotImplementedError("Users.listUsers is not implemented") + } + + open suspend fun listUsersAll(query: String? = null, limit: Int? = null): List { + throw NotImplementedError("Users.listUsersAll is not implemented") + } + + open suspend fun getUser(id: Int): User { + throw NotImplementedError("Users.getUser is not implemented") + } + + open suspend fun getUserStatus(userId: Int): Any { + throw NotImplementedError("Users.getUserStatus is not implemented") + } + + open suspend fun createUser(request: UserCreateRequest): User { + throw NotImplementedError("Users.createUser is not implemented") + } + + open suspend fun updateUser(id: Int, request: UserUpdateRequest): User { + throw NotImplementedError("Users.updateUser is not implemented") + } + + open suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus { + throw NotImplementedError("Users.updateUserStatus is not implemented") + } + + open suspend fun deleteUser(id: Int) { + throw NotImplementedError("Users.deleteUser is not implemented") + } + + open suspend fun deleteUserStatus(userId: Int) { + throw NotImplementedError("Users.deleteUserStatus is not implemented") + } +} + +class UsersServiceImpl internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) : UsersService() { + override suspend fun listUsers( + query: String?, + limit: Int?, + cursor: String?, ): ListMembersResponse { val response = client.get("$baseUrl/users") { query?.let { parameter("query", it) } @@ -1065,7 +1534,7 @@ class UsersService internal constructor( } } - suspend fun listUsersAll(query: String? = null, limit: Int? = null): List { + override suspend fun listUsersAll(query: String?, limit: Int?): List { val items = mutableListOf() var cursor: String? = null do { @@ -1076,7 +1545,7 @@ class UsersService internal constructor( return items } - suspend fun getUser(id: Int): User { + override suspend fun getUser(id: Int): User { val response = client.get("$baseUrl/users/$id") return when (response.status.value) { 200 -> response.body().data @@ -1085,7 +1554,7 @@ class UsersService internal constructor( } } - suspend fun getUserStatus(userId: Int): Any { + override suspend fun getUserStatus(userId: Int): Any { val response = client.get("$baseUrl/users/$userId/status") return when (response.status.value) { 200 -> response.body() @@ -1094,7 +1563,7 @@ class UsersService internal constructor( } } - suspend fun createUser(request: UserCreateRequest): User { + override suspend fun createUser(request: UserCreateRequest): User { val response = client.post("$baseUrl/users") { contentType(ContentType.Application.Json) setBody(request) @@ -1106,7 +1575,7 @@ class UsersService internal constructor( } } - suspend fun updateUser(id: Int, request: UserUpdateRequest): User { + override suspend fun updateUser(id: Int, request: UserUpdateRequest): User { val response = client.put("$baseUrl/users/$id") { contentType(ContentType.Application.Json) setBody(request) @@ -1118,7 +1587,7 @@ class UsersService internal constructor( } } - suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus { + override suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus { val response = client.put("$baseUrl/users/$userId/status") { contentType(ContentType.Application.Json) setBody(request) @@ -1130,7 +1599,7 @@ class UsersService internal constructor( } } - suspend fun deleteUser(id: Int) { + override suspend fun deleteUser(id: Int) { val response = client.delete("$baseUrl/users/$id") when (response.status.value) { 204 -> return @@ -1139,7 +1608,7 @@ class UsersService internal constructor( } } - suspend fun deleteUserStatus(userId: Int) { + override suspend fun deleteUserStatus(userId: Int) { val response = client.delete("$baseUrl/users/$userId/status") when (response.status.value) { 204 -> return @@ -1149,11 +1618,17 @@ class UsersService internal constructor( } } -class ViewsService internal constructor( +abstract class ViewsService { + open suspend fun openView(request: OpenViewRequest) { + throw NotImplementedError("Views.openView is not implemented") + } +} + +class ViewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) { - suspend fun openView(request: OpenViewRequest) { +) : ViewsService() { + override suspend fun openView(request: OpenViewRequest) { val response = client.post("$baseUrl/views/open") { contentType(ContentType.Application.Json) setBody(request) @@ -1166,7 +1641,26 @@ class ViewsService internal constructor( } } -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { +data class PachcaServices( + val bots: BotsService? = null, + val chats: ChatsService? = null, + val common: CommonService? = null, + val groupTags: GroupTagsService? = null, + val linkPreviews: LinkPreviewsService? = null, + val members: MembersService? = null, + val messages: MessagesService? = null, + val profile: ProfileService? = null, + val reactions: ReactionsService? = null, + val readMembers: ReadMembersService? = null, + val search: SearchService? = null, + val security: SecurityService? = null, + val tasks: TasksService? = null, + val threads: ThreadsService? = null, + val users: UsersService? = null, + val views: ViewsService? = null +) + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { private val client = HttpClient { expectSuccess = false followRedirects = false @@ -1186,22 +1680,22 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val bots = BotsService(baseUrl, client) - val chats = ChatsService(baseUrl, client) - val common = CommonService(baseUrl, client) - val groupTags = GroupTagsService(baseUrl, client) - val linkPreviews = LinkPreviewsService(baseUrl, client) - val members = MembersService(baseUrl, client) - val messages = MessagesService(baseUrl, client) - val profile = ProfileService(baseUrl, client) - val reactions = ReactionsService(baseUrl, client) - val readMembers = ReadMembersService(baseUrl, client) - val search = SearchService(baseUrl, client) - val security = SecurityService(baseUrl, client) - val tasks = TasksService(baseUrl, client) - val threads = ThreadsService(baseUrl, client) - val users = UsersService(baseUrl, client) - val views = ViewsService(baseUrl, client) + val bots: BotsService = services.bots ?: BotsServiceImpl(baseUrl, client) + val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) + val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) + val groupTags: GroupTagsService = services.groupTags ?: GroupTagsServiceImpl(baseUrl, client) + val linkPreviews: LinkPreviewsService = services.linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) + val members: MembersService = services.members ?: MembersServiceImpl(baseUrl, client) + val messages: MessagesService = services.messages ?: MessagesServiceImpl(baseUrl, client) + val profile: ProfileService = services.profile ?: ProfileServiceImpl(baseUrl, client) + val reactions: ReactionsService = services.reactions ?: ReactionsServiceImpl(baseUrl, client) + val readMembers: ReadMembersService = services.readMembers ?: ReadMembersServiceImpl(baseUrl, client) + val search: SearchService = services.search ?: SearchServiceImpl(baseUrl, client) + val security: SecurityService = services.security ?: SecurityServiceImpl(baseUrl, client) + val tasks: TasksService = services.tasks ?: TasksServiceImpl(baseUrl, client) + val threads: ThreadsService = services.threads ?: ThreadsServiceImpl(baseUrl, client) + val users: UsersService = services.users ?: UsersServiceImpl(baseUrl, client) + val views: ViewsService = services.views ?: ViewsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/sdk/python/generated/pachca/client.py b/sdk/python/generated/pachca/client.py index c767669c..0f88df80 100644 --- a/sdk/python/generated/pachca/client.py +++ b/sdk/python/generated/pachca/client.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import httpx from .models import ( @@ -73,6 +75,20 @@ from .utils import deserialize, serialize, RetryTransport class SecurityService: + async def get_audit_events( + self, + params: GetAuditEventsParams | None = None, + ) -> GetAuditEventsResponse: + raise NotImplementedError("Security.getAuditEvents is not implemented") + + async def get_audit_events_all( + self, + params: GetAuditEventsParams | None = None, + ) -> list[AuditEvent]: + raise NotImplementedError("Security.getAuditEventsAll is not implemented") + + +class SecurityServiceImpl(SecurityService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -131,6 +147,33 @@ async def get_audit_events_all( class BotsService: + async def get_webhook_events( + self, + params: GetWebhookEventsParams | None = None, + ) -> GetWebhookEventsResponse: + raise NotImplementedError("Bots.getWebhookEvents is not implemented") + + async def get_webhook_events_all( + self, + params: GetWebhookEventsParams | None = None, + ) -> list[WebhookEvent]: + raise NotImplementedError("Bots.getWebhookEventsAll is not implemented") + + async def update_bot( + self, + id: int, + request: BotUpdateRequest, + ) -> BotResponse: + raise NotImplementedError("Bots.updateBot is not implemented") + + async def delete_webhook_event( + self, + id: str, + ) -> None: + raise NotImplementedError("Bots.deleteWebhookEvent is not implemented") + + +class BotsServiceImpl(BotsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -208,6 +251,51 @@ async def delete_webhook_event( class ChatsService: + async def list_chats( + self, + params: ListChatsParams | None = None, + ) -> ListChatsResponse: + raise NotImplementedError("Chats.listChats is not implemented") + + async def list_chats_all( + self, + params: ListChatsParams | None = None, + ) -> list[Chat]: + raise NotImplementedError("Chats.listChatsAll is not implemented") + + async def get_chat( + self, + id: int, + ) -> Chat: + raise NotImplementedError("Chats.getChat is not implemented") + + async def create_chat( + self, + request: ChatCreateRequest, + ) -> Chat: + raise NotImplementedError("Chats.createChat is not implemented") + + async def update_chat( + self, + id: int, + request: ChatUpdateRequest, + ) -> Chat: + raise NotImplementedError("Chats.updateChat is not implemented") + + async def archive_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Chats.archiveChat is not implemented") + + async def unarchive_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Chats.unarchiveChat is not implemented") + + +class ChatsServiceImpl(ChatsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -343,6 +431,37 @@ async def unarchive_chat( class CommonService: + async def download_export( + self, + id: int, + ) -> str: + raise NotImplementedError("Common.downloadExport is not implemented") + + async def list_properties( + self, + params: ListPropertiesParams, + ) -> ListPropertiesResponse: + raise NotImplementedError("Common.listProperties is not implemented") + + async def request_export( + self, + request: ExportRequest, + ) -> None: + raise NotImplementedError("Common.requestExport is not implemented") + + async def upload_file( + self, + direct_url: str, + request: FileUploadRequest, + ) -> None: + raise NotImplementedError("Common.uploadFile is not implemented") + + async def get_upload_params( + self) -> UploadParams: + raise NotImplementedError("Common.getUploadParams is not implemented") + + +class CommonServiceImpl(CommonService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -444,6 +563,64 @@ async def get_upload_params( class MembersService: + async def list_members( + self, + id: int, + params: ListMembersParams | None = None, + ) -> ListMembersResponse: + raise NotImplementedError("Members.listMembers is not implemented") + + async def list_members_all( + self, + id: int, + params: ListMembersParams | None = None, + ) -> list[User]: + raise NotImplementedError("Members.listMembersAll is not implemented") + + async def add_tags( + self, + id: int, + group_tag_ids: list[int], + ) -> None: + raise NotImplementedError("Members.addTags is not implemented") + + async def add_members( + self, + id: int, + request: AddMembersRequest, + ) -> None: + raise NotImplementedError("Members.addMembers is not implemented") + + async def update_member_role( + self, + id: int, + user_id: int, + role: ChatMemberRole, + ) -> None: + raise NotImplementedError("Members.updateMemberRole is not implemented") + + async def remove_tag( + self, + id: int, + tag_id: int, + ) -> None: + raise NotImplementedError("Members.removeTag is not implemented") + + async def leave_chat( + self, + id: int, + ) -> None: + raise NotImplementedError("Members.leaveChat is not implemented") + + async def remove_member( + self, + id: int, + user_id: int, + ) -> None: + raise NotImplementedError("Members.removeMember is not implemented") + + +class MembersServiceImpl(MembersService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -591,6 +768,59 @@ async def remove_member( class GroupTagsService: + async def list_tags( + self, + params: ListTagsParams | None = None, + ) -> ListTagsResponse: + raise NotImplementedError("Group tags.listTags is not implemented") + + async def list_tags_all( + self, + params: ListTagsParams | None = None, + ) -> list[GroupTag]: + raise NotImplementedError("Group tags.listTagsAll is not implemented") + + async def get_tag( + self, + id: int, + ) -> GroupTag: + raise NotImplementedError("Group tags.getTag is not implemented") + + async def get_tag_users( + self, + id: int, + params: GetTagUsersParams | None = None, + ) -> ListMembersResponse: + raise NotImplementedError("Group tags.getTagUsers is not implemented") + + async def get_tag_users_all( + self, + id: int, + params: GetTagUsersParams | None = None, + ) -> list[User]: + raise NotImplementedError("Group tags.getTagUsersAll is not implemented") + + async def create_tag( + self, + request: GroupTagRequest, + ) -> GroupTag: + raise NotImplementedError("Group tags.createTag is not implemented") + + async def update_tag( + self, + id: int, + request: GroupTagRequest, + ) -> GroupTag: + raise NotImplementedError("Group tags.updateTag is not implemented") + + async def delete_tag( + self, + id: int, + ) -> None: + raise NotImplementedError("Group tags.deleteTag is not implemented") + + +class GroupTagsServiceImpl(GroupTagsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -744,6 +974,57 @@ async def delete_tag( class MessagesService: + async def list_chat_messages( + self, + params: ListChatMessagesParams, + ) -> ListChatMessagesResponse: + raise NotImplementedError("Messages.listChatMessages is not implemented") + + async def list_chat_messages_all( + self, + params: ListChatMessagesParams, + ) -> list[Message]: + raise NotImplementedError("Messages.listChatMessagesAll is not implemented") + + async def get_message( + self, + id: int, + ) -> Message: + raise NotImplementedError("Messages.getMessage is not implemented") + + async def create_message( + self, + request: MessageCreateRequest, + ) -> Message: + raise NotImplementedError("Messages.createMessage is not implemented") + + async def pin_message( + self, + id: int, + ) -> None: + raise NotImplementedError("Messages.pinMessage is not implemented") + + async def update_message( + self, + id: int, + request: MessageUpdateRequest, + ) -> Message: + raise NotImplementedError("Messages.updateMessage is not implemented") + + async def delete_message( + self, + id: int, + ) -> None: + raise NotImplementedError("Messages.deleteMessage is not implemented") + + async def unpin_message( + self, + id: int, + ) -> None: + raise NotImplementedError("Messages.unpinMessage is not implemented") + + +class MessagesServiceImpl(MessagesService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -887,6 +1168,15 @@ async def unpin_message( class LinkPreviewsService: + async def create_link_previews( + self, + id: int, + request: LinkPreviewsRequest, + ) -> None: + raise NotImplementedError("Link Previews.createLinkPreviews is not implemented") + + +class LinkPreviewsServiceImpl(LinkPreviewsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -909,6 +1199,36 @@ async def create_link_previews( class ReactionsService: + async def list_reactions( + self, + id: int, + params: ListReactionsParams | None = None, + ) -> ListReactionsResponse: + raise NotImplementedError("Reactions.listReactions is not implemented") + + async def list_reactions_all( + self, + id: int, + params: ListReactionsParams | None = None, + ) -> list[Reaction]: + raise NotImplementedError("Reactions.listReactionsAll is not implemented") + + async def add_reaction( + self, + id: int, + request: ReactionRequest, + ) -> Reaction: + raise NotImplementedError("Reactions.addReaction is not implemented") + + async def remove_reaction( + self, + id: int, + params: RemoveReactionParams, + ) -> None: + raise NotImplementedError("Reactions.removeReaction is not implemented") + + +class ReactionsServiceImpl(ReactionsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -994,6 +1314,15 @@ async def remove_reaction( class ReadMembersService: + async def list_read_members( + self, + id: int, + params: ListReadMembersParams | None = None, + ) -> object: + raise NotImplementedError("Read members.listReadMembers is not implemented") + + +class ReadMembersServiceImpl(ReadMembersService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1022,6 +1351,20 @@ async def list_read_members( class ThreadsService: + async def get_thread( + self, + id: int, + ) -> Thread: + raise NotImplementedError("Threads.getThread is not implemented") + + async def create_thread( + self, + id: int, + ) -> Thread: + raise NotImplementedError("Threads.createThread is not implemented") + + +class ThreadsServiceImpl(ThreadsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1059,6 +1402,30 @@ async def create_thread( class ProfileService: + async def get_token_info( + self) -> AccessTokenInfo: + raise NotImplementedError("Profile.getTokenInfo is not implemented") + + async def get_profile( + self) -> User: + raise NotImplementedError("Profile.getProfile is not implemented") + + async def get_status( + self) -> object: + raise NotImplementedError("Profile.getStatus is not implemented") + + async def update_status( + self, + request: StatusUpdateRequest, + ) -> UserStatus: + raise NotImplementedError("Profile.updateStatus is not implemented") + + async def delete_status( + self) -> None: + raise NotImplementedError("Profile.deleteStatus is not implemented") + + +class ProfileServiceImpl(ProfileService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1136,6 +1503,44 @@ async def delete_status( class SearchService: + async def search_chats( + self, + params: SearchChatsParams | None = None, + ) -> ListChatsResponse: + raise NotImplementedError("Search.searchChats is not implemented") + + async def search_chats_all( + self, + params: SearchChatsParams | None = None, + ) -> list[Chat]: + raise NotImplementedError("Search.searchChatsAll is not implemented") + + async def search_messages( + self, + params: SearchMessagesParams | None = None, + ) -> ListChatMessagesResponse: + raise NotImplementedError("Search.searchMessages is not implemented") + + async def search_messages_all( + self, + params: SearchMessagesParams | None = None, + ) -> list[Message]: + raise NotImplementedError("Search.searchMessagesAll is not implemented") + + async def search_users( + self, + params: SearchUsersParams | None = None, + ) -> ListMembersResponse: + raise NotImplementedError("Search.searchUsers is not implemented") + + async def search_users_all( + self, + params: SearchUsersParams | None = None, + ) -> list[User]: + raise NotImplementedError("Search.searchUsersAll is not implemented") + + +class SearchServiceImpl(SearchService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1298,6 +1703,45 @@ async def search_users_all( class TasksService: + async def list_tasks( + self, + params: ListTasksParams | None = None, + ) -> ListTasksResponse: + raise NotImplementedError("Tasks.listTasks is not implemented") + + async def list_tasks_all( + self, + params: ListTasksParams | None = None, + ) -> list[Task]: + raise NotImplementedError("Tasks.listTasksAll is not implemented") + + async def get_task( + self, + id: int, + ) -> Task: + raise NotImplementedError("Tasks.getTask is not implemented") + + async def create_task( + self, + request: TaskCreateRequest, + ) -> Task: + raise NotImplementedError("Tasks.createTask is not implemented") + + async def update_task( + self, + id: int, + request: TaskUpdateRequest, + ) -> Task: + raise NotImplementedError("Tasks.updateTask is not implemented") + + async def delete_task( + self, + id: int, + ) -> None: + raise NotImplementedError("Tasks.deleteTask is not implemented") + + +class TasksServiceImpl(TasksService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1408,6 +1852,64 @@ async def delete_task( class UsersService: + async def list_users( + self, + params: ListUsersParams | None = None, + ) -> ListMembersResponse: + raise NotImplementedError("Users.listUsers is not implemented") + + async def list_users_all( + self, + params: ListUsersParams | None = None, + ) -> list[User]: + raise NotImplementedError("Users.listUsersAll is not implemented") + + async def get_user( + self, + id: int, + ) -> User: + raise NotImplementedError("Users.getUser is not implemented") + + async def get_user_status( + self, + user_id: int, + ) -> object: + raise NotImplementedError("Users.getUserStatus is not implemented") + + async def create_user( + self, + request: UserCreateRequest, + ) -> User: + raise NotImplementedError("Users.createUser is not implemented") + + async def update_user( + self, + id: int, + request: UserUpdateRequest, + ) -> User: + raise NotImplementedError("Users.updateUser is not implemented") + + async def update_user_status( + self, + user_id: int, + request: StatusUpdateRequest, + ) -> UserStatus: + raise NotImplementedError("Users.updateUserStatus is not implemented") + + async def delete_user( + self, + id: int, + ) -> None: + raise NotImplementedError("Users.deleteUser is not implemented") + + async def delete_user_status( + self, + user_id: int, + ) -> None: + raise NotImplementedError("Users.deleteUserStatus is not implemented") + + +class UsersServiceImpl(UsersService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1569,6 +2071,14 @@ async def delete_user_status( class ViewsService: + async def open_view( + self, + request: OpenViewRequest, + ) -> None: + raise NotImplementedError("Views.openView is not implemented") + + +class ViewsServiceImpl(ViewsService): def __init__(self, client: httpx.AsyncClient) -> None: self._client = client @@ -1589,29 +2099,50 @@ async def open_view( raise deserialize(ApiError, response.json()) +@dataclass +class PachcaServices: + bots: BotsService | None = None + chats: ChatsService | None = None + common: CommonService | None = None + group_tags: GroupTagsService | None = None + link_previews: LinkPreviewsService | None = None + members: MembersService | None = None + messages: MessagesService | None = None + profile: ProfileService | None = None + reactions: ReactionsService | None = None + read_members: ReadMembersService | None = None + search: SearchService | None = None + security: SecurityService | None = None + tasks: TasksService | None = None + threads: ThreadsService | None = None + users: UsersService | None = None + views: ViewsService | None = None + + class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: + services = services or PachcaServices() self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.bots = BotsService(self._client) - self.chats = ChatsService(self._client) - self.common = CommonService(self._client) - self.group_tags = GroupTagsService(self._client) - self.link_previews = LinkPreviewsService(self._client) - self.members = MembersService(self._client) - self.messages = MessagesService(self._client) - self.profile = ProfileService(self._client) - self.reactions = ReactionsService(self._client) - self.read_members = ReadMembersService(self._client) - self.search = SearchService(self._client) - self.security = SecurityService(self._client) - self.tasks = TasksService(self._client) - self.threads = ThreadsService(self._client) - self.users = UsersService(self._client) - self.views = ViewsService(self._client) + self.bots: BotsService = services.bots or BotsServiceImpl(self._client) + self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) + self.common: CommonService = services.common or CommonServiceImpl(self._client) + self.group_tags: GroupTagsService = services.group_tags or GroupTagsServiceImpl(self._client) + self.link_previews: LinkPreviewsService = services.link_previews or LinkPreviewsServiceImpl(self._client) + self.members: MembersService = services.members or MembersServiceImpl(self._client) + self.messages: MessagesService = services.messages or MessagesServiceImpl(self._client) + self.profile: ProfileService = services.profile or ProfileServiceImpl(self._client) + self.reactions: ReactionsService = services.reactions or ReactionsServiceImpl(self._client) + self.read_members: ReadMembersService = services.read_members or ReadMembersServiceImpl(self._client) + self.search: SearchService = services.search or SearchServiceImpl(self._client) + self.security: SecurityService = services.security or SecurityServiceImpl(self._client) + self.tasks: TasksService = services.tasks or TasksServiceImpl(self._client) + self.threads: ThreadsService = services.threads or ThreadsServiceImpl(self._client) + self.users: UsersService = services.users or UsersServiceImpl(self._client) + self.views: ViewsService = services.views or ViewsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift index 9f45447f..2888c38f 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift @@ -3,7 +3,23 @@ import Foundation import FoundationNetworking #endif -public struct SecurityService { +private func pachcaNotImplemented(_ method: String) -> Error { + NSError(domain: "PachcaClient", code: 1, userInfo: [NSLocalizedDescriptionKey: method + " is not implemented"]) +} + +open class SecurityService { + public init() {} + + open func getAuditEvents(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> GetAuditEventsResponse { + throw pachcaNotImplemented("Security.getAuditEvents") + } + + open func getAuditEventsAll(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil) async throws -> [AuditEvent] { + throw pachcaNotImplemented("Security.getAuditEventsAll") + } +} + +public final class SecurityServiceImpl: SecurityService { let baseURL: String let headers: [String: String] let session: URLSession @@ -12,9 +28,10 @@ public struct SecurityService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func getAuditEvents(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> GetAuditEventsResponse { + public override func getAuditEvents(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> GetAuditEventsResponse { var components = URLComponents(string: "\(baseURL)/audit_events")! var queryItems: [URLQueryItem] = [] if let startTime { queryItems.append(URLQueryItem(name: "start_time", value: startTime)) } @@ -41,7 +58,7 @@ public struct SecurityService { } } - public func getAuditEventsAll(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil) async throws -> [AuditEvent] { + public override func getAuditEventsAll(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil) async throws -> [AuditEvent] { var items: [AuditEvent] = [] var cursor: String? = nil repeat { @@ -53,7 +70,27 @@ public struct SecurityService { } } -public struct BotsService { +open class BotsService { + public init() {} + + open func getWebhookEvents(limit: Int? = nil, cursor: String? = nil) async throws -> GetWebhookEventsResponse { + throw pachcaNotImplemented("Bots.getWebhookEvents") + } + + open func getWebhookEventsAll(limit: Int? = nil) async throws -> [WebhookEvent] { + throw pachcaNotImplemented("Bots.getWebhookEventsAll") + } + + open func updateBot(id: Int, request body: BotUpdateRequest) async throws -> BotResponse { + throw pachcaNotImplemented("Bots.updateBot") + } + + open func deleteWebhookEvent(id: String) async throws -> Void { + throw pachcaNotImplemented("Bots.deleteWebhookEvent") + } +} + +public final class BotsServiceImpl: BotsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -62,9 +99,10 @@ public struct BotsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func getWebhookEvents(limit: Int? = nil, cursor: String? = nil) async throws -> GetWebhookEventsResponse { + public override func getWebhookEvents(limit: Int? = nil, cursor: String? = nil) async throws -> GetWebhookEventsResponse { var components = URLComponents(string: "\(baseURL)/webhooks/events")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -84,7 +122,7 @@ public struct BotsService { } } - public func getWebhookEventsAll(limit: Int? = nil) async throws -> [WebhookEvent] { + public override func getWebhookEventsAll(limit: Int? = nil) async throws -> [WebhookEvent] { var items: [WebhookEvent] = [] var cursor: String? = nil repeat { @@ -95,7 +133,7 @@ public struct BotsService { return items } - public func updateBot(id: Int, request body: BotUpdateRequest) async throws -> BotResponse { + public override func updateBot(id: Int, request body: BotUpdateRequest) async throws -> BotResponse { var request = URLRequest(url: URL(string: "\(baseURL)/bots/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -113,7 +151,7 @@ public struct BotsService { } } - public func deleteWebhookEvent(id: String) async throws -> Void { + public override func deleteWebhookEvent(id: String) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/webhooks/events/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -130,7 +168,39 @@ public struct BotsService { } } -public struct ChatsService { +open class ChatsService { + public init() {} + + open func listChats(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatsResponse { + throw pachcaNotImplemented("Chats.listChats") + } + + open func listChatsAll(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil) async throws -> [Chat] { + throw pachcaNotImplemented("Chats.listChatsAll") + } + + open func getChat(id: Int) async throws -> Chat { + throw pachcaNotImplemented("Chats.getChat") + } + + open func createChat(request body: ChatCreateRequest) async throws -> Chat { + throw pachcaNotImplemented("Chats.createChat") + } + + open func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { + throw pachcaNotImplemented("Chats.updateChat") + } + + open func archiveChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Chats.archiveChat") + } + + open func unarchiveChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Chats.unarchiveChat") + } +} + +public final class ChatsServiceImpl: ChatsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -139,9 +209,10 @@ public struct ChatsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listChats(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatsResponse { + public override func listChats(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatsResponse { var components = URLComponents(string: "\(baseURL)/chats")! var queryItems: [URLQueryItem] = [] if let sortId { queryItems.append(URLQueryItem(name: "sort[{field}]", value: sortId.rawValue)) } @@ -166,7 +237,7 @@ public struct ChatsService { } } - public func listChatsAll(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil) async throws -> [Chat] { + public override func listChatsAll(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil) async throws -> [Chat] { var items: [Chat] = [] var cursor: String? = nil repeat { @@ -177,7 +248,7 @@ public struct ChatsService { return items } - public func getChat(id: Int) async throws -> Chat { + public override func getChat(id: Int) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -192,7 +263,7 @@ public struct ChatsService { } } - public func createChat(request body: ChatCreateRequest) async throws -> Chat { + public override func createChat(request body: ChatCreateRequest) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -210,7 +281,7 @@ public struct ChatsService { } } - public func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { + public override func updateChat(id: Int, request body: ChatUpdateRequest) async throws -> Chat { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -228,7 +299,7 @@ public struct ChatsService { } } - public func archiveChat(id: Int) async throws -> Void { + public override func archiveChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/archive")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -244,7 +315,7 @@ public struct ChatsService { } } - public func unarchiveChat(id: Int) async throws -> Void { + public override func unarchiveChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/unarchive")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -261,7 +332,31 @@ public struct ChatsService { } } -public struct CommonService { +open class CommonService { + public init() {} + + open func downloadExport(id: Int) async throws -> String { + throw pachcaNotImplemented("Common.downloadExport") + } + + open func listProperties(entityType: SearchEntityType) async throws -> ListPropertiesResponse { + throw pachcaNotImplemented("Common.listProperties") + } + + open func requestExport(request body: ExportRequest) async throws -> Void { + throw pachcaNotImplemented("Common.requestExport") + } + + open func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { + throw pachcaNotImplemented("Common.uploadFile") + } + + open func getUploadParams() async throws -> UploadParams { + throw pachcaNotImplemented("Common.getUploadParams") + } +} + +public final class CommonServiceImpl: CommonService { let baseURL: String let headers: [String: String] let session: URLSession @@ -270,9 +365,10 @@ public struct CommonService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func downloadExport(id: Int) async throws -> String { + public override func downloadExport(id: Int) async throws -> String { var request = URLRequest(url: URL(string: "\(baseURL)/chats/exports/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let delegate = RedirectPreventer() @@ -291,7 +387,7 @@ public struct CommonService { } } - public func listProperties(entityType: SearchEntityType) async throws -> ListPropertiesResponse { + public override func listProperties(entityType: SearchEntityType) async throws -> ListPropertiesResponse { var components = URLComponents(string: "\(baseURL)/custom_properties")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "entity_type", value: entityType.rawValue)) @@ -310,7 +406,7 @@ public struct CommonService { } } - public func requestExport(request body: ExportRequest) async throws -> Void { + public override func requestExport(request body: ExportRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/exports")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -328,7 +424,7 @@ public struct CommonService { } } - public func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { + public override func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(directUrl)")!) request.httpMethod = "POST" let boundary = UUID().uuidString @@ -364,7 +460,7 @@ public struct CommonService { } } - public func getUploadParams() async throws -> UploadParams { + public override func getUploadParams() async throws -> UploadParams { var request = URLRequest(url: URL(string: "\(baseURL)/uploads")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -381,7 +477,43 @@ public struct CommonService { } } -public struct MembersService { +open class MembersService { + public init() {} + + open func listMembers(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + throw pachcaNotImplemented("Members.listMembers") + } + + open func listMembersAll(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil) async throws -> [User] { + throw pachcaNotImplemented("Members.listMembersAll") + } + + open func addTags(id: Int, groupTagIds: [Int]) async throws -> Void { + throw pachcaNotImplemented("Members.addTags") + } + + open func addMembers(id: Int, request body: AddMembersRequest) async throws -> Void { + throw pachcaNotImplemented("Members.addMembers") + } + + open func updateMemberRole(id: Int, userId: Int, role: ChatMemberRole) async throws -> Void { + throw pachcaNotImplemented("Members.updateMemberRole") + } + + open func removeTag(id: Int, tagId: Int) async throws -> Void { + throw pachcaNotImplemented("Members.removeTag") + } + + open func leaveChat(id: Int) async throws -> Void { + throw pachcaNotImplemented("Members.leaveChat") + } + + open func removeMember(id: Int, userId: Int) async throws -> Void { + throw pachcaNotImplemented("Members.removeMember") + } +} + +public final class MembersServiceImpl: MembersService { let baseURL: String let headers: [String: String] let session: URLSession @@ -390,9 +522,10 @@ public struct MembersService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listMembers(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + public override func listMembers(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { var components = URLComponents(string: "\(baseURL)/chats/{id}/members")! var queryItems: [URLQueryItem] = [] if let role { queryItems.append(URLQueryItem(name: "role", value: role.rawValue)) } @@ -413,7 +546,7 @@ public struct MembersService { } } - public func listMembersAll(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil) async throws -> [User] { + public override func listMembersAll(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil) async throws -> [User] { var items: [User] = [] var cursor: String? = nil repeat { @@ -424,7 +557,7 @@ public struct MembersService { return items } - public func addTags(id: Int, groupTagIds: [Int]) async throws -> Void { + public override func addTags(id: Int, groupTagIds: [Int]) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/group_tags")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -442,7 +575,7 @@ public struct MembersService { } } - public func addMembers(id: Int, request body: AddMembersRequest) async throws -> Void { + public override func addMembers(id: Int, request body: AddMembersRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/members")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -460,7 +593,7 @@ public struct MembersService { } } - public func updateMemberRole(id: Int, userId: Int, role: ChatMemberRole) async throws -> Void { + public override func updateMemberRole(id: Int, userId: Int, role: ChatMemberRole) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/members/\(userId)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -478,7 +611,7 @@ public struct MembersService { } } - public func removeTag(id: Int, tagId: Int) async throws -> Void { + public override func removeTag(id: Int, tagId: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/group_tags/\(tagId)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -494,7 +627,7 @@ public struct MembersService { } } - public func leaveChat(id: Int) async throws -> Void { + public override func leaveChat(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/leave")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -510,7 +643,7 @@ public struct MembersService { } } - public func removeMember(id: Int, userId: Int) async throws -> Void { + public override func removeMember(id: Int, userId: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/chats/\(id)/members/\(userId)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -527,7 +660,43 @@ public struct MembersService { } } -public struct GroupTagsService { +open class GroupTagsService { + public init() {} + + open func listTags(names: TagNamesFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListTagsResponse { + throw pachcaNotImplemented("Group tags.listTags") + } + + open func listTagsAll(names: TagNamesFilter? = nil, limit: Int? = nil) async throws -> [GroupTag] { + throw pachcaNotImplemented("Group tags.listTagsAll") + } + + open func getTag(id: Int) async throws -> GroupTag { + throw pachcaNotImplemented("Group tags.getTag") + } + + open func getTagUsers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + throw pachcaNotImplemented("Group tags.getTagUsers") + } + + open func getTagUsersAll(id: Int, limit: Int? = nil) async throws -> [User] { + throw pachcaNotImplemented("Group tags.getTagUsersAll") + } + + open func createTag(request body: GroupTagRequest) async throws -> GroupTag { + throw pachcaNotImplemented("Group tags.createTag") + } + + open func updateTag(id: Int, request body: GroupTagRequest) async throws -> GroupTag { + throw pachcaNotImplemented("Group tags.updateTag") + } + + open func deleteTag(id: Int) async throws -> Void { + throw pachcaNotImplemented("Group tags.deleteTag") + } +} + +public final class GroupTagsServiceImpl: GroupTagsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -536,9 +705,10 @@ public struct GroupTagsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listTags(names: TagNamesFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListTagsResponse { + public override func listTags(names: TagNamesFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListTagsResponse { var components = URLComponents(string: "\(baseURL)/group_tags")! var queryItems: [URLQueryItem] = [] if let names { queryItems.append(URLQueryItem(name: "names", value: String(data: try serialize(names), encoding: .utf8)!)) } @@ -559,7 +729,7 @@ public struct GroupTagsService { } } - public func listTagsAll(names: TagNamesFilter? = nil, limit: Int? = nil) async throws -> [GroupTag] { + public override func listTagsAll(names: TagNamesFilter? = nil, limit: Int? = nil) async throws -> [GroupTag] { var items: [GroupTag] = [] var cursor: String? = nil repeat { @@ -570,7 +740,7 @@ public struct GroupTagsService { return items } - public func getTag(id: Int) async throws -> GroupTag { + public override func getTag(id: Int) async throws -> GroupTag { var request = URLRequest(url: URL(string: "\(baseURL)/group_tags/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -585,7 +755,7 @@ public struct GroupTagsService { } } - public func getTagUsers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + public override func getTagUsers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { var components = URLComponents(string: "\(baseURL)/group_tags/{id}/users")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -605,7 +775,7 @@ public struct GroupTagsService { } } - public func getTagUsersAll(id: Int, limit: Int? = nil) async throws -> [User] { + public override func getTagUsersAll(id: Int, limit: Int? = nil) async throws -> [User] { var items: [User] = [] var cursor: String? = nil repeat { @@ -616,7 +786,7 @@ public struct GroupTagsService { return items } - public func createTag(request body: GroupTagRequest) async throws -> GroupTag { + public override func createTag(request body: GroupTagRequest) async throws -> GroupTag { var request = URLRequest(url: URL(string: "\(baseURL)/group_tags")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -634,7 +804,7 @@ public struct GroupTagsService { } } - public func updateTag(id: Int, request body: GroupTagRequest) async throws -> GroupTag { + public override func updateTag(id: Int, request body: GroupTagRequest) async throws -> GroupTag { var request = URLRequest(url: URL(string: "\(baseURL)/group_tags/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -652,7 +822,7 @@ public struct GroupTagsService { } } - public func deleteTag(id: Int) async throws -> Void { + public override func deleteTag(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/group_tags/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -669,7 +839,43 @@ public struct GroupTagsService { } } -public struct MessagesService { +open class MessagesService { + public init() {} + + open func listChatMessages(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatMessagesResponse { + throw pachcaNotImplemented("Messages.listChatMessages") + } + + open func listChatMessagesAll(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil) async throws -> [Message] { + throw pachcaNotImplemented("Messages.listChatMessagesAll") + } + + open func getMessage(id: Int) async throws -> Message { + throw pachcaNotImplemented("Messages.getMessage") + } + + open func createMessage(request body: MessageCreateRequest) async throws -> Message { + throw pachcaNotImplemented("Messages.createMessage") + } + + open func pinMessage(id: Int) async throws -> Void { + throw pachcaNotImplemented("Messages.pinMessage") + } + + open func updateMessage(id: Int, request body: MessageUpdateRequest) async throws -> Message { + throw pachcaNotImplemented("Messages.updateMessage") + } + + open func deleteMessage(id: Int) async throws -> Void { + throw pachcaNotImplemented("Messages.deleteMessage") + } + + open func unpinMessage(id: Int) async throws -> Void { + throw pachcaNotImplemented("Messages.unpinMessage") + } +} + +public final class MessagesServiceImpl: MessagesService { let baseURL: String let headers: [String: String] let session: URLSession @@ -678,9 +884,10 @@ public struct MessagesService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listChatMessages(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatMessagesResponse { + public override func listChatMessages(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatMessagesResponse { var components = URLComponents(string: "\(baseURL)/messages")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "chat_id", value: String(chatId))) @@ -702,7 +909,7 @@ public struct MessagesService { } } - public func listChatMessagesAll(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil) async throws -> [Message] { + public override func listChatMessagesAll(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil) async throws -> [Message] { var items: [Message] = [] var cursor: String? = nil repeat { @@ -713,7 +920,7 @@ public struct MessagesService { return items } - public func getMessage(id: Int) async throws -> Message { + public override func getMessage(id: Int) async throws -> Message { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -728,7 +935,7 @@ public struct MessagesService { } } - public func createMessage(request body: MessageCreateRequest) async throws -> Message { + public override func createMessage(request body: MessageCreateRequest) async throws -> Message { var request = URLRequest(url: URL(string: "\(baseURL)/messages")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -746,7 +953,7 @@ public struct MessagesService { } } - public func pinMessage(id: Int) async throws -> Void { + public override func pinMessage(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/pin")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -762,7 +969,7 @@ public struct MessagesService { } } - public func updateMessage(id: Int, request body: MessageUpdateRequest) async throws -> Message { + public override func updateMessage(id: Int, request body: MessageUpdateRequest) async throws -> Message { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -780,7 +987,7 @@ public struct MessagesService { } } - public func deleteMessage(id: Int) async throws -> Void { + public override func deleteMessage(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -796,7 +1003,7 @@ public struct MessagesService { } } - public func unpinMessage(id: Int) async throws -> Void { + public override func unpinMessage(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/pin")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -813,7 +1020,15 @@ public struct MessagesService { } } -public struct LinkPreviewsService { +open class LinkPreviewsService { + public init() {} + + open func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { + throw pachcaNotImplemented("Link Previews.createLinkPreviews") + } +} + +public final class LinkPreviewsServiceImpl: LinkPreviewsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -822,9 +1037,10 @@ public struct LinkPreviewsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { + public override func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/link_previews")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -843,7 +1059,27 @@ public struct LinkPreviewsService { } } -public struct ReactionsService { +open class ReactionsService { + public init() {} + + open func listReactions(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListReactionsResponse { + throw pachcaNotImplemented("Reactions.listReactions") + } + + open func listReactionsAll(id: Int, limit: Int? = nil) async throws -> [Reaction] { + throw pachcaNotImplemented("Reactions.listReactionsAll") + } + + open func addReaction(id: Int, request body: ReactionRequest) async throws -> Reaction { + throw pachcaNotImplemented("Reactions.addReaction") + } + + open func removeReaction(id: Int, code: String, name: String? = nil) async throws -> Void { + throw pachcaNotImplemented("Reactions.removeReaction") + } +} + +public final class ReactionsServiceImpl: ReactionsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -852,9 +1088,10 @@ public struct ReactionsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listReactions(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListReactionsResponse { + public override func listReactions(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListReactionsResponse { var components = URLComponents(string: "\(baseURL)/messages/{id}/reactions")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -874,7 +1111,7 @@ public struct ReactionsService { } } - public func listReactionsAll(id: Int, limit: Int? = nil) async throws -> [Reaction] { + public override func listReactionsAll(id: Int, limit: Int? = nil) async throws -> [Reaction] { var items: [Reaction] = [] var cursor: String? = nil repeat { @@ -885,7 +1122,7 @@ public struct ReactionsService { return items } - public func addReaction(id: Int, request body: ReactionRequest) async throws -> Reaction { + public override func addReaction(id: Int, request body: ReactionRequest) async throws -> Reaction { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/reactions")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -903,7 +1140,7 @@ public struct ReactionsService { } } - public func removeReaction(id: Int, code: String, name: String? = nil) async throws -> Void { + public override func removeReaction(id: Int, code: String, name: String? = nil) async throws -> Void { var components = URLComponents(string: "\(baseURL)/messages/{id}/reactions")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "code", value: String(code))) @@ -925,7 +1162,15 @@ public struct ReactionsService { } } -public struct ReadMembersService { +open class ReadMembersService { + public init() {} + + open func listReadMembers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> String { + throw pachcaNotImplemented("Read members.listReadMembers") + } +} + +public final class ReadMembersServiceImpl: ReadMembersService { let baseURL: String let headers: [String: String] let session: URLSession @@ -934,9 +1179,10 @@ public struct ReadMembersService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listReadMembers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> String { + public override func listReadMembers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> String { var components = URLComponents(string: "\(baseURL)/messages/{id}/read_member_ids")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -957,7 +1203,19 @@ public struct ReadMembersService { } } -public struct ThreadsService { +open class ThreadsService { + public init() {} + + open func getThread(id: Int) async throws -> Thread { + throw pachcaNotImplemented("Threads.getThread") + } + + open func createThread(id: Int) async throws -> Thread { + throw pachcaNotImplemented("Threads.createThread") + } +} + +public final class ThreadsServiceImpl: ThreadsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -966,9 +1224,10 @@ public struct ThreadsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func getThread(id: Int) async throws -> Thread { + public override func getThread(id: Int) async throws -> Thread { var request = URLRequest(url: URL(string: "\(baseURL)/threads/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -983,7 +1242,7 @@ public struct ThreadsService { } } - public func createThread(id: Int) async throws -> Thread { + public override func createThread(id: Int) async throws -> Thread { var request = URLRequest(url: URL(string: "\(baseURL)/messages/\(id)/thread")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1000,7 +1259,31 @@ public struct ThreadsService { } } -public struct ProfileService { +open class ProfileService { + public init() {} + + open func getTokenInfo() async throws -> AccessTokenInfo { + throw pachcaNotImplemented("Profile.getTokenInfo") + } + + open func getProfile() async throws -> User { + throw pachcaNotImplemented("Profile.getProfile") + } + + open func getStatus() async throws -> String { + throw pachcaNotImplemented("Profile.getStatus") + } + + open func updateStatus(request body: StatusUpdateRequest) async throws -> UserStatus { + throw pachcaNotImplemented("Profile.updateStatus") + } + + open func deleteStatus() async throws -> Void { + throw pachcaNotImplemented("Profile.deleteStatus") + } +} + +public final class ProfileServiceImpl: ProfileService { let baseURL: String let headers: [String: String] let session: URLSession @@ -1009,9 +1292,10 @@ public struct ProfileService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func getTokenInfo() async throws -> AccessTokenInfo { + public override func getTokenInfo() async throws -> AccessTokenInfo { var request = URLRequest(url: URL(string: "\(baseURL)/oauth/token/info")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1026,7 +1310,7 @@ public struct ProfileService { } } - public func getProfile() async throws -> User { + public override func getProfile() async throws -> User { var request = URLRequest(url: URL(string: "\(baseURL)/profile")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1041,7 +1325,7 @@ public struct ProfileService { } } - public func getStatus() async throws -> String { + public override func getStatus() async throws -> String { var request = URLRequest(url: URL(string: "\(baseURL)/profile/status")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1056,7 +1340,7 @@ public struct ProfileService { } } - public func updateStatus(request body: StatusUpdateRequest) async throws -> UserStatus { + public override func updateStatus(request body: StatusUpdateRequest) async throws -> UserStatus { var request = URLRequest(url: URL(string: "\(baseURL)/profile/status")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1074,7 +1358,7 @@ public struct ProfileService { } } - public func deleteStatus() async throws -> Void { + public override func deleteStatus() async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/profile/status")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1091,7 +1375,35 @@ public struct ProfileService { } } -public struct SearchService { +open class SearchService { + public init() {} + + open func searchChats(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> ListChatsResponse { + throw pachcaNotImplemented("Search.searchChats") + } + + open func searchChatsAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> [Chat] { + throw pachcaNotImplemented("Search.searchChatsAll") + } + + open func searchMessages(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> ListChatMessagesResponse { + throw pachcaNotImplemented("Search.searchMessages") + } + + open func searchMessagesAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> [Message] { + throw pachcaNotImplemented("Search.searchMessagesAll") + } + + open func searchUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> ListMembersResponse { + throw pachcaNotImplemented("Search.searchUsers") + } + + open func searchUsersAll(query: String? = nil, limit: Int? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> [User] { + throw pachcaNotImplemented("Search.searchUsersAll") + } +} + +public final class SearchServiceImpl: SearchService { let baseURL: String let headers: [String: String] let session: URLSession @@ -1100,9 +1412,10 @@ public struct SearchService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func searchChats(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> ListChatsResponse { + public override func searchChats(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> ListChatsResponse { var components = URLComponents(string: "\(baseURL)/search/chats")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1129,7 +1442,7 @@ public struct SearchService { } } - public func searchChatsAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> [Chat] { + public override func searchChatsAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> [Chat] { var items: [Chat] = [] var cursor: String? = nil repeat { @@ -1140,7 +1453,7 @@ public struct SearchService { return items } - public func searchMessages(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> ListChatMessagesResponse { + public override func searchMessages(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> ListChatMessagesResponse { var components = URLComponents(string: "\(baseURL)/search/messages")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1167,7 +1480,7 @@ public struct SearchService { } } - public func searchMessagesAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> [Message] { + public override func searchMessagesAll(query: String? = nil, limit: Int? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> [Message] { var items: [Message] = [] var cursor: String? = nil repeat { @@ -1178,7 +1491,7 @@ public struct SearchService { return items } - public func searchUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> ListMembersResponse { + public override func searchUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> ListMembersResponse { var components = URLComponents(string: "\(baseURL)/search/users")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1204,7 +1517,7 @@ public struct SearchService { } } - public func searchUsersAll(query: String? = nil, limit: Int? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> [User] { + public override func searchUsersAll(query: String? = nil, limit: Int? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> [User] { var items: [User] = [] var cursor: String? = nil repeat { @@ -1216,7 +1529,35 @@ public struct SearchService { } } -public struct TasksService { +open class TasksService { + public init() {} + + open func listTasks(limit: Int? = nil, cursor: String? = nil) async throws -> ListTasksResponse { + throw pachcaNotImplemented("Tasks.listTasks") + } + + open func listTasksAll(limit: Int? = nil) async throws -> [Task] { + throw pachcaNotImplemented("Tasks.listTasksAll") + } + + open func getTask(id: Int) async throws -> Task { + throw pachcaNotImplemented("Tasks.getTask") + } + + open func createTask(request body: TaskCreateRequest) async throws -> Task { + throw pachcaNotImplemented("Tasks.createTask") + } + + open func updateTask(id: Int, request body: TaskUpdateRequest) async throws -> Task { + throw pachcaNotImplemented("Tasks.updateTask") + } + + open func deleteTask(id: Int) async throws -> Void { + throw pachcaNotImplemented("Tasks.deleteTask") + } +} + +public final class TasksServiceImpl: TasksService { let baseURL: String let headers: [String: String] let session: URLSession @@ -1225,9 +1566,10 @@ public struct TasksService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listTasks(limit: Int? = nil, cursor: String? = nil) async throws -> ListTasksResponse { + public override func listTasks(limit: Int? = nil, cursor: String? = nil) async throws -> ListTasksResponse { var components = URLComponents(string: "\(baseURL)/tasks")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -1247,7 +1589,7 @@ public struct TasksService { } } - public func listTasksAll(limit: Int? = nil) async throws -> [Task] { + public override func listTasksAll(limit: Int? = nil) async throws -> [Task] { var items: [Task] = [] var cursor: String? = nil repeat { @@ -1258,7 +1600,7 @@ public struct TasksService { return items } - public func getTask(id: Int) async throws -> Task { + public override func getTask(id: Int) async throws -> Task { var request = URLRequest(url: URL(string: "\(baseURL)/tasks/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1273,7 +1615,7 @@ public struct TasksService { } } - public func createTask(request body: TaskCreateRequest) async throws -> Task { + public override func createTask(request body: TaskCreateRequest) async throws -> Task { var request = URLRequest(url: URL(string: "\(baseURL)/tasks")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1291,7 +1633,7 @@ public struct TasksService { } } - public func updateTask(id: Int, request body: TaskUpdateRequest) async throws -> Task { + public override func updateTask(id: Int, request body: TaskUpdateRequest) async throws -> Task { var request = URLRequest(url: URL(string: "\(baseURL)/tasks/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1309,7 +1651,7 @@ public struct TasksService { } } - public func deleteTask(id: Int) async throws -> Void { + public override func deleteTask(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/tasks/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1326,7 +1668,47 @@ public struct TasksService { } } -public struct UsersService { +open class UsersService { + public init() {} + + open func listUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + throw pachcaNotImplemented("Users.listUsers") + } + + open func listUsersAll(query: String? = nil, limit: Int? = nil) async throws -> [User] { + throw pachcaNotImplemented("Users.listUsersAll") + } + + open func getUser(id: Int) async throws -> User { + throw pachcaNotImplemented("Users.getUser") + } + + open func getUserStatus(userId: Int) async throws -> String { + throw pachcaNotImplemented("Users.getUserStatus") + } + + open func createUser(request body: UserCreateRequest) async throws -> User { + throw pachcaNotImplemented("Users.createUser") + } + + open func updateUser(id: Int, request body: UserUpdateRequest) async throws -> User { + throw pachcaNotImplemented("Users.updateUser") + } + + open func updateUserStatus(userId: Int, request body: StatusUpdateRequest) async throws -> UserStatus { + throw pachcaNotImplemented("Users.updateUserStatus") + } + + open func deleteUser(id: Int) async throws -> Void { + throw pachcaNotImplemented("Users.deleteUser") + } + + open func deleteUserStatus(userId: Int) async throws -> Void { + throw pachcaNotImplemented("Users.deleteUserStatus") + } +} + +public final class UsersServiceImpl: UsersService { let baseURL: String let headers: [String: String] let session: URLSession @@ -1335,9 +1717,10 @@ public struct UsersService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func listUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + public override func listUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { var components = URLComponents(string: "\(baseURL)/users")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1358,7 +1741,7 @@ public struct UsersService { } } - public func listUsersAll(query: String? = nil, limit: Int? = nil) async throws -> [User] { + public override func listUsersAll(query: String? = nil, limit: Int? = nil) async throws -> [User] { var items: [User] = [] var cursor: String? = nil repeat { @@ -1369,7 +1752,7 @@ public struct UsersService { return items } - public func getUser(id: Int) async throws -> User { + public override func getUser(id: Int) async throws -> User { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(id)")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1384,7 +1767,7 @@ public struct UsersService { } } - public func getUserStatus(userId: Int) async throws -> String { + public override func getUserStatus(userId: Int) async throws -> String { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/status")!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } let (data, urlResponse) = try await dataWithRetry(session: session, for: request) @@ -1399,7 +1782,7 @@ public struct UsersService { } } - public func createUser(request body: UserCreateRequest) async throws -> User { + public override func createUser(request body: UserCreateRequest) async throws -> User { var request = URLRequest(url: URL(string: "\(baseURL)/users")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1417,7 +1800,7 @@ public struct UsersService { } } - public func updateUser(id: Int, request body: UserUpdateRequest) async throws -> User { + public override func updateUser(id: Int, request body: UserUpdateRequest) async throws -> User { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(id)")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1435,7 +1818,7 @@ public struct UsersService { } } - public func updateUserStatus(userId: Int, request body: StatusUpdateRequest) async throws -> UserStatus { + public override func updateUserStatus(userId: Int, request body: StatusUpdateRequest) async throws -> UserStatus { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/status")!) request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1453,7 +1836,7 @@ public struct UsersService { } } - public func deleteUser(id: Int) async throws -> Void { + public override func deleteUser(id: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(id)")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1469,7 +1852,7 @@ public struct UsersService { } } - public func deleteUserStatus(userId: Int) async throws -> Void { + public override func deleteUserStatus(userId: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/status")!) request.httpMethod = "DELETE" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1486,7 +1869,15 @@ public struct UsersService { } } -public struct ViewsService { +open class ViewsService { + public init() {} + + open func openView(request body: OpenViewRequest) async throws -> Void { + throw pachcaNotImplemented("Views.openView") + } +} + +public final class ViewsServiceImpl: ViewsService { let baseURL: String let headers: [String: String] let session: URLSession @@ -1495,9 +1886,10 @@ public struct ViewsService { self.baseURL = baseURL self.headers = headers self.session = session + super.init() } - public func openView(request body: OpenViewRequest) async throws -> Void { + public override func openView(request body: OpenViewRequest) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/views/open")!) request.httpMethod = "POST" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1528,6 +1920,27 @@ private final class RedirectPreventer: NSObject, URLSessionTaskDelegate { } } +public struct PachcaServices { + public var bots: BotsService? = nil + public var chats: ChatsService? = nil + public var common: CommonService? = nil + public var groupTags: GroupTagsService? = nil + public var linkPreviews: LinkPreviewsService? = nil + public var members: MembersService? = nil + public var messages: MessagesService? = nil + public var profile: ProfileService? = nil + public var reactions: ReactionsService? = nil + public var readMembers: ReadMembersService? = nil + public var search: SearchService? = nil + public var security: SecurityService? = nil + public var tasks: TasksService? = nil + public var threads: ThreadsService? = nil + public var users: UsersService? = nil + public var views: ViewsService? = nil + + public init() {} +} + public struct PachcaClient { public let bots: BotsService public let chats: ChatsService @@ -1546,23 +1959,23 @@ public struct PachcaClient { public let users: UsersService public let views: ViewsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { let headers = ["Authorization": "Bearer \(token)"] - self.bots = BotsService(baseURL: baseURL, headers: headers) - self.chats = ChatsService(baseURL: baseURL, headers: headers) - self.common = CommonService(baseURL: baseURL, headers: headers) - self.groupTags = GroupTagsService(baseURL: baseURL, headers: headers) - self.linkPreviews = LinkPreviewsService(baseURL: baseURL, headers: headers) - self.members = MembersService(baseURL: baseURL, headers: headers) - self.messages = MessagesService(baseURL: baseURL, headers: headers) - self.profile = ProfileService(baseURL: baseURL, headers: headers) - self.reactions = ReactionsService(baseURL: baseURL, headers: headers) - self.readMembers = ReadMembersService(baseURL: baseURL, headers: headers) - self.search = SearchService(baseURL: baseURL, headers: headers) - self.security = SecurityService(baseURL: baseURL, headers: headers) - self.tasks = TasksService(baseURL: baseURL, headers: headers) - self.threads = ThreadsService(baseURL: baseURL, headers: headers) - self.users = UsersService(baseURL: baseURL, headers: headers) - self.views = ViewsService(baseURL: baseURL, headers: headers) + self.bots = services.bots ?? BotsServiceImpl(baseURL: baseURL, headers: headers) + self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.groupTags = services.groupTags ?? GroupTagsServiceImpl(baseURL: baseURL, headers: headers) + self.linkPreviews = services.linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) + self.members = services.members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) + self.messages = services.messages ?? MessagesServiceImpl(baseURL: baseURL, headers: headers) + self.profile = services.profile ?? ProfileServiceImpl(baseURL: baseURL, headers: headers) + self.reactions = services.reactions ?? ReactionsServiceImpl(baseURL: baseURL, headers: headers) + self.readMembers = services.readMembers ?? ReadMembersServiceImpl(baseURL: baseURL, headers: headers) + self.search = services.search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) + self.security = services.security ?? SecurityServiceImpl(baseURL: baseURL, headers: headers) + self.tasks = services.tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) + self.threads = services.threads ?? ThreadsServiceImpl(baseURL: baseURL, headers: headers) + self.users = services.users ?? UsersServiceImpl(baseURL: baseURL, headers: headers) + self.views = services.views ?? ViewsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/sdk/typescript/src/generated/client.ts b/sdk/typescript/src/generated/client.ts index 466a0ad8..b4cae222 100644 --- a/sdk/typescript/src/generated/client.ts +++ b/sdk/typescript/src/generated/client.ts @@ -60,13 +60,25 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -class SecurityService { +export abstract class SecurityService { + async getAuditEvents(params?: GetAuditEventsParams): Promise { + throw new Error("Security.getAuditEvents is not implemented"); + } + + async getAuditEventsAll(params?: Omit): Promise { + throw new Error("Security.getAuditEventsAll is not implemented"); + } +} + +export class SecurityServiceImpl extends SecurityService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async getAuditEvents(params?: GetAuditEventsParams): Promise { + override async getAuditEvents(params?: GetAuditEventsParams): Promise { const query = new URLSearchParams(); if (params?.startTime !== undefined) query.set("start_time", params.startTime); if (params?.endTime !== undefined) query.set("end_time", params.endTime); @@ -92,7 +104,7 @@ class SecurityService { } } - async getAuditEventsAll(params?: Omit): Promise { + override async getAuditEventsAll(params?: Omit): Promise { const items: AuditEvent[] = []; let cursor: string | undefined; do { @@ -104,13 +116,33 @@ class SecurityService { } } -class BotsService { +export abstract class BotsService { + async getWebhookEvents(params?: GetWebhookEventsParams): Promise { + throw new Error("Bots.getWebhookEvents is not implemented"); + } + + async getWebhookEventsAll(params?: Omit): Promise { + throw new Error("Bots.getWebhookEventsAll is not implemented"); + } + + async updateBot(id: number, request: BotUpdateRequest): Promise { + throw new Error("Bots.updateBot is not implemented"); + } + + async deleteWebhookEvent(id: string): Promise { + throw new Error("Bots.deleteWebhookEvent is not implemented"); + } +} + +export class BotsServiceImpl extends BotsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async getWebhookEvents(params?: GetWebhookEventsParams): Promise { + override async getWebhookEvents(params?: GetWebhookEventsParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -129,7 +161,7 @@ class BotsService { } } - async getWebhookEventsAll(params?: Omit): Promise { + override async getWebhookEventsAll(params?: Omit): Promise { const items: WebhookEvent[] = []; let cursor: string | undefined; do { @@ -140,7 +172,7 @@ class BotsService { return items; } - async updateBot(id: number, request: BotUpdateRequest): Promise { + override async updateBot(id: number, request: BotUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/bots/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -157,7 +189,7 @@ class BotsService { } } - async deleteWebhookEvent(id: string): Promise { + override async deleteWebhookEvent(id: string): Promise { const response = await fetchWithRetry(`${this.baseUrl}/webhooks/events/${id}`, { method: "DELETE", headers: this.headers, @@ -173,13 +205,45 @@ class BotsService { } } -class ChatsService { +export abstract class ChatsService { + async listChats(params?: ListChatsParams): Promise { + throw new Error("Chats.listChats is not implemented"); + } + + async listChatsAll(params?: Omit): Promise { + throw new Error("Chats.listChatsAll is not implemented"); + } + + async getChat(id: number): Promise { + throw new Error("Chats.getChat is not implemented"); + } + + async createChat(request: ChatCreateRequest): Promise { + throw new Error("Chats.createChat is not implemented"); + } + + async updateChat(id: number, request: ChatUpdateRequest): Promise { + throw new Error("Chats.updateChat is not implemented"); + } + + async archiveChat(id: number): Promise { + throw new Error("Chats.archiveChat is not implemented"); + } + + async unarchiveChat(id: number): Promise { + throw new Error("Chats.unarchiveChat is not implemented"); + } +} + +export class ChatsServiceImpl extends ChatsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listChats(params?: ListChatsParams): Promise { + override async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); if (params?.availability !== undefined) query.set("availability", params.availability); @@ -203,7 +267,7 @@ class ChatsService { } } - async listChatsAll(params?: Omit): Promise { + override async listChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -214,7 +278,7 @@ class ChatsService { return items; } - async getChat(id: number): Promise { + override async getChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { headers: this.headers, }); @@ -229,7 +293,7 @@ class ChatsService { } } - async createChat(request: ChatCreateRequest): Promise { + override async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -246,7 +310,7 @@ class ChatsService { } } - async updateChat(id: number, request: ChatUpdateRequest): Promise { + override async updateChat(id: number, request: ChatUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -263,7 +327,7 @@ class ChatsService { } } - async archiveChat(id: number): Promise { + override async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -278,7 +342,7 @@ class ChatsService { } } - async unarchiveChat(id: number): Promise { + override async unarchiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/unarchive`, { method: "PUT", headers: this.headers, @@ -294,13 +358,37 @@ class ChatsService { } } -class CommonService { +export abstract class CommonService { + async downloadExport(id: number): Promise { + throw new Error("Common.downloadExport is not implemented"); + } + + async listProperties(params: ListPropertiesParams): Promise { + throw new Error("Common.listProperties is not implemented"); + } + + async requestExport(request: ExportRequest): Promise { + throw new Error("Common.requestExport is not implemented"); + } + + async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + throw new Error("Common.uploadFile is not implemented"); + } + + async getUploadParams(): Promise { + throw new Error("Common.getUploadParams is not implemented"); + } +} + +export class CommonServiceImpl extends CommonService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async downloadExport(id: number): Promise { + override async downloadExport(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/exports/${id}`, { headers: this.headers, redirect: "manual", @@ -320,7 +408,7 @@ class CommonService { } } - async listProperties(params: ListPropertiesParams): Promise { + override async listProperties(params: ListPropertiesParams): Promise { const query = new URLSearchParams(); query.set("entity_type", params.entityType); const response = await fetchWithRetry(`${this.baseUrl}/custom_properties?${query}`, { @@ -337,7 +425,7 @@ class CommonService { } } - async requestExport(request: ExportRequest): Promise { + override async requestExport(request: ExportRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/exports`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -353,7 +441,7 @@ class CommonService { } } - async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + override async uploadFile(directUrl: string, request: FileUploadRequest): Promise { const form = new FormData(); form.set("Content-Disposition", request.contentDisposition); form.set("acl", request.acl); @@ -376,7 +464,7 @@ class CommonService { } } - async getUploadParams(): Promise { + override async getUploadParams(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/uploads`, { method: "POST", headers: this.headers, @@ -393,13 +481,49 @@ class CommonService { } } -class MembersService { +export abstract class MembersService { + async listMembers(id: number, params?: ListMembersParams): Promise { + throw new Error("Members.listMembers is not implemented"); + } + + async listMembersAll(id: number, params?: Omit): Promise { + throw new Error("Members.listMembersAll is not implemented"); + } + + async addTags(id: number, groupTagIds: number[]): Promise { + throw new Error("Members.addTags is not implemented"); + } + + async addMembers(id: number, request: AddMembersRequest): Promise { + throw new Error("Members.addMembers is not implemented"); + } + + async updateMemberRole(id: number, userId: number, role: ChatMemberRole): Promise { + throw new Error("Members.updateMemberRole is not implemented"); + } + + async removeTag(id: number, tagId: number): Promise { + throw new Error("Members.removeTag is not implemented"); + } + + async leaveChat(id: number): Promise { + throw new Error("Members.leaveChat is not implemented"); + } + + async removeMember(id: number, userId: number): Promise { + throw new Error("Members.removeMember is not implemented"); + } +} + +export class MembersServiceImpl extends MembersService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listMembers(id: number, params?: ListMembersParams): Promise { + override async listMembers(id: number, params?: ListMembersParams): Promise { const query = new URLSearchParams(); if (params?.role !== undefined) query.set("role", params.role); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -419,7 +543,7 @@ class MembersService { } } - async listMembersAll(id: number, params?: Omit): Promise { + override async listMembersAll(id: number, params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -430,7 +554,7 @@ class MembersService { return items; } - async addTags(id: number, groupTagIds: number[]): Promise { + override async addTags(id: number, groupTagIds: number[]): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/group_tags`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -446,7 +570,7 @@ class MembersService { } } - async addMembers(id: number, request: AddMembersRequest): Promise { + override async addMembers(id: number, request: AddMembersRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -462,7 +586,7 @@ class MembersService { } } - async updateMemberRole(id: number, userId: number, role: ChatMemberRole): Promise { + override async updateMemberRole(id: number, userId: number, role: ChatMemberRole): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members/${userId}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -478,7 +602,7 @@ class MembersService { } } - async removeTag(id: number, tagId: number): Promise { + override async removeTag(id: number, tagId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/group_tags/${tagId}`, { method: "DELETE", headers: this.headers, @@ -493,7 +617,7 @@ class MembersService { } } - async leaveChat(id: number): Promise { + override async leaveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/leave`, { method: "DELETE", headers: this.headers, @@ -508,7 +632,7 @@ class MembersService { } } - async removeMember(id: number, userId: number): Promise { + override async removeMember(id: number, userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members/${userId}`, { method: "DELETE", headers: this.headers, @@ -524,13 +648,49 @@ class MembersService { } } -class GroupTagsService { +export abstract class GroupTagsService { + async listTags(params?: ListTagsParams): Promise { + throw new Error("Group tags.listTags is not implemented"); + } + + async listTagsAll(params?: Omit): Promise { + throw new Error("Group tags.listTagsAll is not implemented"); + } + + async getTag(id: number): Promise { + throw new Error("Group tags.getTag is not implemented"); + } + + async getTagUsers(id: number, params?: GetTagUsersParams): Promise { + throw new Error("Group tags.getTagUsers is not implemented"); + } + + async getTagUsersAll(id: number, params?: Omit): Promise { + throw new Error("Group tags.getTagUsersAll is not implemented"); + } + + async createTag(request: GroupTagRequest): Promise { + throw new Error("Group tags.createTag is not implemented"); + } + + async updateTag(id: number, request: GroupTagRequest): Promise { + throw new Error("Group tags.updateTag is not implemented"); + } + + async deleteTag(id: number): Promise { + throw new Error("Group tags.deleteTag is not implemented"); + } +} + +export class GroupTagsServiceImpl extends GroupTagsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listTags(params?: ListTagsParams): Promise { + override async listTags(params?: ListTagsParams): Promise { const query = new URLSearchParams(); if (params?.names !== undefined) query.set("names", String(params.names)); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -550,7 +710,7 @@ class GroupTagsService { } } - async listTagsAll(params?: Omit): Promise { + override async listTagsAll(params?: Omit): Promise { const items: GroupTag[] = []; let cursor: string | undefined; do { @@ -561,7 +721,7 @@ class GroupTagsService { return items; } - async getTag(id: number): Promise { + override async getTag(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { headers: this.headers, }); @@ -576,7 +736,7 @@ class GroupTagsService { } } - async getTagUsers(id: number, params?: GetTagUsersParams): Promise { + override async getTagUsers(id: number, params?: GetTagUsersParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -595,7 +755,7 @@ class GroupTagsService { } } - async getTagUsersAll(id: number, params?: Omit): Promise { + override async getTagUsersAll(id: number, params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -606,7 +766,7 @@ class GroupTagsService { return items; } - async createTag(request: GroupTagRequest): Promise { + override async createTag(request: GroupTagRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -623,7 +783,7 @@ class GroupTagsService { } } - async updateTag(id: number, request: GroupTagRequest): Promise { + override async updateTag(id: number, request: GroupTagRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -640,7 +800,7 @@ class GroupTagsService { } } - async deleteTag(id: number): Promise { + override async deleteTag(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { method: "DELETE", headers: this.headers, @@ -656,13 +816,49 @@ class GroupTagsService { } } -class MessagesService { +export abstract class MessagesService { + async listChatMessages(params: ListChatMessagesParams): Promise { + throw new Error("Messages.listChatMessages is not implemented"); + } + + async listChatMessagesAll(params: Omit): Promise { + throw new Error("Messages.listChatMessagesAll is not implemented"); + } + + async getMessage(id: number): Promise { + throw new Error("Messages.getMessage is not implemented"); + } + + async createMessage(request: MessageCreateRequest): Promise { + throw new Error("Messages.createMessage is not implemented"); + } + + async pinMessage(id: number): Promise { + throw new Error("Messages.pinMessage is not implemented"); + } + + async updateMessage(id: number, request: MessageUpdateRequest): Promise { + throw new Error("Messages.updateMessage is not implemented"); + } + + async deleteMessage(id: number): Promise { + throw new Error("Messages.deleteMessage is not implemented"); + } + + async unpinMessage(id: number): Promise { + throw new Error("Messages.unpinMessage is not implemented"); + } +} + +export class MessagesServiceImpl extends MessagesService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listChatMessages(params: ListChatMessagesParams): Promise { + override async listChatMessages(params: ListChatMessagesParams): Promise { const query = new URLSearchParams(); query.set("chat_id", String(params.chatId)); if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); @@ -682,7 +878,7 @@ class MessagesService { } } - async listChatMessagesAll(params: Omit): Promise { + override async listChatMessagesAll(params: Omit): Promise { const items: Message[] = []; let cursor: string | undefined; do { @@ -693,7 +889,7 @@ class MessagesService { return items; } - async getMessage(id: number): Promise { + override async getMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { headers: this.headers, }); @@ -708,7 +904,7 @@ class MessagesService { } } - async createMessage(request: MessageCreateRequest): Promise { + override async createMessage(request: MessageCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -725,7 +921,7 @@ class MessagesService { } } - async pinMessage(id: number): Promise { + override async pinMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/pin`, { method: "POST", headers: this.headers, @@ -740,7 +936,7 @@ class MessagesService { } } - async updateMessage(id: number, request: MessageUpdateRequest): Promise { + override async updateMessage(id: number, request: MessageUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -757,7 +953,7 @@ class MessagesService { } } - async deleteMessage(id: number): Promise { + override async deleteMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { method: "DELETE", headers: this.headers, @@ -772,7 +968,7 @@ class MessagesService { } } - async unpinMessage(id: number): Promise { + override async unpinMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/pin`, { method: "DELETE", headers: this.headers, @@ -788,13 +984,21 @@ class MessagesService { } } -class LinkPreviewsService { +export abstract class LinkPreviewsService { + async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + throw new Error("Link Previews.createLinkPreviews is not implemented"); + } +} + +export class LinkPreviewsServiceImpl extends LinkPreviewsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + override async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/link_previews`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -811,13 +1015,33 @@ class LinkPreviewsService { } } -class ReactionsService { +export abstract class ReactionsService { + async listReactions(id: number, params?: ListReactionsParams): Promise { + throw new Error("Reactions.listReactions is not implemented"); + } + + async listReactionsAll(id: number, params?: Omit): Promise { + throw new Error("Reactions.listReactionsAll is not implemented"); + } + + async addReaction(id: number, request: ReactionRequest): Promise { + throw new Error("Reactions.addReaction is not implemented"); + } + + async removeReaction(id: number, params: RemoveReactionParams): Promise { + throw new Error("Reactions.removeReaction is not implemented"); + } +} + +export class ReactionsServiceImpl extends ReactionsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listReactions(id: number, params?: ListReactionsParams): Promise { + override async listReactions(id: number, params?: ListReactionsParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -836,7 +1060,7 @@ class ReactionsService { } } - async listReactionsAll(id: number, params?: Omit): Promise { + override async listReactionsAll(id: number, params?: Omit): Promise { const items: Reaction[] = []; let cursor: string | undefined; do { @@ -847,7 +1071,7 @@ class ReactionsService { return items; } - async addReaction(id: number, request: ReactionRequest): Promise { + override async addReaction(id: number, request: ReactionRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/reactions`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -864,7 +1088,7 @@ class ReactionsService { } } - async removeReaction(id: number, params: RemoveReactionParams): Promise { + override async removeReaction(id: number, params: RemoveReactionParams): Promise { const query = new URLSearchParams(); query.set("code", params.code); if (params?.name !== undefined) query.set("name", params.name); @@ -883,13 +1107,21 @@ class ReactionsService { } } -class ReadMembersService { +export abstract class ReadMembersService { + async listReadMembers(id: number, params?: ListReadMembersParams): Promise { + throw new Error("Read members.listReadMembers is not implemented"); + } +} + +export class ReadMembersServiceImpl extends ReadMembersService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listReadMembers(id: number, params?: ListReadMembersParams): Promise { + override async listReadMembers(id: number, params?: ListReadMembersParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -909,13 +1141,25 @@ class ReadMembersService { } } -class ThreadsService { +export abstract class ThreadsService { + async getThread(id: number): Promise { + throw new Error("Threads.getThread is not implemented"); + } + + async createThread(id: number): Promise { + throw new Error("Threads.createThread is not implemented"); + } +} + +export class ThreadsServiceImpl extends ThreadsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async getThread(id: number): Promise { + override async getThread(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/threads/${id}`, { headers: this.headers, }); @@ -930,7 +1174,7 @@ class ThreadsService { } } - async createThread(id: number): Promise { + override async createThread(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/thread`, { method: "POST", headers: this.headers, @@ -947,13 +1191,37 @@ class ThreadsService { } } -class ProfileService { +export abstract class ProfileService { + async getTokenInfo(): Promise { + throw new Error("Profile.getTokenInfo is not implemented"); + } + + async getProfile(): Promise { + throw new Error("Profile.getProfile is not implemented"); + } + + async getStatus(): Promise { + throw new Error("Profile.getStatus is not implemented"); + } + + async updateStatus(request: StatusUpdateRequest): Promise { + throw new Error("Profile.updateStatus is not implemented"); + } + + async deleteStatus(): Promise { + throw new Error("Profile.deleteStatus is not implemented"); + } +} + +export class ProfileServiceImpl extends ProfileService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async getTokenInfo(): Promise { + override async getTokenInfo(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/oauth/token/info`, { headers: this.headers, }); @@ -968,7 +1236,7 @@ class ProfileService { } } - async getProfile(): Promise { + override async getProfile(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile`, { headers: this.headers, }); @@ -983,7 +1251,7 @@ class ProfileService { } } - async getStatus(): Promise { + override async getStatus(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { headers: this.headers, }); @@ -998,7 +1266,7 @@ class ProfileService { } } - async updateStatus(request: StatusUpdateRequest): Promise { + override async updateStatus(request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1015,7 +1283,7 @@ class ProfileService { } } - async deleteStatus(): Promise { + override async deleteStatus(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "DELETE", headers: this.headers, @@ -1031,13 +1299,41 @@ class ProfileService { } } -class SearchService { +export abstract class SearchService { + async searchChats(params?: SearchChatsParams): Promise { + throw new Error("Search.searchChats is not implemented"); + } + + async searchChatsAll(params?: Omit): Promise { + throw new Error("Search.searchChatsAll is not implemented"); + } + + async searchMessages(params?: SearchMessagesParams): Promise { + throw new Error("Search.searchMessages is not implemented"); + } + + async searchMessagesAll(params?: Omit): Promise { + throw new Error("Search.searchMessagesAll is not implemented"); + } + + async searchUsers(params?: SearchUsersParams): Promise { + throw new Error("Search.searchUsers is not implemented"); + } + + async searchUsersAll(params?: Omit): Promise { + throw new Error("Search.searchUsersAll is not implemented"); + } +} + +export class SearchServiceImpl extends SearchService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async searchChats(params?: SearchChatsParams): Promise { + override async searchChats(params?: SearchChatsParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1063,7 +1359,7 @@ class SearchService { } } - async searchChatsAll(params?: Omit): Promise { + override async searchChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -1074,7 +1370,7 @@ class SearchService { return items; } - async searchMessages(params?: SearchMessagesParams): Promise { + override async searchMessages(params?: SearchMessagesParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1100,7 +1396,7 @@ class SearchService { } } - async searchMessagesAll(params?: Omit): Promise { + override async searchMessagesAll(params?: Omit): Promise { const items: Message[] = []; let cursor: string | undefined; do { @@ -1111,7 +1407,7 @@ class SearchService { return items; } - async searchUsers(params?: SearchUsersParams): Promise { + override async searchUsers(params?: SearchUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1136,7 +1432,7 @@ class SearchService { } } - async searchUsersAll(params?: Omit): Promise { + override async searchUsersAll(params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -1148,13 +1444,41 @@ class SearchService { } } -class TasksService { +export abstract class TasksService { + async listTasks(params?: ListTasksParams): Promise { + throw new Error("Tasks.listTasks is not implemented"); + } + + async listTasksAll(params?: Omit): Promise { + throw new Error("Tasks.listTasksAll is not implemented"); + } + + async getTask(id: number): Promise { + throw new Error("Tasks.getTask is not implemented"); + } + + async createTask(request: TaskCreateRequest): Promise { + throw new Error("Tasks.createTask is not implemented"); + } + + async updateTask(id: number, request: TaskUpdateRequest): Promise { + throw new Error("Tasks.updateTask is not implemented"); + } + + async deleteTask(id: number): Promise { + throw new Error("Tasks.deleteTask is not implemented"); + } +} + +export class TasksServiceImpl extends TasksService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listTasks(params?: ListTasksParams): Promise { + override async listTasks(params?: ListTasksParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -1173,7 +1497,7 @@ class TasksService { } } - async listTasksAll(params?: Omit): Promise { + override async listTasksAll(params?: Omit): Promise { const items: Task[] = []; let cursor: string | undefined; do { @@ -1184,7 +1508,7 @@ class TasksService { return items; } - async getTask(id: number): Promise { + override async getTask(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { headers: this.headers, }); @@ -1199,7 +1523,7 @@ class TasksService { } } - async createTask(request: TaskCreateRequest): Promise { + override async createTask(request: TaskCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1216,7 +1540,7 @@ class TasksService { } } - async updateTask(id: number, request: TaskUpdateRequest): Promise { + override async updateTask(id: number, request: TaskUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1233,7 +1557,7 @@ class TasksService { } } - async deleteTask(id: number): Promise { + override async deleteTask(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { method: "DELETE", headers: this.headers, @@ -1249,13 +1573,53 @@ class TasksService { } } -class UsersService { +export abstract class UsersService { + async listUsers(params?: ListUsersParams): Promise { + throw new Error("Users.listUsers is not implemented"); + } + + async listUsersAll(params?: Omit): Promise { + throw new Error("Users.listUsersAll is not implemented"); + } + + async getUser(id: number): Promise { + throw new Error("Users.getUser is not implemented"); + } + + async getUserStatus(userId: number): Promise { + throw new Error("Users.getUserStatus is not implemented"); + } + + async createUser(request: UserCreateRequest): Promise { + throw new Error("Users.createUser is not implemented"); + } + + async updateUser(id: number, request: UserUpdateRequest): Promise { + throw new Error("Users.updateUser is not implemented"); + } + + async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { + throw new Error("Users.updateUserStatus is not implemented"); + } + + async deleteUser(id: number): Promise { + throw new Error("Users.deleteUser is not implemented"); + } + + async deleteUserStatus(userId: number): Promise { + throw new Error("Users.deleteUserStatus is not implemented"); + } +} + +export class UsersServiceImpl extends UsersService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async listUsers(params?: ListUsersParams): Promise { + override async listUsers(params?: ListUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1275,7 +1639,7 @@ class UsersService { } } - async listUsersAll(params?: Omit): Promise { + override async listUsersAll(params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -1286,7 +1650,7 @@ class UsersService { return items; } - async getUser(id: number): Promise { + override async getUser(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { headers: this.headers, }); @@ -1301,7 +1665,7 @@ class UsersService { } } - async getUserStatus(userId: number): Promise { + override async getUserStatus(userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { headers: this.headers, }); @@ -1316,7 +1680,7 @@ class UsersService { } } - async createUser(request: UserCreateRequest): Promise { + override async createUser(request: UserCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1333,7 +1697,7 @@ class UsersService { } } - async updateUser(id: number, request: UserUpdateRequest): Promise { + override async updateUser(id: number, request: UserUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1350,7 +1714,7 @@ class UsersService { } } - async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { + override async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1367,7 +1731,7 @@ class UsersService { } } - async deleteUser(id: number): Promise { + override async deleteUser(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { method: "DELETE", headers: this.headers, @@ -1382,7 +1746,7 @@ class UsersService { } } - async deleteUserStatus(userId: number): Promise { + override async deleteUserStatus(userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "DELETE", headers: this.headers, @@ -1398,13 +1762,21 @@ class UsersService { } } -class ViewsService { +export abstract class ViewsService { + async openView(request: OpenViewRequest): Promise { + throw new Error("Views.openView is not implemented"); + } +} + +export class ViewsServiceImpl extends ViewsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } - async openView(request: OpenViewRequest): Promise { + override async openView(request: OpenViewRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/views/open`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1421,6 +1793,25 @@ class ViewsService { } } +export interface PachcaServices { + bots?: BotsService; + chats?: ChatsService; + common?: CommonService; + groupTags?: GroupTagsService; + linkPreviews?: LinkPreviewsService; + members?: MembersService; + messages?: MessagesService; + profile?: ProfileService; + reactions?: ReactionsService; + readMembers?: ReadMembersService; + search?: SearchService; + security?: SecurityService; + tasks?: TasksService; + threads?: ThreadsService; + users?: UsersService; + views?: ViewsService; +} + export class PachcaClient { readonly bots: BotsService; readonly chats: ChatsService; @@ -1439,23 +1830,23 @@ export class PachcaClient { readonly users: UsersService; readonly views: ViewsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { const headers = { Authorization: `Bearer ${token}` }; - this.bots = new BotsService(baseUrl, headers); - this.chats = new ChatsService(baseUrl, headers); - this.common = new CommonService(baseUrl, headers); - this.groupTags = new GroupTagsService(baseUrl, headers); - this.linkPreviews = new LinkPreviewsService(baseUrl, headers); - this.members = new MembersService(baseUrl, headers); - this.messages = new MessagesService(baseUrl, headers); - this.profile = new ProfileService(baseUrl, headers); - this.reactions = new ReactionsService(baseUrl, headers); - this.readMembers = new ReadMembersService(baseUrl, headers); - this.search = new SearchService(baseUrl, headers); - this.security = new SecurityService(baseUrl, headers); - this.tasks = new TasksService(baseUrl, headers); - this.threads = new ThreadsService(baseUrl, headers); - this.users = new UsersService(baseUrl, headers); - this.views = new ViewsService(baseUrl, headers); + this.bots = services.bots ?? new BotsServiceImpl(baseUrl, headers); + this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); + this.groupTags = services.groupTags ?? new GroupTagsServiceImpl(baseUrl, headers); + this.linkPreviews = services.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); + this.members = services.members ?? new MembersServiceImpl(baseUrl, headers); + this.messages = services.messages ?? new MessagesServiceImpl(baseUrl, headers); + this.profile = services.profile ?? new ProfileServiceImpl(baseUrl, headers); + this.reactions = services.reactions ?? new ReactionsServiceImpl(baseUrl, headers); + this.readMembers = services.readMembers ?? new ReadMembersServiceImpl(baseUrl, headers); + this.search = services.search ?? new SearchServiceImpl(baseUrl, headers); + this.security = services.security ?? new SecurityServiceImpl(baseUrl, headers); + this.tasks = services.tasks ?? new TasksServiceImpl(baseUrl, headers); + this.threads = services.threads ?? new ThreadsServiceImpl(baseUrl, headers); + this.users = services.users ?? new UsersServiceImpl(baseUrl, headers); + this.views = services.views ?? new ViewsServiceImpl(baseUrl, headers); } } From 7160087e04413bf6f677de2c214f1976b24a9fa2 Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 16:22:23 +0100 Subject: [PATCH 3/8] Replace PachcaServices with direct service injection --- packages/generator/src/lang/csharp.ts | 22 ++-- packages/generator/src/lang/kotlin.ts | 19 ++-- packages/generator/src/lang/python.ts | 15 +-- packages/generator/src/lang/swift.ts | 16 +-- packages/generator/src/lang/typescript.ts | 19 +++- .../snapshots/ts/examples.json | 2 +- .../allof-sibling/snapshots/ts/examples.json | 2 +- .../circular-ref/snapshots/ts/examples.json | 2 +- .../tests/crud/snapshots/cs/Client.cs | 12 +- .../tests/crud/snapshots/kt/Client.kt | 32 +++--- .../tests/crud/snapshots/py/client.py | 10 +- .../tests/crud/snapshots/swift/Client.swift | 12 +- .../tests/crud/snapshots/ts/client.ts | 16 ++- .../tests/crud/snapshots/ts/examples.json | 2 +- .../deep-nesting/snapshots/ts/examples.json | 2 +- .../tests/edge-cases/snapshots/cs/Client.cs | 17 +-- .../tests/edge-cases/snapshots/kt/Client.kt | 26 ++--- .../tests/edge-cases/snapshots/py/client.py | 13 +-- .../edge-cases/snapshots/swift/Client.swift | 17 +-- .../tests/edge-cases/snapshots/ts/client.ts | 24 ++-- .../edge-cases/snapshots/ts/examples.json | 2 +- .../tests/enums/snapshots/ts/examples.json | 2 +- .../tests/models/snapshots/ts/examples.json | 2 +- .../multi-path-params/snapshots/cs/Client.cs | 12 +- .../multi-path-params/snapshots/kt/Client.kt | 14 +-- .../multi-path-params/snapshots/py/client.py | 10 +- .../snapshots/swift/Client.swift | 12 +- .../multi-path-params/snapshots/ts/client.ts | 16 ++- .../snapshots/ts/examples.json | 2 +- .../nullable-ref/snapshots/ts/examples.json | 2 +- .../tests/oneof/snapshots/ts/examples.json | 2 +- .../tests/patch/snapshots/cs/Client.cs | 12 +- .../tests/patch/snapshots/kt/Client.kt | 14 +-- .../tests/patch/snapshots/py/client.py | 10 +- .../tests/patch/snapshots/swift/Client.swift | 12 +- .../tests/patch/snapshots/ts/client.ts | 16 ++- .../tests/patch/snapshots/ts/examples.json | 2 +- .../tests/record/snapshots/cs/Client.cs | 12 +- .../tests/record/snapshots/kt/Client.kt | 14 +-- .../tests/record/snapshots/py/client.py | 10 +- .../tests/record/snapshots/swift/Client.swift | 12 +- .../tests/record/snapshots/ts/client.ts | 16 ++- .../tests/record/snapshots/ts/examples.json | 2 +- .../tests/redirect/snapshots/cs/Client.cs | 12 +- .../tests/redirect/snapshots/kt/Client.kt | 14 +-- .../tests/redirect/snapshots/py/client.py | 10 +- .../redirect/snapshots/swift/Client.swift | 12 +- .../tests/redirect/snapshots/ts/client.ts | 16 ++- .../tests/redirect/snapshots/ts/examples.json | 2 +- .../snapshots/ts/examples.json | 2 +- .../tests/search/snapshots/cs/Client.cs | 12 +- .../tests/search/snapshots/kt/Client.kt | 40 +++---- .../tests/search/snapshots/py/client.py | 10 +- .../tests/search/snapshots/swift/Client.swift | 12 +- .../tests/search/snapshots/ts/client.ts | 16 ++- .../tests/search/snapshots/ts/examples.json | 2 +- .../tests/unions/snapshots/ts/examples.json | 2 +- .../tests/unwrap/snapshots/cs/Client.cs | 17 +-- .../tests/unwrap/snapshots/kt/Client.kt | 20 ++-- .../tests/unwrap/snapshots/py/client.py | 13 +-- .../tests/unwrap/snapshots/swift/Client.swift | 17 +-- .../tests/unwrap/snapshots/ts/client.ts | 24 ++-- .../tests/unwrap/snapshots/ts/examples.json | 2 +- .../tests/upload/snapshots/cs/Client.cs | 12 +- .../tests/upload/snapshots/kt/Client.kt | 14 +-- .../tests/upload/snapshots/py/client.py | 10 +- .../tests/upload/snapshots/swift/Client.swift | 12 +- .../tests/upload/snapshots/ts/client.ts | 16 ++- .../tests/upload/snapshots/ts/examples.json | 2 +- sdk/csharp/generated/Client.cs | 87 ++++++--------- .../src/main/kotlin/com/pachca/Client.kt | 104 +++++++++--------- sdk/python/generated/pachca/client.py | 55 +++------ .../Pachca/GeneratedSources/Client.swift | 55 +++------ sdk/typescript/src/generated/client.ts | 72 ++++++------ sdk/typescript/src/generated/examples.json | 2 +- 75 files changed, 501 insertions(+), 683 deletions(-) diff --git a/packages/generator/src/lang/csharp.ts b/packages/generator/src/lang/csharp.ts index 265decf0..fc5c15fa 100644 --- a/packages/generator/src/lang/csharp.ts +++ b/packages/generator/src/lang/csharp.ts @@ -556,7 +556,7 @@ function emitService( const serviceName = tagToServiceName(svc.tag); const implName = serviceToImplName(serviceName); - lines.push(`public abstract class ${serviceName}`); + lines.push(`public class ${serviceName}`); lines.push('{'); for (let i = 0; i < svc.operations.length; i++) { lines.push(''); @@ -1008,30 +1008,24 @@ function emitPachcaClient( lines.push('{'); lines.push(' private readonly HttpClient _client;'); lines.push(''); - lines.push(' public sealed class Services'); - lines.push(' {'); - - // Service properties const serviceEntries = ir.services .map((svc) => ({ propName: snakeToPascal(tagToProperty(svc.tag)), + paramName: tagToProperty(svc.tag), className: tagToServiceName(svc.tag), })) .sort((a, b) => a.propName.localeCompare(b.propName)); - - for (const s of serviceEntries) { - lines.push(` public ${s.className}? ${s.propName} { get; init; }`); - } - lines.push(' }'); - lines.push(''); for (const s of serviceEntries) { lines.push(` public ${s.className} ${s.propName} { get; }`); } lines.push(''); - lines.push(` public PachcaClient(string token, string baseUrl${csDefault}, Services? services = null)`); + const constructorParams = ['string token', `string baseUrl${csDefault}`]; + for (const s of serviceEntries) { + constructorParams.push(`${s.className}? ${s.paramName} = null`); + } + lines.push(` public PachcaClient(${constructorParams.join(', ')})`); lines.push(' {'); - lines.push(' services ??= new Services();'); if (hasRedirect) { lines.push(' var handler = new SocketsHttpHandler'); @@ -1048,7 +1042,7 @@ function emitPachcaClient( lines.push(''); for (const s of serviceEntries) { - lines.push(` ${s.propName} = services.${s.propName} ?? new ${serviceToImplName(s.className)}(baseUrl, _client);`); + lines.push(` ${s.propName} = ${s.paramName} ?? new ${serviceToImplName(s.className)}(baseUrl, _client);`); } lines.push(' }'); diff --git a/packages/generator/src/lang/kotlin.ts b/packages/generator/src/lang/kotlin.ts index 316c1c7d..24d8cd1c 100644 --- a/packages/generator/src/lang/kotlin.ts +++ b/packages/generator/src/lang/kotlin.ts @@ -400,7 +400,7 @@ function emitService( const serviceName = tagToServiceName(svc.tag); const implName = serviceToImplName(serviceName); - lines.push(`abstract class ${serviceName} {`); + lines.push(`open class ${serviceName} {`); for (let i = 0; i < svc.operations.length; i++) { if (i > 0) lines.push(''); emitThrowingOperation(lines, svc.operations[i], ir); @@ -852,14 +852,15 @@ function emitPachcaClient( })) .sort((a, b) => a.propName.localeCompare(b.propName)); - lines.push('data class PachcaServices('); - for (const [index, s] of serviceEntries.entries()) { - const suffix = index < serviceEntries.length - 1 ? ',' : ''; - lines.push(` val ${s.propName}: ${s.className}? = null${suffix}`); + const constructorArgs = serviceEntries.map((s) => ` ${s.propName}: ${s.className}? = null`); + lines.push(`class PachcaClient(`); + lines.push(' token: String,'); + lines.push(` baseUrl: String${ktDefault}${constructorArgs.length > 0 ? ',' : ''}`); + for (let i = 0; i < constructorArgs.length; i++) { + const suffix = i < constructorArgs.length - 1 ? ',' : ''; + lines.push(`${constructorArgs[i]}${suffix}`); } - lines.push(')'); - lines.push(''); - lines.push(`class PachcaClient(token: String, baseUrl: String${ktDefault}, services: PachcaServices = PachcaServices()) : Closeable {`); + lines.push(') : Closeable {'); lines.push(' private val client = HttpClient {'); lines.push(' expectSuccess = false'); if (hasRedirect) { @@ -883,7 +884,7 @@ function emitPachcaClient( lines.push(''); for (const s of serviceEntries) { - lines.push(` val ${s.propName}: ${s.className} = services.${s.propName} ?: ${serviceToImplName(s.className)}(baseUrl, client)`); + lines.push(` val ${s.propName}: ${s.className} = ${s.propName} ?: ${serviceToImplName(s.className)}(baseUrl, client)`); } lines.push(''); diff --git a/packages/generator/src/lang/python.ts b/packages/generator/src/lang/python.ts index 4b9bf7cd..f54f8706 100644 --- a/packages/generator/src/lang/python.ts +++ b/packages/generator/src/lang/python.ts @@ -754,28 +754,21 @@ function generateClient(ir: IR): { content: string; needUtils: boolean } { lines.push(''); } - lines.push('@dataclass'); - lines.push('class PachcaServices:'); const serviceEntries = ir.services .map((s) => ({ prop: pyServiceProp(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); - for (const s of serviceEntries) { - lines.push(` ${s.prop}: ${s.cls} | None = None`); - } - lines.push(''); - lines.push(''); - lines.push('class PachcaClient:'); const pyDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(` def __init__(self, token: str, base_url: str${pyDefault}, services: PachcaServices | None = None) -> None:`); - lines.push(' services = services or PachcaServices()'); + const constructorArgs = serviceEntries.map((s) => `${s.prop}: ${s.cls} | None = None`); + const signature = ['self', `token: str`, `base_url: str${pyDefault}`, ...constructorArgs].join(', '); + lines.push(` def __init__(${signature}) -> None:`); lines.push(' self._client = httpx.AsyncClient('); lines.push(' base_url=base_url,'); lines.push(' headers={"Authorization": f"Bearer {token}"},'); lines.push(' transport=RetryTransport(httpx.AsyncHTTPTransport()),'); lines.push(' )'); for (const s of serviceEntries) { - lines.push(` self.${s.prop}: ${s.cls} = services.${s.prop} or ${serviceToImplName(s.cls)}(self._client)`); + lines.push(` self.${s.prop}: ${s.cls} = ${s.prop} or ${serviceToImplName(s.cls)}(self._client)`); } lines.push(''); lines.push(' async def close(self) -> None:'); diff --git a/packages/generator/src/lang/swift.ts b/packages/generator/src/lang/swift.ts index 2c9a5125..1e6c7292 100644 --- a/packages/generator/src/lang/swift.ts +++ b/packages/generator/src/lang/swift.ts @@ -563,24 +563,16 @@ function generateClient(ir: IR): string { const svcs = ir.services .map((s) => ({ prop: tagToProperty(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); - if (hasServices) { - lines.push('public struct PachcaServices {'); - for (const s of svcs) lines.push(` public var ${s.prop}: ${s.cls}? = nil`); - lines.push(''); - lines.push(' public init() {}'); - lines.push('}'); - lines.push(''); - } lines.push('public struct PachcaClient {'); for (const s of svcs) lines.push(` public let ${s.prop}: ${s.cls}`); lines.push(''); const swiftDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - if (hasServices) lines.push(` public init(token: String, baseURL: String${swiftDefault}, services: PachcaServices = PachcaServices()) {`); - else lines.push(` public init(token: String, baseURL: String${swiftDefault}) {`); + const initArgs = [`token: String`, `baseURL: String${swiftDefault}`]; + for (const s of svcs) initArgs.push(`${s.prop}: ${s.cls}? = nil`); + lines.push(` public init(${initArgs.join(', ')}) {`); lines.push(' let headers = ["Authorization": "Bearer \\(token)"]'); for (const s of svcs) { - if (hasServices) lines.push(` self.${s.prop} = services.${s.prop} ?? ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); - else lines.push(` self.${s.prop} = ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); + lines.push(` self.${s.prop} = ${s.prop} ?? ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); } lines.push(' }'); lines.push('}'); diff --git a/packages/generator/src/lang/typescript.ts b/packages/generator/src/lang/typescript.ts index 792ff8a6..f1d8cdfa 100644 --- a/packages/generator/src/lang/typescript.ts +++ b/packages/generator/src/lang/typescript.ts @@ -466,21 +466,28 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { } if (hasServices) { - lines.push('export interface PachcaServices {'); const serviceEntries = ir.services .map((s) => ({ prop: tagToProperty(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); + lines.push('export interface PachcaClientOptions {'); + lines.push(' token: string;'); + lines.push(` baseUrl${ir.baseUrl ? '?' : ''}: string;`); for (const s of serviceEntries) lines.push(` ${s.prop}?: ${s.cls};`); lines.push('}'); lines.push(''); lines.push('export class PachcaClient {'); for (const s of serviceEntries) lines.push(` readonly ${s.prop}: ${s.cls};`); lines.push(''); - const defaultUrl = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; - lines.push(` constructor(token: string, baseUrl: string${defaultUrl}, services: PachcaServices = {}) {`); + lines.push(' constructor(options: PachcaClientOptions) {'); + lines.push(' const { token } = options;'); + if (ir.baseUrl) { + lines.push(` const baseUrl = options.baseUrl ?? ${JSON.stringify(ir.baseUrl)};`); + } else { + lines.push(' const { baseUrl } = options;'); + } lines.push(' const headers = { Authorization: `Bearer ${token}` };'); for (const s of serviceEntries) { - lines.push(` this.${s.prop} = services.${s.prop} ?? new ${serviceToImplName(s.cls)}(baseUrl, headers);`); + lines.push(` this.${s.prop} = options.${s.prop} ?? new ${serviceToImplName(s.cls)}(baseUrl, headers);`); } lines.push(' }'); lines.push('}'); @@ -494,7 +501,7 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { function emitService(lines: string[], svc: IRService, ir: IR): void { const serviceName = tagToServiceName(svc.tag); const implName = serviceToImplName(serviceName); - lines.push(`export abstract class ${serviceName} {`); + lines.push(`export class ${serviceName} {`); for (let i = 0; i < svc.operations.length; i++) { emitThrowingMethod(lines, svc.operations[i], ir); if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { @@ -1125,7 +1132,7 @@ function generateExamples(ir: IR): string { const result: Record = {}; result['Client_Init'] = { - usage: 'const client = new PachcaClient("YOUR_TOKEN")', + usage: 'const client = new PachcaClient({ token: "YOUR_TOKEN" })', imports: ['PachcaClient'], }; diff --git a/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json b/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json +++ b/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/allof-sibling/snapshots/ts/examples.json b/packages/generator/tests/allof-sibling/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/allof-sibling/snapshots/ts/examples.json +++ b/packages/generator/tests/allof-sibling/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/circular-ref/snapshots/ts/examples.json b/packages/generator/tests/circular-ref/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/circular-ref/snapshots/ts/examples.json +++ b/packages/generator/tests/circular-ref/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/crud/snapshots/cs/Client.cs b/packages/generator/tests/crud/snapshots/cs/Client.cs index c4c74879..ca6aa9e1 100644 --- a/packages/generator/tests/crud/snapshots/cs/Client.cs +++ b/packages/generator/tests/crud/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class ChatsService +public class ChatsService { public virtual async System.Threading.Tasks.Task ListChatsAsync( @@ -222,21 +222,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public ChatsService? Chats { get; init; } - } - public ChatsService Chats { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", ChatsService? chats = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); + Chats = chats ?? new ChatsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/crud/snapshots/kt/Client.kt b/packages/generator/tests/crud/snapshots/kt/Client.kt index 83d6c3a6..243b6760 100644 --- a/packages/generator/tests/crud/snapshots/kt/Client.kt +++ b/packages/generator/tests/crud/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class ChatsService { +open class ChatsService { open suspend fun listChats( availability: ChatAvailability? = null, limit: Int? = null, @@ -59,11 +59,11 @@ class ChatsServiceImpl internal constructor( private val client: HttpClient, ) : ChatsService() { override suspend fun listChats( - availability: ChatAvailability? = null, - limit: Int? = null, - cursor: String? = null, - sortField: String? = null, - sortOrder: SortOrder? = null, + availability: ChatAvailability?, + limit: Int?, + cursor: String?, + sortField: String?, + sortOrder: SortOrder?, ): ListChatsResponse { val response = client.get("$baseUrl/chats") { availability?.let { parameter("availability", it.value) } @@ -80,10 +80,10 @@ class ChatsServiceImpl internal constructor( } override suspend fun listChatsAll( - availability: ChatAvailability? = null, - limit: Int? = null, - sortField: String? = null, - sortOrder: SortOrder? = null, + availability: ChatAvailability?, + limit: Int?, + sortField: String?, + sortOrder: SortOrder?, ): List { val items = mutableListOf() var cursor: String? = null @@ -153,11 +153,11 @@ class ChatsServiceImpl internal constructor( } } -data class PachcaServices( - val chats: ChatsService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + chats: ChatsService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -176,7 +176,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) + val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/crud/snapshots/py/client.py b/packages/generator/tests/crud/snapshots/py/client.py index 51382b96..8d537da9 100644 --- a/packages/generator/tests/crud/snapshots/py/client.py +++ b/packages/generator/tests/crud/snapshots/py/client.py @@ -193,20 +193,14 @@ async def delete_chat( raise deserialize(ApiError, response.json()) -@dataclass -class PachcaServices: - chats: ChatsService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", chats: ChatsService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) + self.chats: ChatsService = chats or ChatsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/crud/snapshots/swift/Client.swift b/packages/generator/tests/crud/snapshots/swift/Client.swift index e6348ca9..7bbed626 100644 --- a/packages/generator/tests/crud/snapshots/swift/Client.swift +++ b/packages/generator/tests/crud/snapshots/swift/Client.swift @@ -45,10 +45,10 @@ public final class ChatsServiceImpl: ChatsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func listChats(availability: ChatAvailability? = nil, limit: Int? = nil, cursor: String? = nil, sortField: String? = nil, sortOrder: SortOrder? = nil) async throws -> ListChatsResponse { @@ -169,17 +169,11 @@ public final class ChatsServiceImpl: ChatsService { } } -public struct PachcaServices { - public var chats: ChatsService? = nil - - public init() {} -} - public struct PachcaClient { public let chats: ChatsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", chats: ChatsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/crud/snapshots/ts/client.ts b/packages/generator/tests/crud/snapshots/ts/client.ts index 9bd1a1f0..cd4dc7b7 100644 --- a/packages/generator/tests/crud/snapshots/ts/client.ts +++ b/packages/generator/tests/crud/snapshots/ts/client.ts @@ -9,7 +9,7 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -export abstract class ChatsService { +export class ChatsService { async listChats(params?: ListChatsParams): Promise { throw new Error("Chats.listChats is not implemented"); } @@ -43,7 +43,9 @@ export class ChatsServiceImpl extends ChatsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); @@ -158,15 +160,19 @@ export class ChatsServiceImpl extends ChatsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; chats?: ChatsService; } export class PachcaClient { readonly chats: ChatsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/crud/snapshots/ts/examples.json b/packages/generator/tests/crud/snapshots/ts/examples.json index 9488231a..c9d7f4fd 100644 --- a/packages/generator/tests/crud/snapshots/ts/examples.json +++ b/packages/generator/tests/crud/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/deep-nesting/snapshots/ts/examples.json b/packages/generator/tests/deep-nesting/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/deep-nesting/snapshots/ts/examples.json +++ b/packages/generator/tests/deep-nesting/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs index 50edee50..63480f0a 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs @@ -12,7 +12,7 @@ namespace Pachca.Sdk; -public abstract class EventsService +public class EventsService { public virtual async System.Threading.Tasks.Task ListEventsAsync( @@ -91,7 +91,7 @@ public override async System.Threading.Tasks.Task PublishEventAsync( } } -public abstract class UploadsService +public class UploadsService { public virtual async System.Threading.Tasks.Task CreateUploadAsync(UploadRequest request, CancellationToken cancellationToken = default) @@ -135,24 +135,17 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public EventsService? Events { get; init; } - public UploadsService? Uploads { get; init; } - } - public EventsService Events { get; } public UploadsService Uploads { get; } - public PachcaClient(string token, string baseUrl, Services? services = null) + public PachcaClient(string token, string baseUrl, EventsService? events = null, UploadsService? uploads = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Events = services.Events ?? new EventsServiceImpl(baseUrl, _client); - Uploads = services.Uploads ?? new UploadsServiceImpl(baseUrl, _client); + Events = events ?? new EventsServiceImpl(baseUrl, _client); + Uploads = uploads ?? new UploadsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt index 08374224..a4f612ea 100644 --- a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt +++ b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt @@ -14,7 +14,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class EventsService { +open class EventsService { open suspend fun listEvents( isActive: Boolean? = null, scopes: List? = null, @@ -33,9 +33,9 @@ class EventsServiceImpl internal constructor( private val client: HttpClient, ) : EventsService() { override suspend fun listEvents( - isActive: Boolean? = null, - scopes: List? = null, - filter: EventFilter? = null, + isActive: Boolean?, + scopes: List?, + filter: EventFilter?, ): ListEventsResponse { val response = client.get("$baseUrl/events") { isActive?.let { parameter("is_active", it) } @@ -60,7 +60,7 @@ class EventsServiceImpl internal constructor( } } -abstract class UploadsService { +open class UploadsService { open suspend fun createUpload(request: UploadRequest) { throw NotImplementedError("Uploads.createUpload is not implemented") } @@ -87,12 +87,12 @@ class UploadsServiceImpl internal constructor( } } -data class PachcaServices( - val events: EventsService? = null, - val uploads: UploadsService? = null -) - -class PachcaClient(token: String, baseUrl: String, services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String, + events: EventsService? = null, + uploads: UploadsService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -111,8 +111,8 @@ class PachcaClient(token: String, baseUrl: String, services: PachcaServices = Pa } } - val events: EventsService = services.events ?: EventsServiceImpl(baseUrl, client) - val uploads: UploadsService = services.uploads ?: UploadsServiceImpl(baseUrl, client) + val events: EventsService = events ?: EventsServiceImpl(baseUrl, client) + val uploads: UploadsService = uploads ?: UploadsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/edge-cases/snapshots/py/client.py b/packages/generator/tests/edge-cases/snapshots/py/client.py index 985db341..d81348bf 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/client.py +++ b/packages/generator/tests/edge-cases/snapshots/py/client.py @@ -108,22 +108,15 @@ async def create_upload( ) -@dataclass -class PachcaServices: - events: EventsService | None = None - uploads: UploadsService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str, services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str, events: EventsService | None = None, uploads: UploadsService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.events: EventsService = services.events or EventsServiceImpl(self._client) - self.uploads: UploadsService = services.uploads or UploadsServiceImpl(self._client) + self.events: EventsService = events or EventsServiceImpl(self._client) + self.uploads: UploadsService = uploads or UploadsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift index ee947ffa..3a470c11 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift @@ -25,10 +25,10 @@ public final class EventsServiceImpl: EventsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func listEvents(isActive: Bool? = nil, scopes: [OAuthScope]? = nil, filter: EventFilter? = nil) async throws -> ListEventsResponse { @@ -81,10 +81,10 @@ public final class UploadsServiceImpl: UploadsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func createUpload(request body: UploadRequest) async throws -> Void { @@ -118,20 +118,13 @@ public final class UploadsServiceImpl: UploadsService { } } -public struct PachcaServices { - public var events: EventsService? = nil - public var uploads: UploadsService? = nil - - public init() {} -} - public struct PachcaClient { public let events: EventsService public let uploads: UploadsService - public init(token: String, baseURL: String, services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String, events: EventsService? = nil, uploads: UploadsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.events = services.events ?? EventsServiceImpl(baseURL: baseURL, headers: headers) - self.uploads = services.uploads ?? UploadsServiceImpl(baseURL: baseURL, headers: headers) + self.events = events ?? EventsServiceImpl(baseURL: baseURL, headers: headers) + self.uploads = uploads ?? UploadsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/client.ts b/packages/generator/tests/edge-cases/snapshots/ts/client.ts index 3046fdbf..9012ea7d 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/client.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/client.ts @@ -7,7 +7,7 @@ import { } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -export abstract class EventsService { +export class EventsService { async listEvents(params?: ListEventsParams): Promise { throw new Error("Events.listEvents is not implemented"); } @@ -21,7 +21,9 @@ export class EventsServiceImpl extends EventsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async listEvents(params?: ListEventsParams): Promise { const query = new URLSearchParams(); @@ -57,7 +59,7 @@ export class EventsServiceImpl extends EventsService { } } -export abstract class UploadsService { +export class UploadsService { async createUpload(request: UploadRequest): Promise { throw new Error("Uploads.createUpload is not implemented"); } @@ -67,7 +69,9 @@ export class UploadsServiceImpl extends UploadsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async createUpload(request: UploadRequest): Promise { const form = new FormData(); @@ -87,7 +91,9 @@ export class UploadsServiceImpl extends UploadsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl: string; events?: EventsService; uploads?: UploadsService; } @@ -96,9 +102,11 @@ export class PachcaClient { readonly events: EventsService; readonly uploads: UploadsService; - constructor(token: string, baseUrl: string, services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const { baseUrl } = options; const headers = { Authorization: `Bearer ${token}` }; - this.events = services.events ?? new EventsServiceImpl(baseUrl, headers); - this.uploads = services.uploads ?? new UploadsServiceImpl(baseUrl, headers); + this.events = options.events ?? new EventsServiceImpl(baseUrl, headers); + this.uploads = options.uploads ?? new UploadsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/examples.json b/packages/generator/tests/edge-cases/snapshots/ts/examples.json index 56dad943..801e9810 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/examples.json +++ b/packages/generator/tests/edge-cases/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/enums/snapshots/ts/examples.json b/packages/generator/tests/enums/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/enums/snapshots/ts/examples.json +++ b/packages/generator/tests/enums/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/models/snapshots/ts/examples.json b/packages/generator/tests/models/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/models/snapshots/ts/examples.json +++ b/packages/generator/tests/models/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs index f4e66b52..2fd3e330 100644 --- a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs +++ b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class TasksService +public class TasksService { public virtual async System.Threading.Tasks.Task GetTaskAsync( @@ -114,21 +114,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public TasksService? Tasks { get; init; } - } - public TasksService Tasks { get; } - public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", TasksService? tasks = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Tasks = services.Tasks ?? new TasksServiceImpl(baseUrl, _client); + Tasks = tasks ?? new TasksServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt index 053fbd9e..707b0544 100644 --- a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt +++ b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class TasksService { +open class TasksService { open suspend fun getTask(projectId: Int, taskId: Int): Task { throw NotImplementedError("Tasks.getTask is not implemented") } @@ -75,11 +75,11 @@ class TasksServiceImpl internal constructor( } } -data class PachcaServices( - val tasks: TasksService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.example.com/v1", + tasks: TasksService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -98,7 +98,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1" } } - val tasks: TasksService = services.tasks ?: TasksServiceImpl(baseUrl, client) + val tasks: TasksService = tasks ?: TasksServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/multi-path-params/snapshots/py/client.py b/packages/generator/tests/multi-path-params/snapshots/py/client.py index cbc3a600..36ef2ba6 100644 --- a/packages/generator/tests/multi-path-params/snapshots/py/client.py +++ b/packages/generator/tests/multi-path-params/snapshots/py/client.py @@ -90,20 +90,14 @@ async def delete_comment( ) -@dataclass -class PachcaServices: - tasks: TasksService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.example.com/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.example.com/v1", tasks: TasksService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.tasks: TasksService = services.tasks or TasksServiceImpl(self._client) + self.tasks: TasksService = tasks or TasksServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift index c6662ca2..83140f24 100644 --- a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift +++ b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift @@ -29,10 +29,10 @@ public final class TasksServiceImpl: TasksService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func getTask(projectId: Int, taskId: Int) async throws -> Task { @@ -79,17 +79,11 @@ public final class TasksServiceImpl: TasksService { } } -public struct PachcaServices { - public var tasks: TasksService? = nil - - public init() {} -} - public struct PachcaClient { public let tasks: TasksService - public init(token: String, baseURL: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.example.com/v1", tasks: TasksService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.tasks = services.tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) + self.tasks = tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts index 058a2028..4124f37a 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts +++ b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts @@ -1,7 +1,7 @@ import { Task, TaskUpdateRequest } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -export abstract class TasksService { +export class TasksService { async getTask(projectId: number, taskId: number): Promise { throw new Error("Tasks.getTask is not implemented"); } @@ -19,7 +19,9 @@ export class TasksServiceImpl extends TasksService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async getTask(projectId: number, taskId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}`, { @@ -63,15 +65,19 @@ export class TasksServiceImpl extends TasksService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; tasks?: TasksService; } export class PachcaClient { readonly tasks: TasksService; - constructor(token: string, baseUrl: string = "https://api.example.com/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.example.com/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.tasks = services.tasks ?? new TasksServiceImpl(baseUrl, headers); + this.tasks = options.tasks ?? new TasksServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/examples.json b/packages/generator/tests/multi-path-params/snapshots/ts/examples.json index fd311487..d4cb304a 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/examples.json +++ b/packages/generator/tests/multi-path-params/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/nullable-ref/snapshots/ts/examples.json b/packages/generator/tests/nullable-ref/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/nullable-ref/snapshots/ts/examples.json +++ b/packages/generator/tests/nullable-ref/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/oneof/snapshots/ts/examples.json b/packages/generator/tests/oneof/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/oneof/snapshots/ts/examples.json +++ b/packages/generator/tests/oneof/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/patch/snapshots/cs/Client.cs b/packages/generator/tests/patch/snapshots/cs/Client.cs index 69456507..34de53ba 100644 --- a/packages/generator/tests/patch/snapshots/cs/Client.cs +++ b/packages/generator/tests/patch/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class ItemsService +public class ItemsService { public virtual async System.Threading.Tasks.Task PatchItemAsync( @@ -58,21 +58,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public ItemsService? Items { get; init; } - } - public ItemsService Items { get; } - public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", ItemsService? items = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Items = services.Items ?? new ItemsServiceImpl(baseUrl, _client); + Items = items ?? new ItemsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/patch/snapshots/kt/Client.kt b/packages/generator/tests/patch/snapshots/kt/Client.kt index b35908ff..2a75133c 100644 --- a/packages/generator/tests/patch/snapshots/kt/Client.kt +++ b/packages/generator/tests/patch/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class ItemsService { +open class ItemsService { open suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { throw NotImplementedError("Items.patchItem is not implemented") } @@ -35,11 +35,11 @@ class ItemsServiceImpl internal constructor( } } -data class PachcaServices( - val items: ItemsService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.example.com/v1", + items: ItemsService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -58,7 +58,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.example.com/v1" } } - val items: ItemsService = services.items ?: ItemsServiceImpl(baseUrl, client) + val items: ItemsService = items ?: ItemsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/patch/snapshots/py/client.py b/packages/generator/tests/patch/snapshots/py/client.py index 14a279bf..f965fb9d 100644 --- a/packages/generator/tests/patch/snapshots/py/client.py +++ b/packages/generator/tests/patch/snapshots/py/client.py @@ -37,20 +37,14 @@ async def patch_item( raise deserialize(ApiError, body) -@dataclass -class PachcaServices: - items: ItemsService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.example.com/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.example.com/v1", items: ItemsService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.items: ItemsService = services.items or ItemsServiceImpl(self._client) + self.items: ItemsService = items or ItemsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/patch/snapshots/swift/Client.swift b/packages/generator/tests/patch/snapshots/swift/Client.swift index d8a2989a..ee91137a 100644 --- a/packages/generator/tests/patch/snapshots/swift/Client.swift +++ b/packages/generator/tests/patch/snapshots/swift/Client.swift @@ -21,10 +21,10 @@ public final class ItemsServiceImpl: ItemsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func patchItem(id: Int, request body: ItemPatchRequest) async throws -> Item { @@ -44,17 +44,11 @@ public final class ItemsServiceImpl: ItemsService { } } -public struct PachcaServices { - public var items: ItemsService? = nil - - public init() {} -} - public struct PachcaClient { public let items: ItemsService - public init(token: String, baseURL: String = "https://api.example.com/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.example.com/v1", items: ItemsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.items = services.items ?? ItemsServiceImpl(baseURL: baseURL, headers: headers) + self.items = items ?? ItemsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/patch/snapshots/ts/client.ts b/packages/generator/tests/patch/snapshots/ts/client.ts index c8f74033..bfcd1ef6 100644 --- a/packages/generator/tests/patch/snapshots/ts/client.ts +++ b/packages/generator/tests/patch/snapshots/ts/client.ts @@ -1,7 +1,7 @@ import { ItemPatchRequest, Item, ApiError } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -export abstract class ItemsService { +export class ItemsService { async patchItem(id: number, request: ItemPatchRequest): Promise { throw new Error("Items.patchItem is not implemented"); } @@ -11,7 +11,9 @@ export class ItemsServiceImpl extends ItemsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async patchItem(id: number, request: ItemPatchRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/items/${id}`, { @@ -29,15 +31,19 @@ export class ItemsServiceImpl extends ItemsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; items?: ItemsService; } export class PachcaClient { readonly items: ItemsService; - constructor(token: string, baseUrl: string = "https://api.example.com/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.example.com/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.items = services.items ?? new ItemsServiceImpl(baseUrl, headers); + this.items = options.items ?? new ItemsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/patch/snapshots/ts/examples.json b/packages/generator/tests/patch/snapshots/ts/examples.json index ab60f3f7..6fa4df81 100644 --- a/packages/generator/tests/patch/snapshots/ts/examples.json +++ b/packages/generator/tests/patch/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/record/snapshots/cs/Client.cs b/packages/generator/tests/record/snapshots/cs/Client.cs index 83cd4ad7..2c4649ca 100644 --- a/packages/generator/tests/record/snapshots/cs/Client.cs +++ b/packages/generator/tests/record/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class LinkPreviewsService +public class LinkPreviewsService { public virtual async System.Threading.Tasks.Task CreateLinkPreviewsAsync( @@ -60,21 +60,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public LinkPreviewsService? LinkPreviews { get; init; } - } - public LinkPreviewsService LinkPreviews { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", LinkPreviewsService? linkPreviews = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - LinkPreviews = services.LinkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); + LinkPreviews = linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/record/snapshots/kt/Client.kt b/packages/generator/tests/record/snapshots/kt/Client.kt index 5b5cf3b8..98d17fcc 100644 --- a/packages/generator/tests/record/snapshots/kt/Client.kt +++ b/packages/generator/tests/record/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class LinkPreviewsService { +open class LinkPreviewsService { open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") } @@ -36,11 +36,11 @@ class LinkPreviewsServiceImpl internal constructor( } } -data class PachcaServices( - val linkPreviews: LinkPreviewsService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + linkPreviews: LinkPreviewsService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -59,7 +59,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val linkPreviews: LinkPreviewsService = services.linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) + val linkPreviews: LinkPreviewsService = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/record/snapshots/py/client.py b/packages/generator/tests/record/snapshots/py/client.py index 061dd59d..be36aaa4 100644 --- a/packages/generator/tests/record/snapshots/py/client.py +++ b/packages/generator/tests/record/snapshots/py/client.py @@ -38,20 +38,14 @@ async def create_link_previews( raise deserialize(ApiError, response.json()) -@dataclass -class PachcaServices: - link_previews: LinkPreviewsService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", link_previews: LinkPreviewsService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.link_previews: LinkPreviewsService = services.link_previews or LinkPreviewsServiceImpl(self._client) + self.link_previews: LinkPreviewsService = link_previews or LinkPreviewsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/record/snapshots/swift/Client.swift b/packages/generator/tests/record/snapshots/swift/Client.swift index 2798597e..002abe27 100644 --- a/packages/generator/tests/record/snapshots/swift/Client.swift +++ b/packages/generator/tests/record/snapshots/swift/Client.swift @@ -21,10 +21,10 @@ public final class LinkPreviewsServiceImpl: LinkPreviewsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func createLinkPreviews(id: Int, request body: LinkPreviewsRequest) async throws -> Void { @@ -46,17 +46,11 @@ public final class LinkPreviewsServiceImpl: LinkPreviewsService { } } -public struct PachcaServices { - public var linkPreviews: LinkPreviewsService? = nil - - public init() {} -} - public struct PachcaClient { public let linkPreviews: LinkPreviewsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", linkPreviews: LinkPreviewsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.linkPreviews = services.linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) + self.linkPreviews = linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/record/snapshots/ts/client.ts b/packages/generator/tests/record/snapshots/ts/client.ts index 05104c67..9c8aef45 100644 --- a/packages/generator/tests/record/snapshots/ts/client.ts +++ b/packages/generator/tests/record/snapshots/ts/client.ts @@ -1,7 +1,7 @@ import { LinkPreviewsRequest, OAuthError, ApiError } from "./types"; import { serialize, fetchWithRetry } from "./utils"; -export abstract class LinkPreviewsService { +export class LinkPreviewsService { async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { throw new Error("Link Previews.createLinkPreviews is not implemented"); } @@ -11,7 +11,9 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/link_previews`, { @@ -30,15 +32,19 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; linkPreviews?: LinkPreviewsService; } export class PachcaClient { readonly linkPreviews: LinkPreviewsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.linkPreviews = services.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); + this.linkPreviews = options.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/record/snapshots/ts/examples.json b/packages/generator/tests/record/snapshots/ts/examples.json index 17c2ac86..4fef6b27 100644 --- a/packages/generator/tests/record/snapshots/ts/examples.json +++ b/packages/generator/tests/record/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/redirect/snapshots/cs/Client.cs b/packages/generator/tests/redirect/snapshots/cs/Client.cs index d051bc0d..e24963f1 100644 --- a/packages/generator/tests/redirect/snapshots/cs/Client.cs +++ b/packages/generator/tests/redirect/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class CommonService +public class CommonService { public virtual async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) @@ -54,16 +54,10 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public CommonService? Common { get; init; } - } - public CommonService Common { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", CommonService? common = null) { - services ??= new Services(); var handler = new SocketsHttpHandler { AllowAutoRedirect = false, @@ -72,7 +66,7 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); + Common = common ?? new CommonServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/redirect/snapshots/kt/Client.kt b/packages/generator/tests/redirect/snapshots/kt/Client.kt index 09d5b791..db8fbcb8 100644 --- a/packages/generator/tests/redirect/snapshots/kt/Client.kt +++ b/packages/generator/tests/redirect/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class CommonService { +open class CommonService { open suspend fun downloadExport(id: Int): String { throw NotImplementedError("Common.downloadExport is not implemented") } @@ -34,11 +34,11 @@ class CommonServiceImpl internal constructor( } } -data class PachcaServices( - val common: CommonService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + common: CommonService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false followRedirects = false @@ -58,7 +58,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) + val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/redirect/snapshots/py/client.py b/packages/generator/tests/redirect/snapshots/py/client.py index e8afda0d..094eb70e 100644 --- a/packages/generator/tests/redirect/snapshots/py/client.py +++ b/packages/generator/tests/redirect/snapshots/py/client.py @@ -41,20 +41,14 @@ async def download_export( raise deserialize(ApiError, response.json()) -@dataclass -class PachcaServices: - common: CommonService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", common: CommonService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.common: CommonService = services.common or CommonServiceImpl(self._client) + self.common: CommonService = common or CommonServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/redirect/snapshots/swift/Client.swift b/packages/generator/tests/redirect/snapshots/swift/Client.swift index 7b318ca1..e745edea 100644 --- a/packages/generator/tests/redirect/snapshots/swift/Client.swift +++ b/packages/generator/tests/redirect/snapshots/swift/Client.swift @@ -21,10 +21,10 @@ public final class CommonServiceImpl: CommonService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func downloadExport(id: Int) async throws -> String { @@ -59,17 +59,11 @@ private final class RedirectPreventer: NSObject, URLSessionTaskDelegate { } } -public struct PachcaServices { - public var common: CommonService? = nil - - public init() {} -} - public struct PachcaClient { public let common: CommonService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", common: CommonService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/redirect/snapshots/ts/client.ts b/packages/generator/tests/redirect/snapshots/ts/client.ts index e5b89bd4..2141a6e4 100644 --- a/packages/generator/tests/redirect/snapshots/ts/client.ts +++ b/packages/generator/tests/redirect/snapshots/ts/client.ts @@ -1,7 +1,7 @@ import { OAuthError, ApiError } from "./types"; import { fetchWithRetry } from "./utils"; -export abstract class CommonService { +export class CommonService { async downloadExport(id: number): Promise { throw new Error("Common.downloadExport is not implemented"); } @@ -11,7 +11,9 @@ export class CommonServiceImpl extends CommonService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async downloadExport(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/exports/${id}`, { @@ -34,15 +36,19 @@ export class CommonServiceImpl extends CommonService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; common?: CommonService; } export class PachcaClient { readonly common: CommonService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); + this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/redirect/snapshots/ts/examples.json b/packages/generator/tests/redirect/snapshots/ts/examples.json index 90a72057..fdc92450 100644 --- a/packages/generator/tests/redirect/snapshots/ts/examples.json +++ b/packages/generator/tests/redirect/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json b/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json +++ b/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/search/snapshots/cs/Client.cs b/packages/generator/tests/search/snapshots/cs/Client.cs index 9e933be5..8112b380 100644 --- a/packages/generator/tests/search/snapshots/cs/Client.cs +++ b/packages/generator/tests/search/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class SearchService +public class SearchService { public virtual async System.Threading.Tasks.Task SearchMessagesAsync( @@ -123,21 +123,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public SearchService? Search { get; init; } - } - public SearchService Search { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", SearchService? search = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Search = services.Search ?? new SearchServiceImpl(baseUrl, _client); + Search = search ?? new SearchServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/search/snapshots/kt/Client.kt b/packages/generator/tests/search/snapshots/kt/Client.kt index 64b4b1e5..b69306d9 100644 --- a/packages/generator/tests/search/snapshots/kt/Client.kt +++ b/packages/generator/tests/search/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class SearchService { +open class SearchService { open suspend fun searchMessages( query: String, chatIds: List? = null, @@ -46,13 +46,13 @@ class SearchServiceImpl internal constructor( ) : SearchService() { override suspend fun searchMessages( query: String, - chatIds: List? = null, - userIds: List? = null, - createdFrom: String? = null, - createdTo: String? = null, - sort: SearchSort? = null, - limit: Int? = null, - cursor: String? = null, + chatIds: List?, + userIds: List?, + createdFrom: String?, + createdTo: String?, + sort: SearchSort?, + limit: Int?, + cursor: String?, ): SearchMessagesResponse { val response = client.get("$baseUrl/search/messages") { parameter("query", query) @@ -73,12 +73,12 @@ class SearchServiceImpl internal constructor( override suspend fun searchMessagesAll( query: String, - chatIds: List? = null, - userIds: List? = null, - createdFrom: String? = null, - createdTo: String? = null, - sort: SearchSort? = null, - limit: Int? = null, + chatIds: List?, + userIds: List?, + createdFrom: String?, + createdTo: String?, + sort: SearchSort?, + limit: Int?, ): List { val items = mutableListOf() var cursor: String? = null @@ -100,11 +100,11 @@ class SearchServiceImpl internal constructor( } } -data class PachcaServices( - val search: SearchService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + search: SearchService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -123,7 +123,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val search: SearchService = services.search ?: SearchServiceImpl(baseUrl, client) + val search: SearchService = search ?: SearchServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/search/snapshots/py/client.py b/packages/generator/tests/search/snapshots/py/client.py index 3b0ecaac..8a4f11ab 100644 --- a/packages/generator/tests/search/snapshots/py/client.py +++ b/packages/generator/tests/search/snapshots/py/client.py @@ -86,20 +86,14 @@ async def search_messages_all( return items -@dataclass -class PachcaServices: - search: SearchService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", search: SearchService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.search: SearchService = services.search or SearchServiceImpl(self._client) + self.search: SearchService = search or SearchServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/search/snapshots/swift/Client.swift b/packages/generator/tests/search/snapshots/swift/Client.swift index 3a7d9da1..8bb83936 100644 --- a/packages/generator/tests/search/snapshots/swift/Client.swift +++ b/packages/generator/tests/search/snapshots/swift/Client.swift @@ -25,10 +25,10 @@ public final class SearchServiceImpl: SearchService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func searchMessages(query: String, chatIds: [Int]? = nil, userIds: [Int]? = nil, createdFrom: String? = nil, createdTo: String? = nil, sort: SearchSort? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> SearchMessagesResponse { @@ -69,17 +69,11 @@ public final class SearchServiceImpl: SearchService { } } -public struct PachcaServices { - public var search: SearchService? = nil - - public init() {} -} - public struct PachcaClient { public let search: SearchService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", search: SearchService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.search = services.search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) + self.search = search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/search/snapshots/ts/client.ts b/packages/generator/tests/search/snapshots/ts/client.ts index 6567036b..28e3fb24 100644 --- a/packages/generator/tests/search/snapshots/ts/client.ts +++ b/packages/generator/tests/search/snapshots/ts/client.ts @@ -6,7 +6,7 @@ import { } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -export abstract class SearchService { +export class SearchService { async searchMessages(params: SearchMessagesParams): Promise { throw new Error("Search.searchMessages is not implemented"); } @@ -20,7 +20,9 @@ export class SearchServiceImpl extends SearchService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async searchMessages(params: SearchMessagesParams): Promise { const query = new URLSearchParams(); @@ -62,15 +64,19 @@ export class SearchServiceImpl extends SearchService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; search?: SearchService; } export class PachcaClient { readonly search: SearchService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.search = services.search ?? new SearchServiceImpl(baseUrl, headers); + this.search = options.search ?? new SearchServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/search/snapshots/ts/examples.json b/packages/generator/tests/search/snapshots/ts/examples.json index 74cc3202..64cf6196 100644 --- a/packages/generator/tests/search/snapshots/ts/examples.json +++ b/packages/generator/tests/search/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/unions/snapshots/ts/examples.json b/packages/generator/tests/unions/snapshots/ts/examples.json index 176e6945..b42cb203 100644 --- a/packages/generator/tests/unions/snapshots/ts/examples.json +++ b/packages/generator/tests/unions/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/unwrap/snapshots/cs/Client.cs b/packages/generator/tests/unwrap/snapshots/cs/Client.cs index 0376c99a..04a360dd 100644 --- a/packages/generator/tests/unwrap/snapshots/cs/Client.cs +++ b/packages/generator/tests/unwrap/snapshots/cs/Client.cs @@ -11,7 +11,7 @@ namespace Pachca.Sdk; -public abstract class MembersService +public class MembersService { public virtual async System.Threading.Tasks.Task AddMembersAsync( @@ -57,7 +57,7 @@ public override async System.Threading.Tasks.Task AddMembersAsync( } } -public abstract class ChatsService +public class ChatsService { public virtual async System.Threading.Tasks.Task CreateChatAsync(ChatCreateRequest request, CancellationToken cancellationToken = default) @@ -122,24 +122,17 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public ChatsService? Chats { get; init; } - public MembersService? Members { get; init; } - } - public ChatsService Chats { get; } public MembersService Members { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", ChatsService? chats = null, MembersService? members = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); - Members = services.Members ?? new MembersServiceImpl(baseUrl, _client); + Chats = chats ?? new ChatsServiceImpl(baseUrl, _client); + Members = members ?? new MembersServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/unwrap/snapshots/kt/Client.kt b/packages/generator/tests/unwrap/snapshots/kt/Client.kt index b32635ed..0f2e0e2e 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/Client.kt +++ b/packages/generator/tests/unwrap/snapshots/kt/Client.kt @@ -13,7 +13,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class MembersService { +open class MembersService { open suspend fun addMembers(id: Int, memberIds: List) { throw NotImplementedError("Members.addMembers is not implemented") } @@ -36,7 +36,7 @@ class MembersServiceImpl internal constructor( } } -abstract class ChatsService { +open class ChatsService { open suspend fun createChat(request: ChatCreateRequest): Chat { throw NotImplementedError("Chats.createChat is not implemented") } @@ -72,12 +72,12 @@ class ChatsServiceImpl internal constructor( } } -data class PachcaServices( - val chats: ChatsService? = null, - val members: MembersService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + chats: ChatsService? = null, + members: MembersService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -96,8 +96,8 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) - val members: MembersService = services.members ?: MembersServiceImpl(baseUrl, client) + val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) + val members: MembersService = members ?: MembersServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/unwrap/snapshots/py/client.py b/packages/generator/tests/unwrap/snapshots/py/client.py index 61f559cb..b0d04ffb 100644 --- a/packages/generator/tests/unwrap/snapshots/py/client.py +++ b/packages/generator/tests/unwrap/snapshots/py/client.py @@ -94,22 +94,15 @@ async def archive_chat( raise deserialize(ApiError, response.json()) -@dataclass -class PachcaServices: - chats: ChatsService | None = None - members: MembersService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", chats: ChatsService | None = None, members: MembersService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) - self.members: MembersService = services.members or MembersServiceImpl(self._client) + self.chats: ChatsService = chats or ChatsServiceImpl(self._client) + self.members: MembersService = members or MembersServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/unwrap/snapshots/swift/Client.swift b/packages/generator/tests/unwrap/snapshots/swift/Client.swift index 7233b1fd..6b354cf3 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Client.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Client.swift @@ -21,10 +21,10 @@ public final class MembersServiceImpl: MembersService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func addMembers(id: Int, memberIds: [Int]) async throws -> Void { @@ -64,10 +64,10 @@ public final class ChatsServiceImpl: ChatsService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func createChat(request body: ChatCreateRequest) async throws -> Chat { @@ -105,20 +105,13 @@ public final class ChatsServiceImpl: ChatsService { } } -public struct PachcaServices { - public var chats: ChatsService? = nil - public var members: MembersService? = nil - - public init() {} -} - public struct PachcaClient { public let chats: ChatsService public let members: MembersService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", chats: ChatsService? = nil, members: MembersService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) - self.members = services.members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) + self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.members = members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/unwrap/snapshots/ts/client.ts b/packages/generator/tests/unwrap/snapshots/ts/client.ts index 675a8396..43a90403 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/client.ts +++ b/packages/generator/tests/unwrap/snapshots/ts/client.ts @@ -6,7 +6,7 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -export abstract class MembersService { +export class MembersService { async addMembers(id: number, memberIds: number[]): Promise { throw new Error("Members.addMembers is not implemented"); } @@ -16,7 +16,9 @@ export class MembersServiceImpl extends MembersService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async addMembers(id: number, memberIds: number[]): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members`, { @@ -35,7 +37,7 @@ export class MembersServiceImpl extends MembersService { } } -export abstract class ChatsService { +export class ChatsService { async createChat(request: ChatCreateRequest): Promise { throw new Error("Chats.createChat is not implemented"); } @@ -49,7 +51,9 @@ export class ChatsServiceImpl extends ChatsService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { @@ -84,7 +88,9 @@ export class ChatsServiceImpl extends ChatsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; chats?: ChatsService; members?: MembersService; } @@ -93,9 +99,11 @@ export class PachcaClient { readonly chats: ChatsService; readonly members: MembersService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); - this.members = services.members ?? new MembersServiceImpl(baseUrl, headers); + this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.members = options.members ?? new MembersServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/unwrap/snapshots/ts/examples.json b/packages/generator/tests/unwrap/snapshots/ts/examples.json index e9620e7c..bfcb96e6 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/examples.json +++ b/packages/generator/tests/unwrap/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/upload/snapshots/cs/Client.cs b/packages/generator/tests/upload/snapshots/cs/Client.cs index 9bd1c371..643299e0 100644 --- a/packages/generator/tests/upload/snapshots/cs/Client.cs +++ b/packages/generator/tests/upload/snapshots/cs/Client.cs @@ -12,7 +12,7 @@ namespace Pachca.Sdk; -public abstract class CommonService +public class CommonService { public virtual async System.Threading.Tasks.Task UploadFileAsync( @@ -94,21 +94,15 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public CommonService? Common { get; init; } - } - public CommonService Common { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", CommonService? common = null) { - services ??= new Services(); _client = new HttpClient(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); + Common = common ?? new CommonServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/packages/generator/tests/upload/snapshots/kt/Client.kt b/packages/generator/tests/upload/snapshots/kt/Client.kt index 349a0f8c..94d5be7a 100644 --- a/packages/generator/tests/upload/snapshots/kt/Client.kt +++ b/packages/generator/tests/upload/snapshots/kt/Client.kt @@ -14,7 +14,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class CommonService { +open class CommonService { open suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { throw NotImplementedError("Common.uploadFile is not implemented") } @@ -64,11 +64,11 @@ class CommonServiceImpl internal constructor( } } -data class PachcaServices( - val common: CommonService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + common: CommonService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false install(ContentNegotiation) { @@ -87,7 +87,7 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) + val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/packages/generator/tests/upload/snapshots/py/client.py b/packages/generator/tests/upload/snapshots/py/client.py index 80a7b765..9f5b34f6 100644 --- a/packages/generator/tests/upload/snapshots/py/client.py +++ b/packages/generator/tests/upload/snapshots/py/client.py @@ -71,20 +71,14 @@ async def get_upload_params( ) -@dataclass -class PachcaServices: - common: CommonService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", common: CommonService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.common: CommonService = services.common or CommonServiceImpl(self._client) + self.common: CommonService = common or CommonServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/packages/generator/tests/upload/snapshots/swift/Client.swift b/packages/generator/tests/upload/snapshots/swift/Client.swift index 43728060..defcb27b 100644 --- a/packages/generator/tests/upload/snapshots/swift/Client.swift +++ b/packages/generator/tests/upload/snapshots/swift/Client.swift @@ -25,10 +25,10 @@ public final class CommonServiceImpl: CommonService { let session: URLSession init(baseURL: String, headers: [String: String], session: URLSession = .shared) { - super.init() self.baseURL = baseURL self.headers = headers self.session = session + super.init() } public override func uploadFile(directUrl: String, request body: FileUploadRequest) async throws -> Void { @@ -86,17 +86,11 @@ public final class CommonServiceImpl: CommonService { } } -public struct PachcaServices { - public var common: CommonService? = nil - - public init() {} -} - public struct PachcaClient { public let common: CommonService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", common: CommonService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/packages/generator/tests/upload/snapshots/ts/client.ts b/packages/generator/tests/upload/snapshots/ts/client.ts index 861bf913..d14ecde4 100644 --- a/packages/generator/tests/upload/snapshots/ts/client.ts +++ b/packages/generator/tests/upload/snapshots/ts/client.ts @@ -1,7 +1,7 @@ import { FileUploadRequest, OAuthError, UploadParams } from "./types"; import { deserialize, fetchWithRetry } from "./utils"; -export abstract class CommonService { +export class CommonService { async uploadFile(directUrl: string, request: FileUploadRequest): Promise { throw new Error("Common.uploadFile is not implemented"); } @@ -15,7 +15,9 @@ export class CommonServiceImpl extends CommonService { constructor( private baseUrl: string, private headers: Record, - ) {} + ) { + super(); + } override async uploadFile(directUrl: string, request: FileUploadRequest): Promise { const form = new FormData(); @@ -59,15 +61,19 @@ export class CommonServiceImpl extends CommonService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; common?: CommonService; } export class PachcaClient { readonly common: CommonService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); + this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); } } diff --git a/packages/generator/tests/upload/snapshots/ts/examples.json b/packages/generator/tests/upload/snapshots/ts/examples.json index b1103232..cefe2863 100644 --- a/packages/generator/tests/upload/snapshots/ts/examples.json +++ b/packages/generator/tests/upload/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] diff --git a/sdk/csharp/generated/Client.cs b/sdk/csharp/generated/Client.cs index 0c6dd36f..aa4cd5fe 100644 --- a/sdk/csharp/generated/Client.cs +++ b/sdk/csharp/generated/Client.cs @@ -12,7 +12,7 @@ namespace Pachca.Sdk; -public abstract class SecurityService +public class SecurityService { public virtual async System.Threading.Tasks.Task GetAuditEventsAsync( @@ -125,7 +125,7 @@ public override async System.Threading.Tasks.Task> GetAuditEven } } -public abstract class BotsService +public class BotsService { public virtual async System.Threading.Tasks.Task GetWebhookEventsAsync( @@ -247,7 +247,7 @@ public override async System.Threading.Tasks.Task DeleteWebhookEventAsync(string } } -public abstract class ChatsService +public class ChatsService { public virtual async System.Threading.Tasks.Task ListChatsAsync( @@ -466,7 +466,7 @@ public override async System.Threading.Tasks.Task UnarchiveChatAsync(int id, Can } } -public abstract class CommonService +public class CommonService { public virtual async System.Threading.Tasks.Task DownloadExportAsync(int id, CancellationToken cancellationToken = default) @@ -612,7 +612,7 @@ public override async System.Threading.Tasks.Task GetUploadParamsA } } -public abstract class MembersService +public class MembersService { public virtual async System.Threading.Tasks.Task ListMembersAsync( @@ -862,7 +862,7 @@ public override async System.Threading.Tasks.Task RemoveMemberAsync( } } -public abstract class GroupTagsService +public class GroupTagsService { public virtual async System.Threading.Tasks.Task ListTagsAsync( @@ -1094,7 +1094,7 @@ public override async System.Threading.Tasks.Task DeleteTagAsync(int id, Cancell } } -public abstract class MessagesService +public class MessagesService { public virtual async System.Threading.Tasks.Task ListChatMessagesAsync( @@ -1316,7 +1316,7 @@ public override async System.Threading.Tasks.Task UnpinMessageAsync(int id, Canc } } -public abstract class LinkPreviewsService +public class LinkPreviewsService { public virtual async System.Threading.Tasks.Task CreateLinkPreviewsAsync( @@ -1361,7 +1361,7 @@ public override async System.Threading.Tasks.Task CreateLinkPreviewsAsync( } } -public abstract class ReactionsService +public class ReactionsService { public virtual async System.Threading.Tasks.Task ListReactionsAsync( @@ -1499,7 +1499,7 @@ public override async System.Threading.Tasks.Task RemoveReactionAsync( } } -public abstract class ReadMembersService +public class ReadMembersService { public virtual async System.Threading.Tasks.Task ListReadMembersAsync( @@ -1550,7 +1550,7 @@ public override async System.Threading.Tasks.Task ListReadMembersAsync( } } -public abstract class ThreadsService +public class ThreadsService { public virtual async System.Threading.Tasks.Task GetThreadAsync(int id, CancellationToken cancellationToken = default) @@ -1610,7 +1610,7 @@ internal ThreadsServiceImpl(string baseUrl, HttpClient client) } } -public abstract class ProfileService +public class ProfileService { public virtual async System.Threading.Tasks.Task GetTokenInfoAsync(CancellationToken cancellationToken = default) @@ -1737,7 +1737,7 @@ public override async System.Threading.Tasks.Task DeleteStatusAsync(Cancellation } } -public abstract class SearchService +public class SearchService { public virtual async System.Threading.Tasks.Task SearchChatsAsync( @@ -2038,7 +2038,7 @@ public override async System.Threading.Tasks.Task> SearchUsersAllAsyn } } -public abstract class TasksService +public class TasksService { public virtual async System.Threading.Tasks.Task ListTasksAsync( @@ -2205,7 +2205,7 @@ public override async System.Threading.Tasks.Task DeleteTaskAsync(int id, Cancel } } -public abstract class UsersService +public class UsersService { public virtual async System.Threading.Tasks.Task ListUsersAsync( @@ -2451,7 +2451,7 @@ public override async System.Threading.Tasks.Task DeleteUserStatusAsync(int user } } -public abstract class ViewsService +public class ViewsService { public virtual async System.Threading.Tasks.Task OpenViewAsync(OpenViewRequest request, CancellationToken cancellationToken = default) @@ -2494,26 +2494,6 @@ public sealed class PachcaClient : IDisposable { private readonly HttpClient _client; - public sealed class Services - { - public BotsService? Bots { get; init; } - public ChatsService? Chats { get; init; } - public CommonService? Common { get; init; } - public GroupTagsService? GroupTags { get; init; } - public LinkPreviewsService? LinkPreviews { get; init; } - public MembersService? Members { get; init; } - public MessagesService? Messages { get; init; } - public ProfileService? Profile { get; init; } - public ReactionsService? Reactions { get; init; } - public ReadMembersService? ReadMembers { get; init; } - public SearchService? Search { get; init; } - public SecurityService? Security { get; init; } - public TasksService? Tasks { get; init; } - public ThreadsService? Threads { get; init; } - public UsersService? Users { get; init; } - public ViewsService? Views { get; init; } - } - public BotsService Bots { get; } public ChatsService Chats { get; } public CommonService Common { get; } @@ -2531,9 +2511,8 @@ public sealed class Services public UsersService Users { get; } public ViewsService Views { get; } - public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", Services? services = null) + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", BotsService? bots = null, ChatsService? chats = null, CommonService? common = null, GroupTagsService? groupTags = null, LinkPreviewsService? linkPreviews = null, MembersService? members = null, MessagesService? messages = null, ProfileService? profile = null, ReactionsService? reactions = null, ReadMembersService? readMembers = null, SearchService? search = null, SecurityService? security = null, TasksService? tasks = null, ThreadsService? threads = null, UsersService? users = null, ViewsService? views = null) { - services ??= new Services(); var handler = new SocketsHttpHandler { AllowAutoRedirect = false, @@ -2542,22 +2521,22 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - Bots = services.Bots ?? new BotsServiceImpl(baseUrl, _client); - Chats = services.Chats ?? new ChatsServiceImpl(baseUrl, _client); - Common = services.Common ?? new CommonServiceImpl(baseUrl, _client); - GroupTags = services.GroupTags ?? new GroupTagsServiceImpl(baseUrl, _client); - LinkPreviews = services.LinkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); - Members = services.Members ?? new MembersServiceImpl(baseUrl, _client); - Messages = services.Messages ?? new MessagesServiceImpl(baseUrl, _client); - Profile = services.Profile ?? new ProfileServiceImpl(baseUrl, _client); - Reactions = services.Reactions ?? new ReactionsServiceImpl(baseUrl, _client); - ReadMembers = services.ReadMembers ?? new ReadMembersServiceImpl(baseUrl, _client); - Search = services.Search ?? new SearchServiceImpl(baseUrl, _client); - Security = services.Security ?? new SecurityServiceImpl(baseUrl, _client); - Tasks = services.Tasks ?? new TasksServiceImpl(baseUrl, _client); - Threads = services.Threads ?? new ThreadsServiceImpl(baseUrl, _client); - Users = services.Users ?? new UsersServiceImpl(baseUrl, _client); - Views = services.Views ?? new ViewsServiceImpl(baseUrl, _client); + Bots = bots ?? new BotsServiceImpl(baseUrl, _client); + Chats = chats ?? new ChatsServiceImpl(baseUrl, _client); + Common = common ?? new CommonServiceImpl(baseUrl, _client); + GroupTags = groupTags ?? new GroupTagsServiceImpl(baseUrl, _client); + LinkPreviews = linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); + Members = members ?? new MembersServiceImpl(baseUrl, _client); + Messages = messages ?? new MessagesServiceImpl(baseUrl, _client); + Profile = profile ?? new ProfileServiceImpl(baseUrl, _client); + Reactions = reactions ?? new ReactionsServiceImpl(baseUrl, _client); + ReadMembers = readMembers ?? new ReadMembersServiceImpl(baseUrl, _client); + Search = search ?? new SearchServiceImpl(baseUrl, _client); + Security = security ?? new SecurityServiceImpl(baseUrl, _client); + Tasks = tasks ?? new TasksServiceImpl(baseUrl, _client); + Threads = threads ?? new ThreadsServiceImpl(baseUrl, _client); + Users = users ?? new UsersServiceImpl(baseUrl, _client); + Views = views ?? new ViewsServiceImpl(baseUrl, _client); } public void Dispose() diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt index 3fb786e7..9cd1de24 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt @@ -14,7 +14,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -abstract class SecurityService { +open class SecurityService { open suspend fun getAuditEvents( startTime: String? = null, endTime: String? = null, @@ -107,7 +107,7 @@ class SecurityServiceImpl internal constructor( } } -abstract class BotsService { +open class BotsService { open suspend fun getWebhookEvents(limit: Int? = null, cursor: String? = null): GetWebhookEventsResponse { throw NotImplementedError("Bots.getWebhookEvents is not implemented") } @@ -174,7 +174,7 @@ class BotsServiceImpl internal constructor( } } -abstract class ChatsService { +open class ChatsService { open suspend fun listChats( sortId: SortOrder? = null, availability: ChatAvailability? = null, @@ -326,7 +326,7 @@ class ChatsServiceImpl internal constructor( } } -abstract class CommonService { +open class CommonService { open suspend fun downloadExport(id: Int): String { throw NotImplementedError("Common.downloadExport is not implemented") } @@ -420,7 +420,7 @@ class CommonServiceImpl internal constructor( } } -abstract class MembersService { +open class MembersService { open suspend fun listMembers( id: Int, role: ChatMemberRoleFilter? = null, @@ -577,7 +577,7 @@ class MembersServiceImpl internal constructor( } } -abstract class GroupTagsService { +open class GroupTagsService { open suspend fun listTags( names: TagNamesFilter? = null, limit: Int? = null, @@ -721,7 +721,7 @@ class GroupTagsServiceImpl internal constructor( } } -abstract class MessagesService { +open class MessagesService { open suspend fun listChatMessages( chatId: Int, sortId: SortOrder? = null, @@ -868,7 +868,7 @@ class MessagesServiceImpl internal constructor( } } -abstract class LinkPreviewsService { +open class LinkPreviewsService { open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") } @@ -891,7 +891,7 @@ class LinkPreviewsServiceImpl internal constructor( } } -abstract class ReactionsService { +open class ReactionsService { open suspend fun listReactions( id: Int, limit: Int? = null, @@ -977,7 +977,7 @@ class ReactionsServiceImpl internal constructor( } } -abstract class ReadMembersService { +open class ReadMembersService { open suspend fun listReadMembers( id: Int, limit: Int? = null, @@ -1008,7 +1008,7 @@ class ReadMembersServiceImpl internal constructor( } } -abstract class ThreadsService { +open class ThreadsService { open suspend fun getThread(id: Int): Thread { throw NotImplementedError("Threads.getThread is not implemented") } @@ -1041,7 +1041,7 @@ class ThreadsServiceImpl internal constructor( } } -abstract class ProfileService { +open class ProfileService { open suspend fun getTokenInfo(): AccessTokenInfo { throw NotImplementedError("Profile.getTokenInfo is not implemented") } @@ -1116,7 +1116,7 @@ class ProfileServiceImpl internal constructor( } } -abstract class SearchService { +open class SearchService { open suspend fun searchChats( query: String? = null, limit: Int? = null, @@ -1375,7 +1375,7 @@ class SearchServiceImpl internal constructor( } } -abstract class TasksService { +open class TasksService { open suspend fun listTasks(limit: Int? = null, cursor: String? = null): ListTasksResponse { throw NotImplementedError("Tasks.listTasks is not implemented") } @@ -1471,7 +1471,7 @@ class TasksServiceImpl internal constructor( } } -abstract class UsersService { +open class UsersService { open suspend fun listUsers( query: String? = null, limit: Int? = null, @@ -1618,7 +1618,7 @@ class UsersServiceImpl internal constructor( } } -abstract class ViewsService { +open class ViewsService { open suspend fun openView(request: OpenViewRequest) { throw NotImplementedError("Views.openView is not implemented") } @@ -1641,26 +1641,26 @@ class ViewsServiceImpl internal constructor( } } -data class PachcaServices( - val bots: BotsService? = null, - val chats: ChatsService? = null, - val common: CommonService? = null, - val groupTags: GroupTagsService? = null, - val linkPreviews: LinkPreviewsService? = null, - val members: MembersService? = null, - val messages: MessagesService? = null, - val profile: ProfileService? = null, - val reactions: ReactionsService? = null, - val readMembers: ReadMembersService? = null, - val search: SearchService? = null, - val security: SecurityService? = null, - val tasks: TasksService? = null, - val threads: ThreadsService? = null, - val users: UsersService? = null, - val views: ViewsService? = null -) - -class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) : Closeable { +class PachcaClient( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + bots: BotsService? = null, + chats: ChatsService? = null, + common: CommonService? = null, + groupTags: GroupTagsService? = null, + linkPreviews: LinkPreviewsService? = null, + members: MembersService? = null, + messages: MessagesService? = null, + profile: ProfileService? = null, + reactions: ReactionsService? = null, + readMembers: ReadMembersService? = null, + search: SearchService? = null, + security: SecurityService? = null, + tasks: TasksService? = null, + threads: ThreadsService? = null, + users: UsersService? = null, + views: ViewsService? = null +) : Closeable { private val client = HttpClient { expectSuccess = false followRedirects = false @@ -1680,22 +1680,22 @@ class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/ } } - val bots: BotsService = services.bots ?: BotsServiceImpl(baseUrl, client) - val chats: ChatsService = services.chats ?: ChatsServiceImpl(baseUrl, client) - val common: CommonService = services.common ?: CommonServiceImpl(baseUrl, client) - val groupTags: GroupTagsService = services.groupTags ?: GroupTagsServiceImpl(baseUrl, client) - val linkPreviews: LinkPreviewsService = services.linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) - val members: MembersService = services.members ?: MembersServiceImpl(baseUrl, client) - val messages: MessagesService = services.messages ?: MessagesServiceImpl(baseUrl, client) - val profile: ProfileService = services.profile ?: ProfileServiceImpl(baseUrl, client) - val reactions: ReactionsService = services.reactions ?: ReactionsServiceImpl(baseUrl, client) - val readMembers: ReadMembersService = services.readMembers ?: ReadMembersServiceImpl(baseUrl, client) - val search: SearchService = services.search ?: SearchServiceImpl(baseUrl, client) - val security: SecurityService = services.security ?: SecurityServiceImpl(baseUrl, client) - val tasks: TasksService = services.tasks ?: TasksServiceImpl(baseUrl, client) - val threads: ThreadsService = services.threads ?: ThreadsServiceImpl(baseUrl, client) - val users: UsersService = services.users ?: UsersServiceImpl(baseUrl, client) - val views: ViewsService = services.views ?: ViewsServiceImpl(baseUrl, client) + val bots: BotsService = bots ?: BotsServiceImpl(baseUrl, client) + val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) + val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) + val groupTags: GroupTagsService = groupTags ?: GroupTagsServiceImpl(baseUrl, client) + val linkPreviews: LinkPreviewsService = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) + val members: MembersService = members ?: MembersServiceImpl(baseUrl, client) + val messages: MessagesService = messages ?: MessagesServiceImpl(baseUrl, client) + val profile: ProfileService = profile ?: ProfileServiceImpl(baseUrl, client) + val reactions: ReactionsService = reactions ?: ReactionsServiceImpl(baseUrl, client) + val readMembers: ReadMembersService = readMembers ?: ReadMembersServiceImpl(baseUrl, client) + val search: SearchService = search ?: SearchServiceImpl(baseUrl, client) + val security: SecurityService = security ?: SecurityServiceImpl(baseUrl, client) + val tasks: TasksService = tasks ?: TasksServiceImpl(baseUrl, client) + val threads: ThreadsService = threads ?: ThreadsServiceImpl(baseUrl, client) + val users: UsersService = users ?: UsersServiceImpl(baseUrl, client) + val views: ViewsService = views ?: ViewsServiceImpl(baseUrl, client) override fun close() { client.close() diff --git a/sdk/python/generated/pachca/client.py b/sdk/python/generated/pachca/client.py index 0f88df80..ba21c617 100644 --- a/sdk/python/generated/pachca/client.py +++ b/sdk/python/generated/pachca/client.py @@ -2099,50 +2099,29 @@ async def open_view( raise deserialize(ApiError, response.json()) -@dataclass -class PachcaServices: - bots: BotsService | None = None - chats: ChatsService | None = None - common: CommonService | None = None - group_tags: GroupTagsService | None = None - link_previews: LinkPreviewsService | None = None - members: MembersService | None = None - messages: MessagesService | None = None - profile: ProfileService | None = None - reactions: ReactionsService | None = None - read_members: ReadMembersService | None = None - search: SearchService | None = None - security: SecurityService | None = None - tasks: TasksService | None = None - threads: ThreadsService | None = None - users: UsersService | None = None - views: ViewsService | None = None - - class PachcaClient: - def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", services: PachcaServices | None = None) -> None: - services = services or PachcaServices() + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1", bots: BotsService | None = None, chats: ChatsService | None = None, common: CommonService | None = None, group_tags: GroupTagsService | None = None, link_previews: LinkPreviewsService | None = None, members: MembersService | None = None, messages: MessagesService | None = None, profile: ProfileService | None = None, reactions: ReactionsService | None = None, read_members: ReadMembersService | None = None, search: SearchService | None = None, security: SecurityService | None = None, tasks: TasksService | None = None, threads: ThreadsService | None = None, users: UsersService | None = None, views: ViewsService | None = None) -> None: self._client = httpx.AsyncClient( base_url=base_url, headers={"Authorization": f"Bearer {token}"}, transport=RetryTransport(httpx.AsyncHTTPTransport()), ) - self.bots: BotsService = services.bots or BotsServiceImpl(self._client) - self.chats: ChatsService = services.chats or ChatsServiceImpl(self._client) - self.common: CommonService = services.common or CommonServiceImpl(self._client) - self.group_tags: GroupTagsService = services.group_tags or GroupTagsServiceImpl(self._client) - self.link_previews: LinkPreviewsService = services.link_previews or LinkPreviewsServiceImpl(self._client) - self.members: MembersService = services.members or MembersServiceImpl(self._client) - self.messages: MessagesService = services.messages or MessagesServiceImpl(self._client) - self.profile: ProfileService = services.profile or ProfileServiceImpl(self._client) - self.reactions: ReactionsService = services.reactions or ReactionsServiceImpl(self._client) - self.read_members: ReadMembersService = services.read_members or ReadMembersServiceImpl(self._client) - self.search: SearchService = services.search or SearchServiceImpl(self._client) - self.security: SecurityService = services.security or SecurityServiceImpl(self._client) - self.tasks: TasksService = services.tasks or TasksServiceImpl(self._client) - self.threads: ThreadsService = services.threads or ThreadsServiceImpl(self._client) - self.users: UsersService = services.users or UsersServiceImpl(self._client) - self.views: ViewsService = services.views or ViewsServiceImpl(self._client) + self.bots: BotsService = bots or BotsServiceImpl(self._client) + self.chats: ChatsService = chats or ChatsServiceImpl(self._client) + self.common: CommonService = common or CommonServiceImpl(self._client) + self.group_tags: GroupTagsService = group_tags or GroupTagsServiceImpl(self._client) + self.link_previews: LinkPreviewsService = link_previews or LinkPreviewsServiceImpl(self._client) + self.members: MembersService = members or MembersServiceImpl(self._client) + self.messages: MessagesService = messages or MessagesServiceImpl(self._client) + self.profile: ProfileService = profile or ProfileServiceImpl(self._client) + self.reactions: ReactionsService = reactions or ReactionsServiceImpl(self._client) + self.read_members: ReadMembersService = read_members or ReadMembersServiceImpl(self._client) + self.search: SearchService = search or SearchServiceImpl(self._client) + self.security: SecurityService = security or SecurityServiceImpl(self._client) + self.tasks: TasksService = tasks or TasksServiceImpl(self._client) + self.threads: ThreadsService = threads or ThreadsServiceImpl(self._client) + self.users: UsersService = users or UsersServiceImpl(self._client) + self.views: ViewsService = views or ViewsServiceImpl(self._client) async def close(self) -> None: await self._client.aclose() diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift index 2888c38f..d91652a4 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift @@ -1920,27 +1920,6 @@ private final class RedirectPreventer: NSObject, URLSessionTaskDelegate { } } -public struct PachcaServices { - public var bots: BotsService? = nil - public var chats: ChatsService? = nil - public var common: CommonService? = nil - public var groupTags: GroupTagsService? = nil - public var linkPreviews: LinkPreviewsService? = nil - public var members: MembersService? = nil - public var messages: MessagesService? = nil - public var profile: ProfileService? = nil - public var reactions: ReactionsService? = nil - public var readMembers: ReadMembersService? = nil - public var search: SearchService? = nil - public var security: SecurityService? = nil - public var tasks: TasksService? = nil - public var threads: ThreadsService? = nil - public var users: UsersService? = nil - public var views: ViewsService? = nil - - public init() {} -} - public struct PachcaClient { public let bots: BotsService public let chats: ChatsService @@ -1959,23 +1938,23 @@ public struct PachcaClient { public let users: UsersService public let views: ViewsService - public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", services: PachcaServices = PachcaServices()) { + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", bots: BotsService? = nil, chats: ChatsService? = nil, common: CommonService? = nil, groupTags: GroupTagsService? = nil, linkPreviews: LinkPreviewsService? = nil, members: MembersService? = nil, messages: MessagesService? = nil, profile: ProfileService? = nil, reactions: ReactionsService? = nil, readMembers: ReadMembersService? = nil, search: SearchService? = nil, security: SecurityService? = nil, tasks: TasksService? = nil, threads: ThreadsService? = nil, users: UsersService? = nil, views: ViewsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.bots = services.bots ?? BotsServiceImpl(baseURL: baseURL, headers: headers) - self.chats = services.chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) - self.common = services.common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) - self.groupTags = services.groupTags ?? GroupTagsServiceImpl(baseURL: baseURL, headers: headers) - self.linkPreviews = services.linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) - self.members = services.members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) - self.messages = services.messages ?? MessagesServiceImpl(baseURL: baseURL, headers: headers) - self.profile = services.profile ?? ProfileServiceImpl(baseURL: baseURL, headers: headers) - self.reactions = services.reactions ?? ReactionsServiceImpl(baseURL: baseURL, headers: headers) - self.readMembers = services.readMembers ?? ReadMembersServiceImpl(baseURL: baseURL, headers: headers) - self.search = services.search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) - self.security = services.security ?? SecurityServiceImpl(baseURL: baseURL, headers: headers) - self.tasks = services.tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) - self.threads = services.threads ?? ThreadsServiceImpl(baseURL: baseURL, headers: headers) - self.users = services.users ?? UsersServiceImpl(baseURL: baseURL, headers: headers) - self.views = services.views ?? ViewsServiceImpl(baseURL: baseURL, headers: headers) + self.bots = bots ?? BotsServiceImpl(baseURL: baseURL, headers: headers) + self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.groupTags = groupTags ?? GroupTagsServiceImpl(baseURL: baseURL, headers: headers) + self.linkPreviews = linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) + self.members = members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) + self.messages = messages ?? MessagesServiceImpl(baseURL: baseURL, headers: headers) + self.profile = profile ?? ProfileServiceImpl(baseURL: baseURL, headers: headers) + self.reactions = reactions ?? ReactionsServiceImpl(baseURL: baseURL, headers: headers) + self.readMembers = readMembers ?? ReadMembersServiceImpl(baseURL: baseURL, headers: headers) + self.search = search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) + self.security = security ?? SecurityServiceImpl(baseURL: baseURL, headers: headers) + self.tasks = tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) + self.threads = threads ?? ThreadsServiceImpl(baseURL: baseURL, headers: headers) + self.users = users ?? UsersServiceImpl(baseURL: baseURL, headers: headers) + self.views = views ?? ViewsServiceImpl(baseURL: baseURL, headers: headers) } } diff --git a/sdk/typescript/src/generated/client.ts b/sdk/typescript/src/generated/client.ts index b4cae222..52a609e5 100644 --- a/sdk/typescript/src/generated/client.ts +++ b/sdk/typescript/src/generated/client.ts @@ -60,7 +60,7 @@ import { } from "./types"; import { deserialize, serialize, fetchWithRetry } from "./utils"; -export abstract class SecurityService { +export class SecurityService { async getAuditEvents(params?: GetAuditEventsParams): Promise { throw new Error("Security.getAuditEvents is not implemented"); } @@ -116,7 +116,7 @@ export class SecurityServiceImpl extends SecurityService { } } -export abstract class BotsService { +export class BotsService { async getWebhookEvents(params?: GetWebhookEventsParams): Promise { throw new Error("Bots.getWebhookEvents is not implemented"); } @@ -205,7 +205,7 @@ export class BotsServiceImpl extends BotsService { } } -export abstract class ChatsService { +export class ChatsService { async listChats(params?: ListChatsParams): Promise { throw new Error("Chats.listChats is not implemented"); } @@ -358,7 +358,7 @@ export class ChatsServiceImpl extends ChatsService { } } -export abstract class CommonService { +export class CommonService { async downloadExport(id: number): Promise { throw new Error("Common.downloadExport is not implemented"); } @@ -481,7 +481,7 @@ export class CommonServiceImpl extends CommonService { } } -export abstract class MembersService { +export class MembersService { async listMembers(id: number, params?: ListMembersParams): Promise { throw new Error("Members.listMembers is not implemented"); } @@ -648,7 +648,7 @@ export class MembersServiceImpl extends MembersService { } } -export abstract class GroupTagsService { +export class GroupTagsService { async listTags(params?: ListTagsParams): Promise { throw new Error("Group tags.listTags is not implemented"); } @@ -816,7 +816,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } -export abstract class MessagesService { +export class MessagesService { async listChatMessages(params: ListChatMessagesParams): Promise { throw new Error("Messages.listChatMessages is not implemented"); } @@ -984,7 +984,7 @@ export class MessagesServiceImpl extends MessagesService { } } -export abstract class LinkPreviewsService { +export class LinkPreviewsService { async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { throw new Error("Link Previews.createLinkPreviews is not implemented"); } @@ -1015,7 +1015,7 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { } } -export abstract class ReactionsService { +export class ReactionsService { async listReactions(id: number, params?: ListReactionsParams): Promise { throw new Error("Reactions.listReactions is not implemented"); } @@ -1107,7 +1107,7 @@ export class ReactionsServiceImpl extends ReactionsService { } } -export abstract class ReadMembersService { +export class ReadMembersService { async listReadMembers(id: number, params?: ListReadMembersParams): Promise { throw new Error("Read members.listReadMembers is not implemented"); } @@ -1141,7 +1141,7 @@ export class ReadMembersServiceImpl extends ReadMembersService { } } -export abstract class ThreadsService { +export class ThreadsService { async getThread(id: number): Promise { throw new Error("Threads.getThread is not implemented"); } @@ -1191,7 +1191,7 @@ export class ThreadsServiceImpl extends ThreadsService { } } -export abstract class ProfileService { +export class ProfileService { async getTokenInfo(): Promise { throw new Error("Profile.getTokenInfo is not implemented"); } @@ -1299,7 +1299,7 @@ export class ProfileServiceImpl extends ProfileService { } } -export abstract class SearchService { +export class SearchService { async searchChats(params?: SearchChatsParams): Promise { throw new Error("Search.searchChats is not implemented"); } @@ -1444,7 +1444,7 @@ export class SearchServiceImpl extends SearchService { } } -export abstract class TasksService { +export class TasksService { async listTasks(params?: ListTasksParams): Promise { throw new Error("Tasks.listTasks is not implemented"); } @@ -1573,7 +1573,7 @@ export class TasksServiceImpl extends TasksService { } } -export abstract class UsersService { +export class UsersService { async listUsers(params?: ListUsersParams): Promise { throw new Error("Users.listUsers is not implemented"); } @@ -1762,7 +1762,7 @@ export class UsersServiceImpl extends UsersService { } } -export abstract class ViewsService { +export class ViewsService { async openView(request: OpenViewRequest): Promise { throw new Error("Views.openView is not implemented"); } @@ -1793,7 +1793,9 @@ export class ViewsServiceImpl extends ViewsService { } } -export interface PachcaServices { +export interface PachcaClientOptions { + token: string; + baseUrl?: string; bots?: BotsService; chats?: ChatsService; common?: CommonService; @@ -1830,23 +1832,25 @@ export class PachcaClient { readonly users: UsersService; readonly views: ViewsService; - constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1", services: PachcaServices = {}) { + constructor(options: PachcaClientOptions) { + const { token } = options; + const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; const headers = { Authorization: `Bearer ${token}` }; - this.bots = services.bots ?? new BotsServiceImpl(baseUrl, headers); - this.chats = services.chats ?? new ChatsServiceImpl(baseUrl, headers); - this.common = services.common ?? new CommonServiceImpl(baseUrl, headers); - this.groupTags = services.groupTags ?? new GroupTagsServiceImpl(baseUrl, headers); - this.linkPreviews = services.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); - this.members = services.members ?? new MembersServiceImpl(baseUrl, headers); - this.messages = services.messages ?? new MessagesServiceImpl(baseUrl, headers); - this.profile = services.profile ?? new ProfileServiceImpl(baseUrl, headers); - this.reactions = services.reactions ?? new ReactionsServiceImpl(baseUrl, headers); - this.readMembers = services.readMembers ?? new ReadMembersServiceImpl(baseUrl, headers); - this.search = services.search ?? new SearchServiceImpl(baseUrl, headers); - this.security = services.security ?? new SecurityServiceImpl(baseUrl, headers); - this.tasks = services.tasks ?? new TasksServiceImpl(baseUrl, headers); - this.threads = services.threads ?? new ThreadsServiceImpl(baseUrl, headers); - this.users = services.users ?? new UsersServiceImpl(baseUrl, headers); - this.views = services.views ?? new ViewsServiceImpl(baseUrl, headers); + this.bots = options.bots ?? new BotsServiceImpl(baseUrl, headers); + this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); + this.groupTags = options.groupTags ?? new GroupTagsServiceImpl(baseUrl, headers); + this.linkPreviews = options.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); + this.members = options.members ?? new MembersServiceImpl(baseUrl, headers); + this.messages = options.messages ?? new MessagesServiceImpl(baseUrl, headers); + this.profile = options.profile ?? new ProfileServiceImpl(baseUrl, headers); + this.reactions = options.reactions ?? new ReactionsServiceImpl(baseUrl, headers); + this.readMembers = options.readMembers ?? new ReadMembersServiceImpl(baseUrl, headers); + this.search = options.search ?? new SearchServiceImpl(baseUrl, headers); + this.security = options.security ?? new SecurityServiceImpl(baseUrl, headers); + this.tasks = options.tasks ?? new TasksServiceImpl(baseUrl, headers); + this.threads = options.threads ?? new ThreadsServiceImpl(baseUrl, headers); + this.users = options.users ?? new UsersServiceImpl(baseUrl, headers); + this.views = options.views ?? new ViewsServiceImpl(baseUrl, headers); } } diff --git a/sdk/typescript/src/generated/examples.json b/sdk/typescript/src/generated/examples.json index d0aef1ba..d20380f0 100644 --- a/sdk/typescript/src/generated/examples.json +++ b/sdk/typescript/src/generated/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", "imports": [ "PachcaClient" ] From 89dc977d7343c4e7d03b4935d3ddce372d4e742b Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 22:45:57 +0100 Subject: [PATCH 4/8] feat(generator): add stub factory methods to all SDK generators Enable creation of PachcaClient instances without HTTP clients for testing purposes. Each language uses its idiomatic pattern: - Kotlin: companion object with stub() function - C#: static Stub() method with private constructor - Go: NewStubPachcaClient() with functional options - Python: @classmethod stub() bypassing __init__ - Swift: static func stub() with private init Co-Authored-By: Claude Opus 4.5 --- apps/docs/public/guides/sdk/typescript.md | 4 +- apps/docs/public/llms-full.txt | 4 +- packages/generator/src/lang/csharp.ts | 25 ++- packages/generator/src/lang/go.ts | 34 ++++ packages/generator/src/lang/kotlin.ts | 91 +++++++--- packages/generator/src/lang/python.ts | 16 ++ packages/generator/src/lang/swift.ts | 32 +++- packages/generator/src/lang/typescript.ts | 9 + .../snapshots/swift/Client.swift | 10 ++ .../snapshots/swift/Client.swift | 10 ++ .../circular-ref/snapshots/swift/Client.swift | 10 ++ .../tests/crud/snapshots/cs/Client.cs | 14 +- .../tests/crud/snapshots/go/client.go | 20 +++ .../tests/crud/snapshots/kt/Client.kt | 55 +++--- .../tests/crud/snapshots/py/client.py | 10 ++ .../tests/crud/snapshots/swift/Client.swift | 14 +- .../tests/crud/snapshots/ts/client.ts | 6 + .../deep-nesting/snapshots/swift/Client.swift | 10 ++ .../tests/edge-cases/snapshots/cs/Client.cs | 15 +- .../tests/edge-cases/snapshots/go/client.go | 26 +++ .../tests/edge-cases/snapshots/kt/Client.kt | 62 ++++--- .../tests/edge-cases/snapshots/py/client.py | 12 ++ .../edge-cases/snapshots/swift/Client.swift | 18 +- .../tests/edge-cases/snapshots/ts/client.ts | 7 + .../tests/enums/snapshots/swift/Client.swift | 10 ++ .../tests/models/snapshots/swift/Client.swift | 10 ++ .../multi-path-params/snapshots/cs/Client.cs | 14 +- .../multi-path-params/snapshots/go/client.go | 20 +++ .../multi-path-params/snapshots/kt/Client.kt | 55 +++--- .../multi-path-params/snapshots/py/client.py | 10 ++ .../snapshots/swift/Client.swift | 14 +- .../multi-path-params/snapshots/ts/client.ts | 6 + .../nullable-ref/snapshots/swift/Client.swift | 10 ++ .../tests/oneof/snapshots/swift/Client.swift | 10 ++ .../tests/patch/snapshots/cs/Client.cs | 14 +- .../tests/patch/snapshots/go/client.go | 20 +++ .../tests/patch/snapshots/kt/Client.kt | 55 +++--- .../tests/patch/snapshots/py/client.py | 10 ++ .../tests/patch/snapshots/swift/Client.swift | 14 +- .../tests/patch/snapshots/ts/client.ts | 6 + .../tests/record/snapshots/cs/Client.cs | 14 +- .../tests/record/snapshots/go/client.go | 20 +++ .../tests/record/snapshots/kt/Client.kt | 55 +++--- .../tests/record/snapshots/py/client.py | 10 ++ .../tests/record/snapshots/swift/Client.swift | 14 +- .../tests/record/snapshots/ts/client.ts | 6 + .../tests/redirect/snapshots/cs/Client.cs | 14 +- .../tests/redirect/snapshots/go/client.go | 20 +++ .../tests/redirect/snapshots/kt/Client.kt | 57 +++--- .../tests/redirect/snapshots/py/client.py | 10 ++ .../redirect/snapshots/swift/Client.swift | 14 +- .../tests/redirect/snapshots/ts/client.ts | 6 + .../snapshots/swift/Client.swift | 10 ++ .../tests/search/snapshots/cs/Client.cs | 14 +- .../tests/search/snapshots/go/client.go | 20 +++ .../tests/search/snapshots/kt/Client.kt | 55 +++--- .../tests/search/snapshots/py/client.py | 10 ++ .../tests/search/snapshots/swift/Client.swift | 14 +- .../tests/search/snapshots/ts/client.ts | 6 + .../tests/unions/snapshots/swift/Client.swift | 10 ++ .../tests/unwrap/snapshots/cs/Client.cs | 15 +- .../tests/unwrap/snapshots/go/client.go | 26 +++ .../tests/unwrap/snapshots/kt/Client.kt | 62 ++++--- .../tests/unwrap/snapshots/py/client.py | 12 ++ .../tests/unwrap/snapshots/swift/Client.swift | 18 +- .../tests/unwrap/snapshots/ts/client.ts | 7 + .../tests/upload/snapshots/cs/Client.cs | 14 +- .../tests/upload/snapshots/go/client.go | 20 +++ .../tests/upload/snapshots/kt/Client.kt | 55 +++--- .../tests/upload/snapshots/py/client.py | 10 ++ .../tests/upload/snapshots/swift/Client.swift | 14 +- .../tests/upload/snapshots/ts/client.ts | 6 + sdk/csharp/generated/Client.cs | 29 +++- sdk/go/generated/client.go | 110 ++++++++++++ .../src/main/kotlin/com/pachca/Client.kt | 164 ++++++++++++------ sdk/python/generated/pachca/client.py | 40 +++++ .../Pachca/GeneratedSources/Client.swift | 74 ++++++-- sdk/typescript/src/generated/client.ts | 21 +++ 78 files changed, 1548 insertions(+), 320 deletions(-) diff --git a/apps/docs/public/guides/sdk/typescript.md b/apps/docs/public/guides/sdk/typescript.md index 3fd242a3..7d9b8f6e 100644 --- a/apps/docs/public/guides/sdk/typescript.md +++ b/apps/docs/public/guides/sdk/typescript.md @@ -23,7 +23,7 @@ npm install @pachca/sdk ```typescript import { PachcaClient } from "@pachca/sdk" -const client = new PachcaClient("YOUR_TOKEN") +const client = new PachcaClient({ token: "YOUR_TOKEN" }) ``` @@ -329,7 +329,7 @@ import { ```typescript import { Button, FileType, MessageCreateRequest, MessageCreateRequestFile, MessageCreateRequestMessage, MessageEntityType, PachcaClient, TaskCreateRequest, TaskCreateRequestCustomProperty, TaskCreateRequestTask, TaskKind } from "@pachca/sdk" -const client = new PachcaClient("YOUR_TOKEN") +const client = new PachcaClient({ token: "YOUR_TOKEN" }) // Отправка сообщения const request: MessageCreateRequest = { diff --git a/apps/docs/public/llms-full.txt b/apps/docs/public/llms-full.txt index 62f13308..75936cf2 100644 --- a/apps/docs/public/llms-full.txt +++ b/apps/docs/public/llms-full.txt @@ -1182,7 +1182,7 @@ npm install @pachca/sdk ```typescript import { PachcaClient } from "@pachca/sdk" -const client = new PachcaClient("YOUR_TOKEN") +const client = new PachcaClient({ token: "YOUR_TOKEN" }) ``` @@ -1488,7 +1488,7 @@ import { ```typescript import { Button, FileType, MessageCreateRequest, MessageCreateRequestFile, MessageCreateRequestMessage, MessageEntityType, PachcaClient, TaskCreateRequest, TaskCreateRequestCustomProperty, TaskCreateRequestTask, TaskKind } from "@pachca/sdk" -const client = new PachcaClient("YOUR_TOKEN") +const client = new PachcaClient({ token: "YOUR_TOKEN" }) // Отправка сообщения const request: MessageCreateRequest = { diff --git a/packages/generator/src/lang/csharp.ts b/packages/generator/src/lang/csharp.ts index fc5c15fa..5de512a0 100644 --- a/packages/generator/src/lang/csharp.ts +++ b/packages/generator/src/lang/csharp.ts @@ -1006,7 +1006,7 @@ function emitPachcaClient( lines.push('public sealed class PachcaClient : IDisposable'); lines.push('{'); - lines.push(' private readonly HttpClient _client;'); + lines.push(' private readonly HttpClient? _client;'); lines.push(''); const serviceEntries = ir.services .map((svc) => ({ @@ -1019,6 +1019,17 @@ function emitPachcaClient( lines.push(` public ${s.className} ${s.propName} { get; }`); } + // Private constructor taking only services + lines.push(''); + const privateParams = serviceEntries.map((s) => `${s.className} ${s.paramName}`); + lines.push(` private PachcaClient(${privateParams.join(', ')})`); + lines.push(' {'); + for (const s of serviceEntries) { + lines.push(` ${s.propName} = ${s.paramName};`); + } + lines.push(' }'); + + // Public constructor with token, baseUrl, and optional service overrides lines.push(''); const constructorParams = ['string token', `string baseUrl${csDefault}`]; for (const s of serviceEntries) { @@ -1046,10 +1057,20 @@ function emitPachcaClient( } lines.push(' }'); + + // Static Stub() factory method + lines.push(''); + const stubParams = serviceEntries.map((s) => `${s.className}? ${s.paramName} = null`); + lines.push(` public static PachcaClient Stub(${stubParams.join(', ')})`); + lines.push(' {'); + const stubArgs = serviceEntries.map((s) => `${s.paramName} ?? new ${s.className}()`); + lines.push(` return new PachcaClient(${stubArgs.join(', ')});`); + lines.push(' }'); + lines.push(''); lines.push(' public void Dispose()'); lines.push(' {'); - lines.push(' _client.Dispose();'); + lines.push(' _client?.Dispose();'); lines.push(' GC.SuppressFinalize(this);'); lines.push(' }'); lines.push('}'); diff --git a/packages/generator/src/lang/go.ts b/packages/generator/src/lang/go.ts index 19046a35..e212bde5 100644 --- a/packages/generator/src/lang/go.ts +++ b/packages/generator/src/lang/go.ts @@ -806,6 +806,15 @@ function generateClient(ir: IR): string { lines.push(''); lines.push('type ClientOption func(*clientConfig)'); lines.push(''); + + // stubClientConfig struct + lines.push('type stubClientConfig struct {'); + for (const f of fields) lines.push(`\t${f.f.charAt(0).toLowerCase() + f.f.slice(1)} ${f.cls}`); + lines.push('}'); + lines.push(''); + lines.push('type StubClientOption func(*stubClientConfig)'); + lines.push(''); + if (ir.baseUrl) { lines.push(`const DefaultBaseURL = ${JSON.stringify(ir.baseUrl)}`); lines.push(''); @@ -820,6 +829,15 @@ function generateClient(ir: IR): string { lines.push('}'); lines.push(''); } + + // WithStub* option functions + for (const f of fields) { + lines.push(`func WithStub${f.f}(service ${f.cls}) StubClientOption {`); + lines.push(`\treturn func(cfg *stubClientConfig) { cfg.${f.f.charAt(0).toLowerCase() + f.f.slice(1)} = service }`); + lines.push('}'); + lines.push(''); + } + lines.push('func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient {'); if (ir.baseUrl) { lines.push(`\tcfg := clientConfig{baseURL: DefaultBaseURL}`); @@ -847,6 +865,22 @@ function generateClient(ir: IR): string { lines.push('\t}'); lines.push('}'); lines.push(''); + + // NewStubPachcaClient function + lines.push('func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient {'); + lines.push('\tcfg := stubClientConfig{}'); + lines.push('\tfor _, opt := range opts {'); + lines.push('\t\topt(&cfg)'); + lines.push('\t}'); + lines.push('\treturn &PachcaClient{'); + for (const f of fields) { + const cfgField = `cfg.${f.f.charAt(0).toLowerCase() + f.f.slice(1)}`; + const stub = `&${serviceToStubName(f.cls)}{}`; + lines.push(`\t\t${f.f.padEnd(maxField)}: func() ${f.cls} { if ${cfgField} != nil { return ${cfgField} }; return ${stub} }(),`); + } + lines.push('\t}'); + lines.push('}'); + lines.push(''); return lines.join('\n'); } diff --git a/packages/generator/src/lang/kotlin.ts b/packages/generator/src/lang/kotlin.ts index 24d8cd1c..a0b24e86 100644 --- a/packages/generator/src/lang/kotlin.ts +++ b/packages/generator/src/lang/kotlin.ts @@ -852,44 +852,79 @@ function emitPachcaClient( })) .sort((a, b) => a.propName.localeCompare(b.propName)); - const constructorArgs = serviceEntries.map((s) => ` ${s.propName}: ${s.className}? = null`); - lines.push(`class PachcaClient(`); - lines.push(' token: String,'); - lines.push(` baseUrl: String${ktDefault}${constructorArgs.length > 0 ? ',' : ''}`); - for (let i = 0; i < constructorArgs.length; i++) { - const suffix = i < constructorArgs.length - 1 ? ',' : ''; - lines.push(`${constructorArgs[i]}${suffix}`); + // Private constructor taking nullable client + all services + lines.push('class PachcaClient private constructor('); + lines.push(' private val client: HttpClient?,'); + for (let i = 0; i < serviceEntries.length; i++) { + const s = serviceEntries[i]; + const suffix = i < serviceEntries.length - 1 ? ',' : ''; + lines.push(` val ${s.propName}: ${s.className}${suffix}`); } lines.push(') : Closeable {'); - lines.push(' private val client = HttpClient {'); - lines.push(' expectSuccess = false'); - if (hasRedirect) { - lines.push(' followRedirects = false'); + lines.push(''); + lines.push(' companion object {'); + + // operator fun invoke - creates HttpClient and real services + const invokeArgs = [`token: String`, `baseUrl: String${ktDefault}`]; + for (const s of serviceEntries) { + invokeArgs.push(`${s.propName}: ${s.className}? = null`); } - lines.push(' install(ContentNegotiation) {'); - lines.push(' json(Json { explicitNulls = false })'); - lines.push(' }'); - lines.push(' install(HttpRequestRetry) {'); - lines.push(' retryOnServerErrors(maxRetries = 3)'); - lines.push(' retryIf { _, response -> response.status.value == 429 }'); - lines.push(' delayMillis { retry ->'); - lines.push(' val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull()'); - lines.push(' if (retryAfter != null) retryAfter * 1000L else retry * 1000L'); - lines.push(' }'); - lines.push(' }'); - lines.push(' defaultRequest {'); - lines.push(' bearerAuth(token)'); + lines.push(' operator fun invoke('); + for (let i = 0; i < invokeArgs.length; i++) { + const suffix = i < invokeArgs.length - 1 ? ',' : ''; + lines.push(` ${invokeArgs[i]}${suffix}`); + } + lines.push(' ): PachcaClient {'); + lines.push(' val client = createClient(token)'); + lines.push(' return PachcaClient('); + lines.push(' client = client,'); + for (let i = 0; i < serviceEntries.length; i++) { + const s = serviceEntries[i]; + const suffix = i < serviceEntries.length - 1 ? ',' : ''; + lines.push(` ${s.propName} = ${s.propName} ?: ${serviceToImplName(s.className)}(baseUrl, client)${suffix}`); + } + lines.push(' )'); lines.push(' }'); - lines.push(' }'); lines.push(''); - for (const s of serviceEntries) { - lines.push(` val ${s.propName}: ${s.className} = ${s.propName} ?: ${serviceToImplName(s.className)}(baseUrl, client)`); + // fun stub - creates client without HttpClient + lines.push(' fun stub('); + for (let i = 0; i < serviceEntries.length; i++) { + const s = serviceEntries[i]; + const suffix = i < serviceEntries.length - 1 ? ',' : ''; + lines.push(` ${s.propName}: ${s.className} = ${s.className}()${suffix}`); + } + lines.push(' ): PachcaClient = PachcaClient('); + lines.push(' client = null,'); + for (let i = 0; i < serviceEntries.length; i++) { + const s = serviceEntries[i]; + const suffix = i < serviceEntries.length - 1 ? ',' : ''; + lines.push(` ${s.propName} = ${s.propName}${suffix}`); } + lines.push(' )'); + lines.push(''); + // private fun createClient + lines.push(' private fun createClient(token: String): HttpClient = HttpClient {'); + lines.push(' expectSuccess = false'); + if (hasRedirect) { + lines.push(' followRedirects = false'); + } + lines.push(' install(ContentNegotiation) { json(Json { explicitNulls = false }) }'); + lines.push(' install(HttpRequestRetry) {'); + lines.push(' retryOnServerErrors(maxRetries = 3)'); + lines.push(' retryIf { _, response -> response.status.value == 429 }'); + lines.push(' delayMillis { retry ->'); + lines.push(' val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull()'); + lines.push(' if (retryAfter != null) retryAfter * 1000L else retry * 1000L'); + lines.push(' }'); + lines.push(' }'); + lines.push(' defaultRequest { bearerAuth(token) }'); + lines.push(' }'); + lines.push(' }'); lines.push(''); lines.push(' override fun close() {'); - lines.push(' client.close()'); + lines.push(' client?.close()'); lines.push(' }'); lines.push('}'); } diff --git a/packages/generator/src/lang/python.ts b/packages/generator/src/lang/python.ts index f54f8706..ad7b3577 100644 --- a/packages/generator/src/lang/python.ts +++ b/packages/generator/src/lang/python.ts @@ -773,6 +773,22 @@ function generateClient(ir: IR): { content: string; needUtils: boolean } { lines.push(''); lines.push(' async def close(self) -> None:'); lines.push(' await self._client.aclose()'); + lines.push(''); + + // stub classmethod + lines.push(' @classmethod'); + lines.push(' def stub('); + lines.push(' cls,'); + for (const s of serviceEntries) { + lines.push(` ${s.prop}: ${s.cls} | None = None,`); + } + lines.push(' ) -> "PachcaClient":'); + lines.push(' self = cls.__new__(cls)'); + lines.push(' self._client = None'); + for (const s of serviceEntries) { + lines.push(` self.${s.prop} = ${s.prop} or ${s.cls}()`); + } + lines.push(' return self'); while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop(); lines.push(''); diff --git a/packages/generator/src/lang/swift.ts b/packages/generator/src/lang/swift.ts index 1e6c7292..920a7cda 100644 --- a/packages/generator/src/lang/swift.ts +++ b/packages/generator/src/lang/swift.ts @@ -566,14 +566,42 @@ function generateClient(ir: IR): string { lines.push('public struct PachcaClient {'); for (const s of svcs) lines.push(` public let ${s.prop}: ${s.cls}`); lines.push(''); + + // Private init taking only services (for stub) + const privateInitArgs = svcs.map((s) => `${s.prop}: ${s.cls}`); + lines.push(` private init(${privateInitArgs.join(', ')}) {`); + for (const s of svcs) { + lines.push(` self.${s.prop} = ${s.prop}`); + } + lines.push(' }'); + lines.push(''); + + // Public init with token/baseURL delegating to private init const swiftDefault = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; const initArgs = [`token: String`, `baseURL: String${swiftDefault}`]; for (const s of svcs) initArgs.push(`${s.prop}: ${s.cls}? = nil`); lines.push(` public init(${initArgs.join(', ')}) {`); lines.push(' let headers = ["Authorization": "Bearer \\(token)"]'); - for (const s of svcs) { - lines.push(` self.${s.prop} = ${s.prop} ?? ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)`); + lines.push(' self.init('); + for (let i = 0; i < svcs.length; i++) { + const s = svcs[i]; + const suffix = i < svcs.length - 1 ? ',' : ''; + lines.push(` ${s.prop}: ${s.prop} ?? ${serviceToImplName(s.cls)}(baseURL: baseURL, headers: headers)${suffix}`); + } + lines.push(' )'); + lines.push(' }'); + lines.push(''); + + // Static stub() factory + const stubArgs = svcs.map((s) => `${s.prop}: ${s.cls} = ${s.cls}()`); + lines.push(` public static func stub(${stubArgs.join(', ')}) -> PachcaClient {`); + lines.push(' PachcaClient('); + for (let i = 0; i < svcs.length; i++) { + const s = svcs[i]; + const suffix = i < svcs.length - 1 ? ',' : ''; + lines.push(` ${s.prop}: ${s.prop}${suffix}`); } + lines.push(' )'); lines.push(' }'); lines.push('}'); lines.push(''); diff --git a/packages/generator/src/lang/typescript.ts b/packages/generator/src/lang/typescript.ts index f1d8cdfa..cbfa32b6 100644 --- a/packages/generator/src/lang/typescript.ts +++ b/packages/generator/src/lang/typescript.ts @@ -490,6 +490,15 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { lines.push(` this.${s.prop} = options.${s.prop} ?? new ${serviceToImplName(s.cls)}(baseUrl, headers);`); } lines.push(' }'); + lines.push(''); + lines.push(' static stub(options: Partial = {}): PachcaClient {'); + const defaultBaseUrl = ir.baseUrl ? JSON.stringify(ir.baseUrl) : '""'; + lines.push(` return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? ${defaultBaseUrl},`); + for (const s of serviceEntries) { + lines.push(` ${s.prop}: options.${s.prop} ?? new ${s.cls}(),`); + } + lines.push(' });'); + lines.push(' }'); lines.push('}'); } diff --git a/packages/generator/tests/additional-props-bool/snapshots/swift/Client.swift b/packages/generator/tests/additional-props-bool/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/swift/Client.swift +++ b/packages/generator/tests/additional-props-bool/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/allof-sibling/snapshots/swift/Client.swift b/packages/generator/tests/allof-sibling/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/allof-sibling/snapshots/swift/Client.swift +++ b/packages/generator/tests/allof-sibling/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/circular-ref/snapshots/swift/Client.swift b/packages/generator/tests/circular-ref/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/circular-ref/snapshots/swift/Client.swift +++ b/packages/generator/tests/circular-ref/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/crud/snapshots/cs/Client.cs b/packages/generator/tests/crud/snapshots/cs/Client.cs index ca6aa9e1..9a3ce004 100644 --- a/packages/generator/tests/crud/snapshots/cs/Client.cs +++ b/packages/generator/tests/crud/snapshots/cs/Client.cs @@ -220,10 +220,15 @@ public override async System.Threading.Tasks.Task DeleteChatAsync(int id, Cancel public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public ChatsService Chats { get; } + private PachcaClient(ChatsService chats) + { + Chats = chats; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", ChatsService? chats = null) { _client = new HttpClient(); @@ -233,9 +238,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Chats = chats ?? new ChatsServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(ChatsService? chats = null) + { + return new PachcaClient(chats ?? new ChatsService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/crud/snapshots/go/client.go b/packages/generator/tests/crud/snapshots/go/client.go index 0cad340f..70d059c7 100644 --- a/packages/generator/tests/crud/snapshots/go/client.go +++ b/packages/generator/tests/crud/snapshots/go/client.go @@ -320,6 +320,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + chats ChatsService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -330,6 +336,10 @@ func WithChats(service ChatsService) ClientOption { return func(cfg *clientConfig) { cfg.chats = service } } +func WithStubChats(service ChatsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.chats = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -342,3 +352,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Chats: func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Chats: func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceStub{} }(), + } +} diff --git a/packages/generator/tests/crud/snapshots/kt/Client.kt b/packages/generator/tests/crud/snapshots/kt/Client.kt index 243b6760..22d8fe9c 100644 --- a/packages/generator/tests/crud/snapshots/kt/Client.kt +++ b/packages/generator/tests/crud/snapshots/kt/Client.kt @@ -153,32 +153,47 @@ class ChatsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - chats: ChatsService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val chats: ChatsService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + chats: ChatsService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + chats = chats ?: ChatsServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + chats: ChatsService = ChatsService() + ): PachcaClient = PachcaClient( + client = null, + chats = chats + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/crud/snapshots/py/client.py b/packages/generator/tests/crud/snapshots/py/client.py index 8d537da9..066d4f69 100644 --- a/packages/generator/tests/crud/snapshots/py/client.py +++ b/packages/generator/tests/crud/snapshots/py/client.py @@ -204,3 +204,13 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + chats: ChatsService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.chats = chats or ChatsService() + return self diff --git a/packages/generator/tests/crud/snapshots/swift/Client.swift b/packages/generator/tests/crud/snapshots/swift/Client.swift index 7bbed626..bef67a56 100644 --- a/packages/generator/tests/crud/snapshots/swift/Client.swift +++ b/packages/generator/tests/crud/snapshots/swift/Client.swift @@ -172,8 +172,20 @@ public final class ChatsServiceImpl: ChatsService { public struct PachcaClient { public let chats: ChatsService + private init(chats: ChatsService) { + self.chats = chats + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", chats: ChatsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + self.init( + chats: chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(chats: ChatsService = ChatsService()) -> PachcaClient { + PachcaClient( + chats: chats + ) } } diff --git a/packages/generator/tests/crud/snapshots/ts/client.ts b/packages/generator/tests/crud/snapshots/ts/client.ts index cd4dc7b7..c5a92055 100644 --- a/packages/generator/tests/crud/snapshots/ts/client.ts +++ b/packages/generator/tests/crud/snapshots/ts/client.ts @@ -175,4 +175,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + chats: options.chats ?? new ChatsService(), + }); + } } diff --git a/packages/generator/tests/deep-nesting/snapshots/swift/Client.swift b/packages/generator/tests/deep-nesting/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/deep-nesting/snapshots/swift/Client.swift +++ b/packages/generator/tests/deep-nesting/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs index 63480f0a..7361ef39 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs @@ -133,11 +133,17 @@ public override async System.Threading.Tasks.Task CreateUploadAsync(UploadReques public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public EventsService Events { get; } public UploadsService Uploads { get; } + private PachcaClient(EventsService events, UploadsService uploads) + { + Events = events; + Uploads = uploads; + } + public PachcaClient(string token, string baseUrl, EventsService? events = null, UploadsService? uploads = null) { _client = new HttpClient(); @@ -148,9 +154,14 @@ public PachcaClient(string token, string baseUrl, EventsService? events = null, Uploads = uploads ?? new UploadsServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(EventsService? events = null, UploadsService? uploads = null) + { + return new PachcaClient(events ?? new EventsService(), uploads ?? new UploadsService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/edge-cases/snapshots/go/client.go b/packages/generator/tests/edge-cases/snapshots/go/client.go index 8dbb7f0d..fea4900a 100644 --- a/packages/generator/tests/edge-cases/snapshots/go/client.go +++ b/packages/generator/tests/edge-cases/snapshots/go/client.go @@ -198,6 +198,13 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + events EventsService + uploads UploadsService +} + +type StubClientOption func(*stubClientConfig) + func WithBaseURL(baseURL string) ClientOption { return func(cfg *clientConfig) { cfg.baseURL = baseURL } } @@ -210,6 +217,14 @@ func WithUploads(service UploadsService) ClientOption { return func(cfg *clientConfig) { cfg.uploads = service } } +func WithStubEvents(service EventsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.events = service } +} + +func WithStubUploads(service UploadsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.uploads = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{} for _, opt := range opts { @@ -223,3 +238,14 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Uploads: func() UploadsService { if cfg.uploads != nil { return cfg.uploads }; return &UploadsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Events : func() EventsService { if cfg.events != nil { return cfg.events }; return &EventsServiceStub{} }(), + Uploads: func() UploadsService { if cfg.uploads != nil { return cfg.uploads }; return &UploadsServiceStub{} }(), + } +} diff --git a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt index a4f612ea..7fd4f14e 100644 --- a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt +++ b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt @@ -87,34 +87,52 @@ class UploadsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String, - events: EventsService? = null, - uploads: UploadsService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val events: EventsService, + val uploads: UploadsService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String, + events: EventsService? = null, + uploads: UploadsService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + events = events ?: EventsServiceImpl(baseUrl, client), + uploads = uploads ?: UploadsServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + events: EventsService = EventsService(), + uploads: UploadsService = UploadsService() + ): PachcaClient = PachcaClient( + client = null, + events = events, + uploads = uploads + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val events: EventsService = events ?: EventsServiceImpl(baseUrl, client) - val uploads: UploadsService = uploads ?: UploadsServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/edge-cases/snapshots/py/client.py b/packages/generator/tests/edge-cases/snapshots/py/client.py index d81348bf..2cf82ac2 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/client.py +++ b/packages/generator/tests/edge-cases/snapshots/py/client.py @@ -120,3 +120,15 @@ def __init__(self, token: str, base_url: str, events: EventsService | None = Non async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + events: EventsService | None = None, + uploads: UploadsService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.events = events or EventsService() + self.uploads = uploads or UploadsService() + return self diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift index 3a470c11..8d5ec703 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift @@ -122,9 +122,23 @@ public struct PachcaClient { public let events: EventsService public let uploads: UploadsService + private init(events: EventsService, uploads: UploadsService) { + self.events = events + self.uploads = uploads + } + public init(token: String, baseURL: String, events: EventsService? = nil, uploads: UploadsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.events = events ?? EventsServiceImpl(baseURL: baseURL, headers: headers) - self.uploads = uploads ?? UploadsServiceImpl(baseURL: baseURL, headers: headers) + self.init( + events: events ?? EventsServiceImpl(baseURL: baseURL, headers: headers), + uploads: uploads ?? UploadsServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(events: EventsService = EventsService(), uploads: UploadsService = UploadsService()) -> PachcaClient { + PachcaClient( + events: events, + uploads: uploads + ) } } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/client.ts b/packages/generator/tests/edge-cases/snapshots/ts/client.ts index 9012ea7d..6a097555 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/client.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/client.ts @@ -109,4 +109,11 @@ export class PachcaClient { this.events = options.events ?? new EventsServiceImpl(baseUrl, headers); this.uploads = options.uploads ?? new UploadsServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "", + events: options.events ?? new EventsService(), + uploads: options.uploads ?? new UploadsService(), + }); + } } diff --git a/packages/generator/tests/enums/snapshots/swift/Client.swift b/packages/generator/tests/enums/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/enums/snapshots/swift/Client.swift +++ b/packages/generator/tests/enums/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/models/snapshots/swift/Client.swift b/packages/generator/tests/models/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/models/snapshots/swift/Client.swift +++ b/packages/generator/tests/models/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs index 2fd3e330..a93506a9 100644 --- a/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs +++ b/packages/generator/tests/multi-path-params/snapshots/cs/Client.cs @@ -112,10 +112,15 @@ public override async System.Threading.Tasks.Task DeleteCommentAsync( public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public TasksService Tasks { get; } + private PachcaClient(TasksService tasks) + { + Tasks = tasks; + } + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", TasksService? tasks = null) { _client = new HttpClient(); @@ -125,9 +130,14 @@ public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Tasks = tasks ?? new TasksServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(TasksService? tasks = null) + { + return new PachcaClient(tasks ?? new TasksService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/multi-path-params/snapshots/go/client.go b/packages/generator/tests/multi-path-params/snapshots/go/client.go index 2232cb8e..f99688a9 100644 --- a/packages/generator/tests/multi-path-params/snapshots/go/client.go +++ b/packages/generator/tests/multi-path-params/snapshots/go/client.go @@ -153,6 +153,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + tasks TasksService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.example.com/v1" func WithBaseURL(baseURL string) ClientOption { @@ -163,6 +169,10 @@ func WithTasks(service TasksService) ClientOption { return func(cfg *clientConfig) { cfg.tasks = service } } +func WithStubTasks(service TasksService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.tasks = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -175,3 +185,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Tasks: func() TasksService { if cfg.tasks != nil { return cfg.tasks }; return &TasksServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Tasks: func() TasksService { if cfg.tasks != nil { return cfg.tasks }; return &TasksServiceStub{} }(), + } +} diff --git a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt index 707b0544..4578e675 100644 --- a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt +++ b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt @@ -75,32 +75,47 @@ class TasksServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.example.com/v1", - tasks: TasksService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val tasks: TasksService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.example.com/v1", + tasks: TasksService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + tasks = tasks ?: TasksServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + tasks: TasksService = TasksService() + ): PachcaClient = PachcaClient( + client = null, + tasks = tasks + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val tasks: TasksService = tasks ?: TasksServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/multi-path-params/snapshots/py/client.py b/packages/generator/tests/multi-path-params/snapshots/py/client.py index 36ef2ba6..0608f2ac 100644 --- a/packages/generator/tests/multi-path-params/snapshots/py/client.py +++ b/packages/generator/tests/multi-path-params/snapshots/py/client.py @@ -101,3 +101,13 @@ def __init__(self, token: str, base_url: str = "https://api.example.com/v1", tas async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + tasks: TasksService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.tasks = tasks or TasksService() + return self diff --git a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift index 83140f24..353d0d64 100644 --- a/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift +++ b/packages/generator/tests/multi-path-params/snapshots/swift/Client.swift @@ -82,8 +82,20 @@ public final class TasksServiceImpl: TasksService { public struct PachcaClient { public let tasks: TasksService + private init(tasks: TasksService) { + self.tasks = tasks + } + public init(token: String, baseURL: String = "https://api.example.com/v1", tasks: TasksService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.tasks = tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) + self.init( + tasks: tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(tasks: TasksService = TasksService()) -> PachcaClient { + PachcaClient( + tasks: tasks + ) } } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts index 4124f37a..7a752f88 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts +++ b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts @@ -80,4 +80,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.tasks = options.tasks ?? new TasksServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.example.com/v1", + tasks: options.tasks ?? new TasksService(), + }); + } } diff --git a/packages/generator/tests/nullable-ref/snapshots/swift/Client.swift b/packages/generator/tests/nullable-ref/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/nullable-ref/snapshots/swift/Client.swift +++ b/packages/generator/tests/nullable-ref/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/oneof/snapshots/swift/Client.swift b/packages/generator/tests/oneof/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/oneof/snapshots/swift/Client.swift +++ b/packages/generator/tests/oneof/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/patch/snapshots/cs/Client.cs b/packages/generator/tests/patch/snapshots/cs/Client.cs index 34de53ba..ee892bd1 100644 --- a/packages/generator/tests/patch/snapshots/cs/Client.cs +++ b/packages/generator/tests/patch/snapshots/cs/Client.cs @@ -56,10 +56,15 @@ public override async System.Threading.Tasks.Task PatchItemAsync( public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public ItemsService Items { get; } + private PachcaClient(ItemsService items) + { + Items = items; + } + public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", ItemsService? items = null) { _client = new HttpClient(); @@ -69,9 +74,14 @@ public PachcaClient(string token, string baseUrl = "https://api.example.com/v1", Items = items ?? new ItemsServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(ItemsService? items = null) + { + return new PachcaClient(items ?? new ItemsService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/patch/snapshots/go/client.go b/packages/generator/tests/patch/snapshots/go/client.go index 1461b94d..7ce5e3a4 100644 --- a/packages/generator/tests/patch/snapshots/go/client.go +++ b/packages/generator/tests/patch/snapshots/go/client.go @@ -103,6 +103,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + items ItemsService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.example.com/v1" func WithBaseURL(baseURL string) ClientOption { @@ -113,6 +119,10 @@ func WithItems(service ItemsService) ClientOption { return func(cfg *clientConfig) { cfg.items = service } } +func WithStubItems(service ItemsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.items = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -125,3 +135,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Items: func() ItemsService { if cfg.items != nil { return cfg.items }; return &ItemsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Items: func() ItemsService { if cfg.items != nil { return cfg.items }; return &ItemsServiceStub{} }(), + } +} diff --git a/packages/generator/tests/patch/snapshots/kt/Client.kt b/packages/generator/tests/patch/snapshots/kt/Client.kt index 2a75133c..213b970e 100644 --- a/packages/generator/tests/patch/snapshots/kt/Client.kt +++ b/packages/generator/tests/patch/snapshots/kt/Client.kt @@ -35,32 +35,47 @@ class ItemsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.example.com/v1", - items: ItemsService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val items: ItemsService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.example.com/v1", + items: ItemsService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + items = items ?: ItemsServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + items: ItemsService = ItemsService() + ): PachcaClient = PachcaClient( + client = null, + items = items + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val items: ItemsService = items ?: ItemsServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/patch/snapshots/py/client.py b/packages/generator/tests/patch/snapshots/py/client.py index f965fb9d..793e14d4 100644 --- a/packages/generator/tests/patch/snapshots/py/client.py +++ b/packages/generator/tests/patch/snapshots/py/client.py @@ -48,3 +48,13 @@ def __init__(self, token: str, base_url: str = "https://api.example.com/v1", ite async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + items: ItemsService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.items = items or ItemsService() + return self diff --git a/packages/generator/tests/patch/snapshots/swift/Client.swift b/packages/generator/tests/patch/snapshots/swift/Client.swift index ee91137a..400cda4d 100644 --- a/packages/generator/tests/patch/snapshots/swift/Client.swift +++ b/packages/generator/tests/patch/snapshots/swift/Client.swift @@ -47,8 +47,20 @@ public final class ItemsServiceImpl: ItemsService { public struct PachcaClient { public let items: ItemsService + private init(items: ItemsService) { + self.items = items + } + public init(token: String, baseURL: String = "https://api.example.com/v1", items: ItemsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.items = items ?? ItemsServiceImpl(baseURL: baseURL, headers: headers) + self.init( + items: items ?? ItemsServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(items: ItemsService = ItemsService()) -> PachcaClient { + PachcaClient( + items: items + ) } } diff --git a/packages/generator/tests/patch/snapshots/ts/client.ts b/packages/generator/tests/patch/snapshots/ts/client.ts index bfcd1ef6..a2899829 100644 --- a/packages/generator/tests/patch/snapshots/ts/client.ts +++ b/packages/generator/tests/patch/snapshots/ts/client.ts @@ -46,4 +46,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.items = options.items ?? new ItemsServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.example.com/v1", + items: options.items ?? new ItemsService(), + }); + } } diff --git a/packages/generator/tests/record/snapshots/cs/Client.cs b/packages/generator/tests/record/snapshots/cs/Client.cs index 2c4649ca..dfbbe2d8 100644 --- a/packages/generator/tests/record/snapshots/cs/Client.cs +++ b/packages/generator/tests/record/snapshots/cs/Client.cs @@ -58,10 +58,15 @@ public override async System.Threading.Tasks.Task CreateLinkPreviewsAsync( public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public LinkPreviewsService LinkPreviews { get; } + private PachcaClient(LinkPreviewsService linkPreviews) + { + LinkPreviews = linkPreviews; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", LinkPreviewsService? linkPreviews = null) { _client = new HttpClient(); @@ -71,9 +76,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s LinkPreviews = linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(LinkPreviewsService? linkPreviews = null) + { + return new PachcaClient(linkPreviews ?? new LinkPreviewsService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/record/snapshots/go/client.go b/packages/generator/tests/record/snapshots/go/client.go index 270fe968..a064c6cf 100644 --- a/packages/generator/tests/record/snapshots/go/client.go +++ b/packages/generator/tests/record/snapshots/go/client.go @@ -101,6 +101,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + linkPreviews LinkPreviewsService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -111,6 +117,10 @@ func WithLinkPreviews(service LinkPreviewsService) ClientOption { return func(cfg *clientConfig) { cfg.linkPreviews = service } } +func WithStubLinkPreviews(service LinkPreviewsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.linkPreviews = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -123,3 +133,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { LinkPreviews: func() LinkPreviewsService { if cfg.linkPreviews != nil { return cfg.linkPreviews }; return &LinkPreviewsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + LinkPreviews: func() LinkPreviewsService { if cfg.linkPreviews != nil { return cfg.linkPreviews }; return &LinkPreviewsServiceStub{} }(), + } +} diff --git a/packages/generator/tests/record/snapshots/kt/Client.kt b/packages/generator/tests/record/snapshots/kt/Client.kt index 98d17fcc..e4394489 100644 --- a/packages/generator/tests/record/snapshots/kt/Client.kt +++ b/packages/generator/tests/record/snapshots/kt/Client.kt @@ -36,32 +36,47 @@ class LinkPreviewsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - linkPreviews: LinkPreviewsService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val linkPreviews: LinkPreviewsService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + linkPreviews: LinkPreviewsService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + linkPreviews = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + linkPreviews: LinkPreviewsService = LinkPreviewsService() + ): PachcaClient = PachcaClient( + client = null, + linkPreviews = linkPreviews + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val linkPreviews: LinkPreviewsService = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/record/snapshots/py/client.py b/packages/generator/tests/record/snapshots/py/client.py index be36aaa4..c8df541f 100644 --- a/packages/generator/tests/record/snapshots/py/client.py +++ b/packages/generator/tests/record/snapshots/py/client.py @@ -49,3 +49,13 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + link_previews: LinkPreviewsService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.link_previews = link_previews or LinkPreviewsService() + return self diff --git a/packages/generator/tests/record/snapshots/swift/Client.swift b/packages/generator/tests/record/snapshots/swift/Client.swift index 002abe27..ca1eae0f 100644 --- a/packages/generator/tests/record/snapshots/swift/Client.swift +++ b/packages/generator/tests/record/snapshots/swift/Client.swift @@ -49,8 +49,20 @@ public final class LinkPreviewsServiceImpl: LinkPreviewsService { public struct PachcaClient { public let linkPreviews: LinkPreviewsService + private init(linkPreviews: LinkPreviewsService) { + self.linkPreviews = linkPreviews + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", linkPreviews: LinkPreviewsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.linkPreviews = linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) + self.init( + linkPreviews: linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(linkPreviews: LinkPreviewsService = LinkPreviewsService()) -> PachcaClient { + PachcaClient( + linkPreviews: linkPreviews + ) } } diff --git a/packages/generator/tests/record/snapshots/ts/client.ts b/packages/generator/tests/record/snapshots/ts/client.ts index 9c8aef45..e8fc863d 100644 --- a/packages/generator/tests/record/snapshots/ts/client.ts +++ b/packages/generator/tests/record/snapshots/ts/client.ts @@ -47,4 +47,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.linkPreviews = options.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + linkPreviews: options.linkPreviews ?? new LinkPreviewsService(), + }); + } } diff --git a/packages/generator/tests/redirect/snapshots/cs/Client.cs b/packages/generator/tests/redirect/snapshots/cs/Client.cs index e24963f1..69fda314 100644 --- a/packages/generator/tests/redirect/snapshots/cs/Client.cs +++ b/packages/generator/tests/redirect/snapshots/cs/Client.cs @@ -52,10 +52,15 @@ public override async System.Threading.Tasks.Task DownloadExportAsync(in public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public CommonService Common { get; } + private PachcaClient(CommonService common) + { + Common = common; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", CommonService? common = null) { var handler = new SocketsHttpHandler @@ -69,9 +74,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Common = common ?? new CommonServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(CommonService? common = null) + { + return new PachcaClient(common ?? new CommonService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/redirect/snapshots/go/client.go b/packages/generator/tests/redirect/snapshots/go/client.go index 6351f8a4..948f84a0 100644 --- a/packages/generator/tests/redirect/snapshots/go/client.go +++ b/packages/generator/tests/redirect/snapshots/go/client.go @@ -100,6 +100,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + common CommonService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -110,6 +116,10 @@ func WithCommon(service CommonService) ClientOption { return func(cfg *clientConfig) { cfg.common = service } } +func WithStubCommon(service CommonService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.common = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -125,3 +135,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceStub{} }(), + } +} diff --git a/packages/generator/tests/redirect/snapshots/kt/Client.kt b/packages/generator/tests/redirect/snapshots/kt/Client.kt index db8fbcb8..58b1c433 100644 --- a/packages/generator/tests/redirect/snapshots/kt/Client.kt +++ b/packages/generator/tests/redirect/snapshots/kt/Client.kt @@ -34,33 +34,48 @@ class CommonServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - common: CommonService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val common: CommonService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - followRedirects = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + common: CommonService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + common = common ?: CommonServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + common: CommonService = CommonService() + ): PachcaClient = PachcaClient( + client = null, + common = common + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + followRedirects = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/redirect/snapshots/py/client.py b/packages/generator/tests/redirect/snapshots/py/client.py index 094eb70e..8b389731 100644 --- a/packages/generator/tests/redirect/snapshots/py/client.py +++ b/packages/generator/tests/redirect/snapshots/py/client.py @@ -52,3 +52,13 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + common: CommonService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.common = common or CommonService() + return self diff --git a/packages/generator/tests/redirect/snapshots/swift/Client.swift b/packages/generator/tests/redirect/snapshots/swift/Client.swift index e745edea..731a3b21 100644 --- a/packages/generator/tests/redirect/snapshots/swift/Client.swift +++ b/packages/generator/tests/redirect/snapshots/swift/Client.swift @@ -62,8 +62,20 @@ private final class RedirectPreventer: NSObject, URLSessionTaskDelegate { public struct PachcaClient { public let common: CommonService + private init(common: CommonService) { + self.common = common + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", common: CommonService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.init( + common: common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(common: CommonService = CommonService()) -> PachcaClient { + PachcaClient( + common: common + ) } } diff --git a/packages/generator/tests/redirect/snapshots/ts/client.ts b/packages/generator/tests/redirect/snapshots/ts/client.ts index 2141a6e4..7ebb19c8 100644 --- a/packages/generator/tests/redirect/snapshots/ts/client.ts +++ b/packages/generator/tests/redirect/snapshots/ts/client.ts @@ -51,4 +51,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + common: options.common ?? new CommonService(), + }); + } } diff --git a/packages/generator/tests/reserved-keywords/snapshots/swift/Client.swift b/packages/generator/tests/reserved-keywords/snapshots/swift/Client.swift index 9f305880..38b39b3b 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/swift/Client.swift +++ b/packages/generator/tests/reserved-keywords/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String = "https://api.example.com/api/v1") { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/search/snapshots/cs/Client.cs b/packages/generator/tests/search/snapshots/cs/Client.cs index 8112b380..b2de9ecc 100644 --- a/packages/generator/tests/search/snapshots/cs/Client.cs +++ b/packages/generator/tests/search/snapshots/cs/Client.cs @@ -121,10 +121,15 @@ public override async System.Threading.Tasks.Task> Sea public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public SearchService Search { get; } + private PachcaClient(SearchService search) + { + Search = search; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", SearchService? search = null) { _client = new HttpClient(); @@ -134,9 +139,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Search = search ?? new SearchServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(SearchService? search = null) + { + return new PachcaClient(search ?? new SearchService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/search/snapshots/go/client.go b/packages/generator/tests/search/snapshots/go/client.go index 13805f10..d8a66110 100644 --- a/packages/generator/tests/search/snapshots/go/client.go +++ b/packages/generator/tests/search/snapshots/go/client.go @@ -151,6 +151,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + search SearchService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -161,6 +167,10 @@ func WithSearch(service SearchService) ClientOption { return func(cfg *clientConfig) { cfg.search = service } } +func WithStubSearch(service SearchService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.search = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -173,3 +183,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Search: func() SearchService { if cfg.search != nil { return cfg.search }; return &SearchServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Search: func() SearchService { if cfg.search != nil { return cfg.search }; return &SearchServiceStub{} }(), + } +} diff --git a/packages/generator/tests/search/snapshots/kt/Client.kt b/packages/generator/tests/search/snapshots/kt/Client.kt index b69306d9..f7548266 100644 --- a/packages/generator/tests/search/snapshots/kt/Client.kt +++ b/packages/generator/tests/search/snapshots/kt/Client.kt @@ -100,32 +100,47 @@ class SearchServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - search: SearchService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val search: SearchService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + search: SearchService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + search = search ?: SearchServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + search: SearchService = SearchService() + ): PachcaClient = PachcaClient( + client = null, + search = search + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val search: SearchService = search ?: SearchServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/search/snapshots/py/client.py b/packages/generator/tests/search/snapshots/py/client.py index 8a4f11ab..50e0a8fd 100644 --- a/packages/generator/tests/search/snapshots/py/client.py +++ b/packages/generator/tests/search/snapshots/py/client.py @@ -97,3 +97,13 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + search: SearchService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.search = search or SearchService() + return self diff --git a/packages/generator/tests/search/snapshots/swift/Client.swift b/packages/generator/tests/search/snapshots/swift/Client.swift index 8bb83936..4e3c42cc 100644 --- a/packages/generator/tests/search/snapshots/swift/Client.swift +++ b/packages/generator/tests/search/snapshots/swift/Client.swift @@ -72,8 +72,20 @@ public final class SearchServiceImpl: SearchService { public struct PachcaClient { public let search: SearchService + private init(search: SearchService) { + self.search = search + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", search: SearchService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.search = search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) + self.init( + search: search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(search: SearchService = SearchService()) -> PachcaClient { + PachcaClient( + search: search + ) } } diff --git a/packages/generator/tests/search/snapshots/ts/client.ts b/packages/generator/tests/search/snapshots/ts/client.ts index 28e3fb24..22ed06af 100644 --- a/packages/generator/tests/search/snapshots/ts/client.ts +++ b/packages/generator/tests/search/snapshots/ts/client.ts @@ -79,4 +79,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.search = options.search ?? new SearchServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + search: options.search ?? new SearchService(), + }); + } } diff --git a/packages/generator/tests/unions/snapshots/swift/Client.swift b/packages/generator/tests/unions/snapshots/swift/Client.swift index 0a2590e4..8691461e 100644 --- a/packages/generator/tests/unions/snapshots/swift/Client.swift +++ b/packages/generator/tests/unions/snapshots/swift/Client.swift @@ -5,7 +5,17 @@ import FoundationNetworking public struct PachcaClient { + private init() { + } + public init(token: String, baseURL: String) { let headers = ["Authorization": "Bearer \(token)"] + self.init( + ) + } + + public static func stub() -> PachcaClient { + PachcaClient( + ) } } diff --git a/packages/generator/tests/unwrap/snapshots/cs/Client.cs b/packages/generator/tests/unwrap/snapshots/cs/Client.cs index 04a360dd..cfbca6d4 100644 --- a/packages/generator/tests/unwrap/snapshots/cs/Client.cs +++ b/packages/generator/tests/unwrap/snapshots/cs/Client.cs @@ -120,11 +120,17 @@ public override async System.Threading.Tasks.Task ArchiveChatAsync(int id, Cance public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public ChatsService Chats { get; } public MembersService Members { get; } + private PachcaClient(ChatsService chats, MembersService members) + { + Chats = chats; + Members = members; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", ChatsService? chats = null, MembersService? members = null) { _client = new HttpClient(); @@ -135,9 +141,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Members = members ?? new MembersServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(ChatsService? chats = null, MembersService? members = null) + { + return new PachcaClient(chats ?? new ChatsService(), members ?? new MembersService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/unwrap/snapshots/go/client.go b/packages/generator/tests/unwrap/snapshots/go/client.go index be0ec69d..4b90d95f 100644 --- a/packages/generator/tests/unwrap/snapshots/go/client.go +++ b/packages/generator/tests/unwrap/snapshots/go/client.go @@ -182,6 +182,13 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + chats ChatsService + members MembersService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -196,6 +203,14 @@ func WithMembers(service MembersService) ClientOption { return func(cfg *clientConfig) { cfg.members = service } } +func WithStubChats(service ChatsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.chats = service } +} + +func WithStubMembers(service MembersService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.members = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -209,3 +224,14 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Members: func() MembersService { if cfg.members != nil { return cfg.members }; return &MembersServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Chats : func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceStub{} }(), + Members: func() MembersService { if cfg.members != nil { return cfg.members }; return &MembersServiceStub{} }(), + } +} diff --git a/packages/generator/tests/unwrap/snapshots/kt/Client.kt b/packages/generator/tests/unwrap/snapshots/kt/Client.kt index 0f2e0e2e..2db70fe9 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/Client.kt +++ b/packages/generator/tests/unwrap/snapshots/kt/Client.kt @@ -72,34 +72,52 @@ class ChatsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - chats: ChatsService? = null, - members: MembersService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val chats: ChatsService, + val members: MembersService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + chats: ChatsService? = null, + members: MembersService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + chats = chats ?: ChatsServiceImpl(baseUrl, client), + members = members ?: MembersServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + chats: ChatsService = ChatsService(), + members: MembersService = MembersService() + ): PachcaClient = PachcaClient( + client = null, + chats = chats, + members = members + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) - val members: MembersService = members ?: MembersServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/unwrap/snapshots/py/client.py b/packages/generator/tests/unwrap/snapshots/py/client.py index b0d04ffb..b9f69b93 100644 --- a/packages/generator/tests/unwrap/snapshots/py/client.py +++ b/packages/generator/tests/unwrap/snapshots/py/client.py @@ -106,3 +106,15 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + chats: ChatsService | None = None, + members: MembersService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.chats = chats or ChatsService() + self.members = members or MembersService() + return self diff --git a/packages/generator/tests/unwrap/snapshots/swift/Client.swift b/packages/generator/tests/unwrap/snapshots/swift/Client.swift index 6b354cf3..f839811a 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Client.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Client.swift @@ -109,9 +109,23 @@ public struct PachcaClient { public let chats: ChatsService public let members: MembersService + private init(chats: ChatsService, members: MembersService) { + self.chats = chats + self.members = members + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", chats: ChatsService? = nil, members: MembersService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) - self.members = members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) + self.init( + chats: chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers), + members: members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(chats: ChatsService = ChatsService(), members: MembersService = MembersService()) -> PachcaClient { + PachcaClient( + chats: chats, + members: members + ) } } diff --git a/packages/generator/tests/unwrap/snapshots/ts/client.ts b/packages/generator/tests/unwrap/snapshots/ts/client.ts index 43a90403..4f68dd60 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/client.ts +++ b/packages/generator/tests/unwrap/snapshots/ts/client.ts @@ -106,4 +106,11 @@ export class PachcaClient { this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); this.members = options.members ?? new MembersServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + chats: options.chats ?? new ChatsService(), + members: options.members ?? new MembersService(), + }); + } } diff --git a/packages/generator/tests/upload/snapshots/cs/Client.cs b/packages/generator/tests/upload/snapshots/cs/Client.cs index 643299e0..65fb0771 100644 --- a/packages/generator/tests/upload/snapshots/cs/Client.cs +++ b/packages/generator/tests/upload/snapshots/cs/Client.cs @@ -92,10 +92,15 @@ public override async System.Threading.Tasks.Task GetUploadParamsA public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public CommonService Common { get; } + private PachcaClient(CommonService common) + { + Common = common; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", CommonService? common = null) { _client = new HttpClient(); @@ -105,9 +110,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Common = common ?? new CommonServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(CommonService? common = null) + { + return new PachcaClient(common ?? new CommonService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/packages/generator/tests/upload/snapshots/go/client.go b/packages/generator/tests/upload/snapshots/go/client.go index 81a74581..70c3f017 100644 --- a/packages/generator/tests/upload/snapshots/go/client.go +++ b/packages/generator/tests/upload/snapshots/go/client.go @@ -152,6 +152,12 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + common CommonService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -162,6 +168,10 @@ func WithCommon(service CommonService) ClientOption { return func(cfg *clientConfig) { cfg.common = service } } +func WithStubCommon(service CommonService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.common = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -174,3 +184,13 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Common: func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceStub{} }(), + } +} diff --git a/packages/generator/tests/upload/snapshots/kt/Client.kt b/packages/generator/tests/upload/snapshots/kt/Client.kt index 94d5be7a..276f6df8 100644 --- a/packages/generator/tests/upload/snapshots/kt/Client.kt +++ b/packages/generator/tests/upload/snapshots/kt/Client.kt @@ -64,32 +64,47 @@ class CommonServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - common: CommonService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val common: CommonService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + common: CommonService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + common = common ?: CommonServiceImpl(baseUrl, client) + ) } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L + + fun stub( + common: CommonService = CommonService() + ): PachcaClient = PachcaClient( + client = null, + common = common + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } } - } - defaultRequest { - bearerAuth(token) + defaultRequest { bearerAuth(token) } } } - val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/packages/generator/tests/upload/snapshots/py/client.py b/packages/generator/tests/upload/snapshots/py/client.py index 9f5b34f6..e6d472a1 100644 --- a/packages/generator/tests/upload/snapshots/py/client.py +++ b/packages/generator/tests/upload/snapshots/py/client.py @@ -82,3 +82,13 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + common: CommonService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.common = common or CommonService() + return self diff --git a/packages/generator/tests/upload/snapshots/swift/Client.swift b/packages/generator/tests/upload/snapshots/swift/Client.swift index defcb27b..9f646c8b 100644 --- a/packages/generator/tests/upload/snapshots/swift/Client.swift +++ b/packages/generator/tests/upload/snapshots/swift/Client.swift @@ -89,8 +89,20 @@ public final class CommonServiceImpl: CommonService { public struct PachcaClient { public let common: CommonService + private init(common: CommonService) { + self.common = common + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", common: CommonService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + self.init( + common: common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(common: CommonService = CommonService()) -> PachcaClient { + PachcaClient( + common: common + ) } } diff --git a/packages/generator/tests/upload/snapshots/ts/client.ts b/packages/generator/tests/upload/snapshots/ts/client.ts index d14ecde4..4609523a 100644 --- a/packages/generator/tests/upload/snapshots/ts/client.ts +++ b/packages/generator/tests/upload/snapshots/ts/client.ts @@ -76,4 +76,10 @@ export class PachcaClient { const headers = { Authorization: `Bearer ${token}` }; this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + common: options.common ?? new CommonService(), + }); + } } diff --git a/sdk/csharp/generated/Client.cs b/sdk/csharp/generated/Client.cs index aa4cd5fe..bbd87d93 100644 --- a/sdk/csharp/generated/Client.cs +++ b/sdk/csharp/generated/Client.cs @@ -2492,7 +2492,7 @@ public override async System.Threading.Tasks.Task OpenViewAsync(OpenViewRequest public sealed class PachcaClient : IDisposable { - private readonly HttpClient _client; + private readonly HttpClient? _client; public BotsService Bots { get; } public ChatsService Chats { get; } @@ -2511,6 +2511,26 @@ public sealed class PachcaClient : IDisposable public UsersService Users { get; } public ViewsService Views { get; } + private PachcaClient(BotsService bots, ChatsService chats, CommonService common, GroupTagsService groupTags, LinkPreviewsService linkPreviews, MembersService members, MessagesService messages, ProfileService profile, ReactionsService reactions, ReadMembersService readMembers, SearchService search, SecurityService security, TasksService tasks, ThreadsService threads, UsersService users, ViewsService views) + { + Bots = bots; + Chats = chats; + Common = common; + GroupTags = groupTags; + LinkPreviews = linkPreviews; + Members = members; + Messages = messages; + Profile = profile; + Reactions = reactions; + ReadMembers = readMembers; + Search = search; + Security = security; + Tasks = tasks; + Threads = threads; + Users = users; + Views = views; + } + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1", BotsService? bots = null, ChatsService? chats = null, CommonService? common = null, GroupTagsService? groupTags = null, LinkPreviewsService? linkPreviews = null, MembersService? members = null, MessagesService? messages = null, ProfileService? profile = null, ReactionsService? reactions = null, ReadMembersService? readMembers = null, SearchService? search = null, SecurityService? security = null, TasksService? tasks = null, ThreadsService? threads = null, UsersService? users = null, ViewsService? views = null) { var handler = new SocketsHttpHandler @@ -2539,9 +2559,14 @@ public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/s Views = views ?? new ViewsServiceImpl(baseUrl, _client); } + public static PachcaClient Stub(BotsService? bots = null, ChatsService? chats = null, CommonService? common = null, GroupTagsService? groupTags = null, LinkPreviewsService? linkPreviews = null, MembersService? members = null, MessagesService? messages = null, ProfileService? profile = null, ReactionsService? reactions = null, ReadMembersService? readMembers = null, SearchService? search = null, SecurityService? security = null, TasksService? tasks = null, ThreadsService? threads = null, UsersService? users = null, ViewsService? views = null) + { + return new PachcaClient(bots ?? new BotsService(), chats ?? new ChatsService(), common ?? new CommonService(), groupTags ?? new GroupTagsService(), linkPreviews ?? new LinkPreviewsService(), members ?? new MembersService(), messages ?? new MessagesService(), profile ?? new ProfileService(), reactions ?? new ReactionsService(), readMembers ?? new ReadMembersService(), search ?? new SearchService(), security ?? new SecurityService(), tasks ?? new TasksService(), threads ?? new ThreadsService(), users ?? new UsersService(), views ?? new ViewsService()); + } + public void Dispose() { - _client.Dispose(); + _client?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/sdk/go/generated/client.go b/sdk/go/generated/client.go index fdca0df4..dc1a905b 100644 --- a/sdk/go/generated/client.go +++ b/sdk/go/generated/client.go @@ -3058,6 +3058,27 @@ type clientConfig struct { type ClientOption func(*clientConfig) +type stubClientConfig struct { + bots BotsService + chats ChatsService + common CommonService + groupTags GroupTagsService + linkPreviews LinkPreviewsService + members MembersService + messages MessagesService + profile ProfileService + reactions ReactionsService + readMembers ReadMembersService + search SearchService + security SecurityService + tasks TasksService + threads ThreadsService + users UsersService + views ViewsService +} + +type StubClientOption func(*stubClientConfig) + const DefaultBaseURL = "https://api.pachca.com/api/shared/v1" func WithBaseURL(baseURL string) ClientOption { @@ -3128,6 +3149,70 @@ func WithViews(service ViewsService) ClientOption { return func(cfg *clientConfig) { cfg.views = service } } +func WithStubBots(service BotsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.bots = service } +} + +func WithStubChats(service ChatsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.chats = service } +} + +func WithStubCommon(service CommonService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.common = service } +} + +func WithStubGroupTags(service GroupTagsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.groupTags = service } +} + +func WithStubLinkPreviews(service LinkPreviewsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.linkPreviews = service } +} + +func WithStubMembers(service MembersService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.members = service } +} + +func WithStubMessages(service MessagesService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.messages = service } +} + +func WithStubProfile(service ProfileService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.profile = service } +} + +func WithStubReactions(service ReactionsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.reactions = service } +} + +func WithStubReadMembers(service ReadMembersService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.readMembers = service } +} + +func WithStubSearch(service SearchService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.search = service } +} + +func WithStubSecurity(service SecurityService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.security = service } +} + +func WithStubTasks(service TasksService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.tasks = service } +} + +func WithStubThreads(service ThreadsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.threads = service } +} + +func WithStubUsers(service UsersService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.users = service } +} + +func WithStubViews(service ViewsService) StubClientOption { + return func(cfg *stubClientConfig) { cfg.views = service } +} + func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { cfg := clientConfig{baseURL: DefaultBaseURL} for _, opt := range opts { @@ -3158,3 +3243,28 @@ func NewPachcaClient(token string, opts ...ClientOption) *PachcaClient { Views : func() ViewsService { if cfg.views != nil { return cfg.views }; return &ViewsServiceImpl{baseURL: cfg.baseURL, client: client} }(), } } + +func NewStubPachcaClient(opts ...StubClientOption) *PachcaClient { + cfg := stubClientConfig{} + for _, opt := range opts { + opt(&cfg) + } + return &PachcaClient{ + Bots : func() BotsService { if cfg.bots != nil { return cfg.bots }; return &BotsServiceStub{} }(), + Chats : func() ChatsService { if cfg.chats != nil { return cfg.chats }; return &ChatsServiceStub{} }(), + Common : func() CommonService { if cfg.common != nil { return cfg.common }; return &CommonServiceStub{} }(), + GroupTags : func() GroupTagsService { if cfg.groupTags != nil { return cfg.groupTags }; return &GroupTagsServiceStub{} }(), + LinkPreviews: func() LinkPreviewsService { if cfg.linkPreviews != nil { return cfg.linkPreviews }; return &LinkPreviewsServiceStub{} }(), + Members : func() MembersService { if cfg.members != nil { return cfg.members }; return &MembersServiceStub{} }(), + Messages : func() MessagesService { if cfg.messages != nil { return cfg.messages }; return &MessagesServiceStub{} }(), + Profile : func() ProfileService { if cfg.profile != nil { return cfg.profile }; return &ProfileServiceStub{} }(), + Reactions : func() ReactionsService { if cfg.reactions != nil { return cfg.reactions }; return &ReactionsServiceStub{} }(), + ReadMembers : func() ReadMembersService { if cfg.readMembers != nil { return cfg.readMembers }; return &ReadMembersServiceStub{} }(), + Search : func() SearchService { if cfg.search != nil { return cfg.search }; return &SearchServiceStub{} }(), + Security : func() SecurityService { if cfg.security != nil { return cfg.security }; return &SecurityServiceStub{} }(), + Tasks : func() TasksService { if cfg.tasks != nil { return cfg.tasks }; return &TasksServiceStub{} }(), + Threads : func() ThreadsService { if cfg.threads != nil { return cfg.threads }; return &ThreadsServiceStub{} }(), + Users : func() UsersService { if cfg.users != nil { return cfg.users }; return &UsersServiceStub{} }(), + Views : func() ViewsService { if cfg.views != nil { return cfg.views }; return &ViewsServiceStub{} }(), + } +} diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt index 9cd1de24..23e2e025 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt @@ -1641,63 +1641,123 @@ class ViewsServiceImpl internal constructor( } } -class PachcaClient( - token: String, - baseUrl: String = "https://api.pachca.com/api/shared/v1", - bots: BotsService? = null, - chats: ChatsService? = null, - common: CommonService? = null, - groupTags: GroupTagsService? = null, - linkPreviews: LinkPreviewsService? = null, - members: MembersService? = null, - messages: MessagesService? = null, - profile: ProfileService? = null, - reactions: ReactionsService? = null, - readMembers: ReadMembersService? = null, - search: SearchService? = null, - security: SecurityService? = null, - tasks: TasksService? = null, - threads: ThreadsService? = null, - users: UsersService? = null, - views: ViewsService? = null +class PachcaClient private constructor( + private val client: HttpClient?, + val bots: BotsService, + val chats: ChatsService, + val common: CommonService, + val groupTags: GroupTagsService, + val linkPreviews: LinkPreviewsService, + val members: MembersService, + val messages: MessagesService, + val profile: ProfileService, + val reactions: ReactionsService, + val readMembers: ReadMembersService, + val search: SearchService, + val security: SecurityService, + val tasks: TasksService, + val threads: ThreadsService, + val users: UsersService, + val views: ViewsService ) : Closeable { - private val client = HttpClient { - expectSuccess = false - followRedirects = false - install(ContentNegotiation) { - json(Json { explicitNulls = false }) - } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = 3) - retryIf { _, response -> response.status.value == 429 } - delayMillis { retry -> - val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() - if (retryAfter != null) retryAfter * 1000L else retry * 1000L - } + + companion object { + operator fun invoke( + token: String, + baseUrl: String = "https://api.pachca.com/api/shared/v1", + bots: BotsService? = null, + chats: ChatsService? = null, + common: CommonService? = null, + groupTags: GroupTagsService? = null, + linkPreviews: LinkPreviewsService? = null, + members: MembersService? = null, + messages: MessagesService? = null, + profile: ProfileService? = null, + reactions: ReactionsService? = null, + readMembers: ReadMembersService? = null, + search: SearchService? = null, + security: SecurityService? = null, + tasks: TasksService? = null, + threads: ThreadsService? = null, + users: UsersService? = null, + views: ViewsService? = null + ): PachcaClient { + val client = createClient(token) + return PachcaClient( + client = client, + bots = bots ?: BotsServiceImpl(baseUrl, client), + chats = chats ?: ChatsServiceImpl(baseUrl, client), + common = common ?: CommonServiceImpl(baseUrl, client), + groupTags = groupTags ?: GroupTagsServiceImpl(baseUrl, client), + linkPreviews = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client), + members = members ?: MembersServiceImpl(baseUrl, client), + messages = messages ?: MessagesServiceImpl(baseUrl, client), + profile = profile ?: ProfileServiceImpl(baseUrl, client), + reactions = reactions ?: ReactionsServiceImpl(baseUrl, client), + readMembers = readMembers ?: ReadMembersServiceImpl(baseUrl, client), + search = search ?: SearchServiceImpl(baseUrl, client), + security = security ?: SecurityServiceImpl(baseUrl, client), + tasks = tasks ?: TasksServiceImpl(baseUrl, client), + threads = threads ?: ThreadsServiceImpl(baseUrl, client), + users = users ?: UsersServiceImpl(baseUrl, client), + views = views ?: ViewsServiceImpl(baseUrl, client) + ) } - defaultRequest { - bearerAuth(token) + + fun stub( + bots: BotsService = BotsService(), + chats: ChatsService = ChatsService(), + common: CommonService = CommonService(), + groupTags: GroupTagsService = GroupTagsService(), + linkPreviews: LinkPreviewsService = LinkPreviewsService(), + members: MembersService = MembersService(), + messages: MessagesService = MessagesService(), + profile: ProfileService = ProfileService(), + reactions: ReactionsService = ReactionsService(), + readMembers: ReadMembersService = ReadMembersService(), + search: SearchService = SearchService(), + security: SecurityService = SecurityService(), + tasks: TasksService = TasksService(), + threads: ThreadsService = ThreadsService(), + users: UsersService = UsersService(), + views: ViewsService = ViewsService() + ): PachcaClient = PachcaClient( + client = null, + bots = bots, + chats = chats, + common = common, + groupTags = groupTags, + linkPreviews = linkPreviews, + members = members, + messages = messages, + profile = profile, + reactions = reactions, + readMembers = readMembers, + search = search, + security = security, + tasks = tasks, + threads = threads, + users = users, + views = views + ) + + private fun createClient(token: String): HttpClient = HttpClient { + expectSuccess = false + followRedirects = false + install(ContentNegotiation) { json(Json { explicitNulls = false }) } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 3) + retryIf { _, response -> response.status.value == 429 } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null) retryAfter * 1000L else retry * 1000L + } + } + defaultRequest { bearerAuth(token) } } } - val bots: BotsService = bots ?: BotsServiceImpl(baseUrl, client) - val chats: ChatsService = chats ?: ChatsServiceImpl(baseUrl, client) - val common: CommonService = common ?: CommonServiceImpl(baseUrl, client) - val groupTags: GroupTagsService = groupTags ?: GroupTagsServiceImpl(baseUrl, client) - val linkPreviews: LinkPreviewsService = linkPreviews ?: LinkPreviewsServiceImpl(baseUrl, client) - val members: MembersService = members ?: MembersServiceImpl(baseUrl, client) - val messages: MessagesService = messages ?: MessagesServiceImpl(baseUrl, client) - val profile: ProfileService = profile ?: ProfileServiceImpl(baseUrl, client) - val reactions: ReactionsService = reactions ?: ReactionsServiceImpl(baseUrl, client) - val readMembers: ReadMembersService = readMembers ?: ReadMembersServiceImpl(baseUrl, client) - val search: SearchService = search ?: SearchServiceImpl(baseUrl, client) - val security: SecurityService = security ?: SecurityServiceImpl(baseUrl, client) - val tasks: TasksService = tasks ?: TasksServiceImpl(baseUrl, client) - val threads: ThreadsService = threads ?: ThreadsServiceImpl(baseUrl, client) - val users: UsersService = users ?: UsersServiceImpl(baseUrl, client) - val views: ViewsService = views ?: ViewsServiceImpl(baseUrl, client) - override fun close() { - client.close() + client?.close() } } diff --git a/sdk/python/generated/pachca/client.py b/sdk/python/generated/pachca/client.py index ba21c617..ece8318d 100644 --- a/sdk/python/generated/pachca/client.py +++ b/sdk/python/generated/pachca/client.py @@ -2125,3 +2125,43 @@ def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/share async def close(self) -> None: await self._client.aclose() + + @classmethod + def stub( + cls, + bots: BotsService | None = None, + chats: ChatsService | None = None, + common: CommonService | None = None, + group_tags: GroupTagsService | None = None, + link_previews: LinkPreviewsService | None = None, + members: MembersService | None = None, + messages: MessagesService | None = None, + profile: ProfileService | None = None, + reactions: ReactionsService | None = None, + read_members: ReadMembersService | None = None, + search: SearchService | None = None, + security: SecurityService | None = None, + tasks: TasksService | None = None, + threads: ThreadsService | None = None, + users: UsersService | None = None, + views: ViewsService | None = None, + ) -> "PachcaClient": + self = cls.__new__(cls) + self._client = None + self.bots = bots or BotsService() + self.chats = chats or ChatsService() + self.common = common or CommonService() + self.group_tags = group_tags or GroupTagsService() + self.link_previews = link_previews or LinkPreviewsService() + self.members = members or MembersService() + self.messages = messages or MessagesService() + self.profile = profile or ProfileService() + self.reactions = reactions or ReactionsService() + self.read_members = read_members or ReadMembersService() + self.search = search or SearchService() + self.security = security or SecurityService() + self.tasks = tasks or TasksService() + self.threads = threads or ThreadsService() + self.users = users or UsersService() + self.views = views or ViewsService() + return self diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift index d91652a4..67e6670f 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift @@ -1938,23 +1938,65 @@ public struct PachcaClient { public let users: UsersService public let views: ViewsService + private init(bots: BotsService, chats: ChatsService, common: CommonService, groupTags: GroupTagsService, linkPreviews: LinkPreviewsService, members: MembersService, messages: MessagesService, profile: ProfileService, reactions: ReactionsService, readMembers: ReadMembersService, search: SearchService, security: SecurityService, tasks: TasksService, threads: ThreadsService, users: UsersService, views: ViewsService) { + self.bots = bots + self.chats = chats + self.common = common + self.groupTags = groupTags + self.linkPreviews = linkPreviews + self.members = members + self.messages = messages + self.profile = profile + self.reactions = reactions + self.readMembers = readMembers + self.search = search + self.security = security + self.tasks = tasks + self.threads = threads + self.users = users + self.views = views + } + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1", bots: BotsService? = nil, chats: ChatsService? = nil, common: CommonService? = nil, groupTags: GroupTagsService? = nil, linkPreviews: LinkPreviewsService? = nil, members: MembersService? = nil, messages: MessagesService? = nil, profile: ProfileService? = nil, reactions: ReactionsService? = nil, readMembers: ReadMembersService? = nil, search: SearchService? = nil, security: SecurityService? = nil, tasks: TasksService? = nil, threads: ThreadsService? = nil, users: UsersService? = nil, views: ViewsService? = nil) { let headers = ["Authorization": "Bearer \(token)"] - self.bots = bots ?? BotsServiceImpl(baseURL: baseURL, headers: headers) - self.chats = chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers) - self.common = common ?? CommonServiceImpl(baseURL: baseURL, headers: headers) - self.groupTags = groupTags ?? GroupTagsServiceImpl(baseURL: baseURL, headers: headers) - self.linkPreviews = linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers) - self.members = members ?? MembersServiceImpl(baseURL: baseURL, headers: headers) - self.messages = messages ?? MessagesServiceImpl(baseURL: baseURL, headers: headers) - self.profile = profile ?? ProfileServiceImpl(baseURL: baseURL, headers: headers) - self.reactions = reactions ?? ReactionsServiceImpl(baseURL: baseURL, headers: headers) - self.readMembers = readMembers ?? ReadMembersServiceImpl(baseURL: baseURL, headers: headers) - self.search = search ?? SearchServiceImpl(baseURL: baseURL, headers: headers) - self.security = security ?? SecurityServiceImpl(baseURL: baseURL, headers: headers) - self.tasks = tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers) - self.threads = threads ?? ThreadsServiceImpl(baseURL: baseURL, headers: headers) - self.users = users ?? UsersServiceImpl(baseURL: baseURL, headers: headers) - self.views = views ?? ViewsServiceImpl(baseURL: baseURL, headers: headers) + self.init( + bots: bots ?? BotsServiceImpl(baseURL: baseURL, headers: headers), + chats: chats ?? ChatsServiceImpl(baseURL: baseURL, headers: headers), + common: common ?? CommonServiceImpl(baseURL: baseURL, headers: headers), + groupTags: groupTags ?? GroupTagsServiceImpl(baseURL: baseURL, headers: headers), + linkPreviews: linkPreviews ?? LinkPreviewsServiceImpl(baseURL: baseURL, headers: headers), + members: members ?? MembersServiceImpl(baseURL: baseURL, headers: headers), + messages: messages ?? MessagesServiceImpl(baseURL: baseURL, headers: headers), + profile: profile ?? ProfileServiceImpl(baseURL: baseURL, headers: headers), + reactions: reactions ?? ReactionsServiceImpl(baseURL: baseURL, headers: headers), + readMembers: readMembers ?? ReadMembersServiceImpl(baseURL: baseURL, headers: headers), + search: search ?? SearchServiceImpl(baseURL: baseURL, headers: headers), + security: security ?? SecurityServiceImpl(baseURL: baseURL, headers: headers), + tasks: tasks ?? TasksServiceImpl(baseURL: baseURL, headers: headers), + threads: threads ?? ThreadsServiceImpl(baseURL: baseURL, headers: headers), + users: users ?? UsersServiceImpl(baseURL: baseURL, headers: headers), + views: views ?? ViewsServiceImpl(baseURL: baseURL, headers: headers) + ) + } + + public static func stub(bots: BotsService = BotsService(), chats: ChatsService = ChatsService(), common: CommonService = CommonService(), groupTags: GroupTagsService = GroupTagsService(), linkPreviews: LinkPreviewsService = LinkPreviewsService(), members: MembersService = MembersService(), messages: MessagesService = MessagesService(), profile: ProfileService = ProfileService(), reactions: ReactionsService = ReactionsService(), readMembers: ReadMembersService = ReadMembersService(), search: SearchService = SearchService(), security: SecurityService = SecurityService(), tasks: TasksService = TasksService(), threads: ThreadsService = ThreadsService(), users: UsersService = UsersService(), views: ViewsService = ViewsService()) -> PachcaClient { + PachcaClient( + bots: bots, + chats: chats, + common: common, + groupTags: groupTags, + linkPreviews: linkPreviews, + members: members, + messages: messages, + profile: profile, + reactions: reactions, + readMembers: readMembers, + search: search, + security: security, + tasks: tasks, + threads: threads, + users: users, + views: views + ) } } diff --git a/sdk/typescript/src/generated/client.ts b/sdk/typescript/src/generated/client.ts index 52a609e5..2fa03e2a 100644 --- a/sdk/typescript/src/generated/client.ts +++ b/sdk/typescript/src/generated/client.ts @@ -1853,4 +1853,25 @@ export class PachcaClient { this.users = options.users ?? new UsersServiceImpl(baseUrl, headers); this.views = options.views ?? new ViewsServiceImpl(baseUrl, headers); } + + static stub(options: Partial = {}): PachcaClient { + return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", + bots: options.bots ?? new BotsService(), + chats: options.chats ?? new ChatsService(), + common: options.common ?? new CommonService(), + groupTags: options.groupTags ?? new GroupTagsService(), + linkPreviews: options.linkPreviews ?? new LinkPreviewsService(), + members: options.members ?? new MembersService(), + messages: options.messages ?? new MessagesService(), + profile: options.profile ?? new ProfileService(), + reactions: options.reactions ?? new ReactionsService(), + readMembers: options.readMembers ?? new ReadMembersService(), + search: options.search ?? new SearchService(), + security: options.security ?? new SecurityService(), + tasks: options.tasks ?? new TasksService(), + threads: options.threads ?? new ThreadsService(), + users: options.users ?? new UsersService(), + views: options.views ?? new ViewsService(), + }); + } } From 8173d918c703e46ff082e7d2634f96cb413ec4bb Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 22:55:27 +0100 Subject: [PATCH 5/8] docs: add testing examples with stub clients to SDK READMEs Document PachcaClient.stub() usage for unit testing with mock services. Each README now includes language-specific examples and describes what happens when calling non-overridden methods (throws/returns error). Co-Authored-By: Claude Opus 4.5 --- sdk/csharp/generated/README.md | 23 ++++++++++++++++++++ sdk/go/README.md | 39 ++++++++++++++++++++++++++++++++++ sdk/kotlin/README.md | 26 +++++++++++++++++++++++ sdk/python/generated/README.md | 25 ++++++++++++++++++++++ sdk/swift/README.md | 28 ++++++++++++++++++++++++ sdk/typescript/README.md | 25 ++++++++++++++++++++++ 6 files changed, 166 insertions(+) diff --git a/sdk/csharp/generated/README.md b/sdk/csharp/generated/README.md index 90e201e9..0c99d578 100644 --- a/sdk/csharp/generated/README.md +++ b/sdk/csharp/generated/README.md @@ -121,3 +121,26 @@ catch (ApiError e) using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var users = await client.Users.ListUsersAsync(cancellationToken: cts.Token); ``` + +## Тестирование + +Для unit-тестов используйте `PachcaClient.Stub()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения выбрасывают `NotImplementedException("Service.method is not implemented")`: + +```csharp +using Moq; +using Pachca.Sdk; + +// Мок-сервис +var mockMessages = new Mock(); +mockMessages + .Setup(m => m.GetMessageAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new Message { Id = 1, Content = "Test message", EntityId = 123 }); + +// Тест +var client = PachcaClient.Stub(messages: mockMessages.Object); + +var message = await client.Messages.GetMessageAsync(1); +Assert.Equal("Test message", message.Content); +``` diff --git a/sdk/go/README.md b/sdk/go/README.md index bb0b5c45..a13f934f 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -116,3 +116,42 @@ if err != nil { } } ``` + +## Тестирование + +Для unit-тестов используйте `NewStubPachcaClient()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения возвращают `error` с сообщением `"Service.method is not implemented"`: + +```go +import ( + "context" + "testing" + + pachca "github.com/pachca/openapi/sdk/go/generated" +) + +// Мок-сервис +type mockMessagesService struct { + pachca.MessagesService +} + +func (m *mockMessagesService) GetMessage(ctx context.Context, id int64) (*pachca.Message, error) { + return &pachca.Message{ID: id, Content: "Test message", EntityID: 123}, nil +} + +// Тест +func TestGetMessage(t *testing.T) { + client := pachca.NewStubPachcaClient( + pachca.WithStubMessages(&mockMessagesService{}), + ) + + msg, err := client.Messages.GetMessage(context.Background(), 1) + if err != nil { + t.Fatal(err) + } + if msg.Content != "Test message" { + t.Errorf("expected 'Test message', got %q", msg.Content) + } +} +``` diff --git a/sdk/kotlin/README.md b/sdk/kotlin/README.md index 52ae08c5..fc4fab4c 100644 --- a/sdk/kotlin/README.md +++ b/sdk/kotlin/README.md @@ -111,3 +111,29 @@ try { println("Ошибка API: ${e.errors}") } ``` + +## Тестирование + +Для unit-тестов используйте `PachcaClient.stub()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения выбрасывают `NotImplementedError("Service.method is not implemented")`: + +```kotlin +import com.pachca.sdk.* +import io.mockk.coEvery +import io.mockk.mockk + +// Мок-сервис +val mockMessages = mockk() +coEvery { mockMessages.getMessage(any()) } returns Message( + id = 1, + content = "Test message", + entityId = 123 +) + +// Тест +val client = PachcaClient.stub(messages = mockMessages) + +val message = client.messages.getMessage(1) +assertEquals("Test message", message.content) +``` diff --git a/sdk/python/generated/README.md b/sdk/python/generated/README.md index 8fbc3ffc..63832e44 100644 --- a/sdk/python/generated/README.md +++ b/sdk/python/generated/README.md @@ -103,3 +103,28 @@ except OAuthError as e: except ApiError as e: print(f"Ошибка API: {e.errors}") ``` + +## Тестирование + +Для unit-тестов используйте `PachcaClient.stub()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения выбрасывают `NotImplementedError("Service.method is not implemented")`: + +```python +from unittest.mock import AsyncMock +from pachca import PachcaClient, MessagesService, Message + +# Мок-сервис +mock_messages = MessagesService() +mock_messages.get_message = AsyncMock(return_value=Message( + id=1, + content="Test message", + entity_id=123 +)) + +# Тест +client = PachcaClient.stub(messages=mock_messages) + +message = await client.messages.get_message(1) +assert message.content == "Test message" +``` diff --git a/sdk/swift/README.md b/sdk/swift/README.md index 3296d6fc..536d9a63 100644 --- a/sdk/swift/README.md +++ b/sdk/swift/README.md @@ -110,3 +110,31 @@ do { print("Ошибка авторизации: \(error.message)") } ``` + +## Тестирование + +Для unit-тестов используйте `PachcaClient.stub()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения выбрасывают `NSError` с описанием `"Service.method is not implemented"`: + +```swift +import XCTest +import PachcaSDK + +// Мок-сервис +class MockMessagesService: MessagesService { + override func getMessage(_ id: Int64) async throws -> Message { + return Message(id: id, content: "Test message", entityId: 123) + } +} + +// Тест +final class MessagesTests: XCTestCase { + func testGetMessage() async throws { + let client = PachcaClient.stub(messages: MockMessagesService()) + + let message = try await client.messages.getMessage(1) + XCTAssertEqual(message.content, "Test message") + } +} +``` diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 43c26520..dc327c19 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -95,3 +95,28 @@ try { } } ``` + +## Тестирование + +Для unit-тестов используйте `PachcaClient.stub()` — создаёт клиент без HTTP-подключения. + +Методы без переопределения выбрасывают `Error("Service.method is not implemented")`: + +```typescript +import { PachcaClient, MessagesService, Message } from "@pachca/sdk"; + +// Мок-сервис +class MockMessagesService extends MessagesService { + async getMessage(id: number): Promise { + return { id, content: "Test message", entityId: 123 } as Message; + } +} + +// Тест +const client = PachcaClient.stub({ + messages: new MockMessagesService(), +}); + +const message = await client.messages.getMessage(1); +expect(message.content).toBe("Test message"); +``` From baf2dd0f14b12e345aa10ffa83c00a7fc8718bde Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 23:04:43 +0100 Subject: [PATCH 6/8] fix(csharp): add missing System.Collections.Generic import for HashSet Also adds jitter to retry delays and uses explicit 5xx status codes (500, 502, 503, 504) instead of >= 500 range. Co-Authored-By: Claude Opus 4.5 --- packages/generator/src/lang/csharp.ts | 15 ++++++++++++--- .../additional-props-bool/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/allof-sibling/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/circular-ref/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/crud/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/deep-nesting/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/edge-cases/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/enums/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/models/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/multi-path-params/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/nullable-ref/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/oneof/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/patch/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/record/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/redirect/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../tests/reserved-keywords/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/search/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/unions/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/unwrap/snapshots/cs/Utils.cs | 15 ++++++++++++--- .../generator/tests/upload/snapshots/cs/Utils.cs | 15 ++++++++++++--- sdk/csharp/generated/Utils.cs | 15 ++++++++++++--- 21 files changed, 252 insertions(+), 63 deletions(-) diff --git a/packages/generator/src/lang/csharp.ts b/packages/generator/src/lang/csharp.ts index 5de512a0..f7d083d7 100644 --- a/packages/generator/src/lang/csharp.ts +++ b/packages/generator/src/lang/csharp.ts @@ -403,6 +403,7 @@ function generateUtils(): string { return `#nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -416,6 +417,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -423,6 +426,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -446,15 +455,15 @@ internal static class PachcaUtils { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs b/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs +++ b/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs b/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs +++ b/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs b/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs +++ b/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/crud/snapshots/cs/Utils.cs b/packages/generator/tests/crud/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/crud/snapshots/cs/Utils.cs +++ b/packages/generator/tests/crud/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs b/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs +++ b/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs b/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/enums/snapshots/cs/Utils.cs b/packages/generator/tests/enums/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/enums/snapshots/cs/Utils.cs +++ b/packages/generator/tests/enums/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/models/snapshots/cs/Utils.cs b/packages/generator/tests/models/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/models/snapshots/cs/Utils.cs +++ b/packages/generator/tests/models/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs b/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs +++ b/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs b/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs +++ b/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/oneof/snapshots/cs/Utils.cs b/packages/generator/tests/oneof/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/oneof/snapshots/cs/Utils.cs +++ b/packages/generator/tests/oneof/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/patch/snapshots/cs/Utils.cs b/packages/generator/tests/patch/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/patch/snapshots/cs/Utils.cs +++ b/packages/generator/tests/patch/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/record/snapshots/cs/Utils.cs b/packages/generator/tests/record/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/record/snapshots/cs/Utils.cs +++ b/packages/generator/tests/record/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/redirect/snapshots/cs/Utils.cs b/packages/generator/tests/redirect/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/redirect/snapshots/cs/Utils.cs +++ b/packages/generator/tests/redirect/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs b/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs +++ b/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/search/snapshots/cs/Utils.cs b/packages/generator/tests/search/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/search/snapshots/cs/Utils.cs +++ b/packages/generator/tests/search/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/unions/snapshots/cs/Utils.cs b/packages/generator/tests/unions/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/unions/snapshots/cs/Utils.cs +++ b/packages/generator/tests/unions/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/unwrap/snapshots/cs/Utils.cs b/packages/generator/tests/unwrap/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/unwrap/snapshots/cs/Utils.cs +++ b/packages/generator/tests/unwrap/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/packages/generator/tests/upload/snapshots/cs/Utils.cs b/packages/generator/tests/upload/snapshots/cs/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/packages/generator/tests/upload/snapshots/cs/Utils.cs +++ b/packages/generator/tests/upload/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } diff --git a/sdk/csharp/generated/Utils.cs b/sdk/csharp/generated/Utils.cs index 8b913ac8..482fcaaf 100644 --- a/sdk/csharp/generated/Utils.cs +++ b/sdk/csharp/generated/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -14,6 +15,8 @@ namespace Pachca.Sdk; internal static class PachcaUtils { private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); internal static readonly JsonSerializerOptions JsonOptions = new() { @@ -21,6 +24,12 @@ internal static class PachcaUtils PropertyNameCaseInsensitive = true, }; + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + internal static async Task SendWithRetryAsync( HttpClient client, HttpRequestMessage request, @@ -44,15 +53,15 @@ internal static async Task SendWithRetryAsync( { var delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } - if ((int)response.StatusCode >= 500 && attempt < MaxRetries) + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) { var delay = TimeSpan.FromSeconds(attempt + 1); - await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + await System.Threading.Tasks.Task.Delay(AddJitter(delay), cancellationToken).ConfigureAwait(false); response.Dispose(); continue; } From 78b985dd228612859b91b568b193563d875d3dd8 Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Fri, 27 Mar 2026 23:50:37 +0100 Subject: [PATCH 7/8] feat(generator): add jitter to retry logic, Kotlin interfaces, TS fixes - Add jitter (0.5-1.0 random factor) to retry delays across all SDKs - Add 5xx retry (500, 502, 503, 504) to TypeScript, Go, Python, Swift - Convert Kotlin services from open class to interface pattern - Revert TypeScript constructor to non-breaking positional args format - Remove TypeScript override keyword for ES5/ES6 compatibility Co-Authored-By: Claude Opus 4.5 --- packages/generator/src/lang/go.ts | 17 ++++++- packages/generator/src/lang/kotlin.ts | 32 ++++++------- packages/generator/src/lang/python.ts | 14 +++++- packages/generator/src/lang/swift.ts | 29 ++++++++---- packages/generator/src/lang/typescript.ts | 46 +++++++++---------- .../snapshots/swift/Utils.swift | 29 ++++++++---- .../snapshots/ts/examples.json | 2 +- .../allof-sibling/snapshots/swift/Utils.swift | 29 ++++++++---- .../allof-sibling/snapshots/ts/examples.json | 2 +- .../circular-ref/snapshots/swift/Utils.swift | 29 ++++++++---- .../circular-ref/snapshots/ts/examples.json | 2 +- .../tests/crud/snapshots/go/client.go | 16 ++++++- .../tests/crud/snapshots/kt/Client.kt | 31 +++++-------- .../tests/crud/snapshots/py/utils.py | 14 +++++- .../tests/crud/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/crud/snapshots/ts/client.ts | 34 ++++++-------- .../tests/crud/snapshots/ts/examples.json | 2 +- .../tests/crud/snapshots/ts/utils.ts | 12 ++++- .../deep-nesting/snapshots/swift/Utils.swift | 29 ++++++++---- .../deep-nesting/snapshots/ts/examples.json | 2 +- .../tests/edge-cases/snapshots/go/client.go | 16 ++++++- .../tests/edge-cases/snapshots/kt/Client.kt | 23 ++++------ .../tests/edge-cases/snapshots/py/utils.py | 14 +++++- .../edge-cases/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/edge-cases/snapshots/ts/client.ts | 31 +++++-------- .../edge-cases/snapshots/ts/examples.json | 2 +- .../tests/edge-cases/snapshots/ts/utils.ts | 12 ++++- .../tests/enums/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/enums/snapshots/ts/examples.json | 2 +- .../tests/models/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/models/snapshots/ts/examples.json | 2 +- .../multi-path-params/snapshots/go/client.go | 16 ++++++- .../multi-path-params/snapshots/kt/Client.kt | 19 ++++---- .../multi-path-params/snapshots/py/utils.py | 14 +++++- .../snapshots/swift/Utils.swift | 29 ++++++++---- .../multi-path-params/snapshots/ts/client.ts | 26 ++++------- .../snapshots/ts/examples.json | 2 +- .../multi-path-params/snapshots/ts/utils.ts | 12 ++++- .../nullable-ref/snapshots/swift/Utils.swift | 29 ++++++++---- .../nullable-ref/snapshots/ts/examples.json | 2 +- .../tests/oneof/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/oneof/snapshots/ts/examples.json | 2 +- .../tests/patch/snapshots/go/client.go | 16 ++++++- .../tests/patch/snapshots/kt/Client.kt | 9 ++-- .../tests/patch/snapshots/py/utils.py | 14 +++++- .../tests/patch/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/patch/snapshots/ts/client.ts | 22 +++------ .../tests/patch/snapshots/ts/examples.json | 2 +- .../tests/patch/snapshots/ts/utils.ts | 12 ++++- .../tests/record/snapshots/go/client.go | 16 ++++++- .../tests/record/snapshots/kt/Client.kt | 9 ++-- .../tests/record/snapshots/py/utils.py | 14 +++++- .../tests/record/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/record/snapshots/ts/client.ts | 22 +++------ .../tests/record/snapshots/ts/examples.json | 2 +- .../tests/record/snapshots/ts/utils.ts | 12 ++++- .../tests/redirect/snapshots/go/client.go | 16 ++++++- .../tests/redirect/snapshots/kt/Client.kt | 9 ++-- .../tests/redirect/snapshots/py/utils.py | 14 +++++- .../redirect/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/redirect/snapshots/ts/client.ts | 22 +++------ .../tests/redirect/snapshots/ts/examples.json | 2 +- .../tests/redirect/snapshots/ts/utils.ts | 12 ++++- .../snapshots/swift/Utils.swift | 29 ++++++++---- .../snapshots/ts/examples.json | 2 +- .../tests/search/snapshots/go/client.go | 16 ++++++- .../tests/search/snapshots/kt/Client.kt | 16 +++---- .../tests/search/snapshots/py/utils.py | 14 +++++- .../tests/search/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/search/snapshots/ts/client.ts | 24 ++++------ .../tests/search/snapshots/ts/examples.json | 2 +- .../tests/search/snapshots/ts/utils.ts | 12 ++++- .../tests/unions/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/unions/snapshots/ts/examples.json | 2 +- .../tests/unwrap/snapshots/go/client.go | 16 ++++++- .../tests/unwrap/snapshots/kt/Client.kt | 21 ++++----- .../tests/unwrap/snapshots/py/utils.py | 14 +++++- .../tests/unwrap/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/unwrap/snapshots/ts/client.ts | 31 +++++-------- .../tests/unwrap/snapshots/ts/examples.json | 2 +- .../tests/unwrap/snapshots/ts/utils.ts | 12 ++++- .../tests/upload/snapshots/go/client.go | 16 ++++++- .../tests/upload/snapshots/kt/Client.kt | 12 ++--- .../tests/upload/snapshots/py/utils.py | 14 +++++- .../tests/upload/snapshots/swift/Utils.swift | 29 ++++++++---- .../tests/upload/snapshots/ts/client.ts | 24 ++++------ .../tests/upload/snapshots/ts/examples.json | 2 +- .../tests/upload/snapshots/ts/utils.ts | 12 ++++- 88 files changed, 990 insertions(+), 500 deletions(-) diff --git a/packages/generator/src/lang/go.ts b/packages/generator/src/lang/go.ts index e212bde5..c44f2af4 100644 --- a/packages/generator/src/lang/go.ts +++ b/packages/generator/src/lang/go.ts @@ -716,7 +716,7 @@ function generateClient(ir: IR): string { const needURL = ir.services.some((s) => s.operations.some((o) => o.queryParams.length > 0)); const needErrors = ir.services.some((s) => s.operations.some((o) => o.successResponse.isRedirect)); const needMultipart = ir.services.some((s) => s.operations.some((o) => o.requestBody?.contentType === 'multipart')); - const imports: string[] = ['"context"', '"encoding/json"', '"fmt"', '"net/http"', '"strconv"', '"time"']; + const imports: string[] = ['"context"', '"encoding/json"', '"fmt"', '"math/rand"', '"net/http"', '"strconv"', '"time"']; if (needBytes) imports.push('"bytes"'); if (needURL) imports.push('"net/url"'); if (needErrors) imports.push('"errors"'); @@ -743,6 +743,13 @@ function generateClient(ir: IR): string { lines.push(''); lines.push('const maxRetries = 3'); lines.push(''); + lines.push('var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true}'); + lines.push(''); + lines.push('func addJitter(delay time.Duration) time.Duration {'); + lines.push('\tfactor := 0.5 + rand.Float64()*0.5'); + lines.push('\treturn time.Duration(float64(delay) * factor)'); + lines.push('}'); + lines.push(''); lines.push('func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) {'); lines.push('\tfor attempt := 0; ; attempt++ {'); lines.push('\t\tif attempt > 0 && req.GetBody != nil {'); @@ -760,7 +767,13 @@ function generateClient(ir: IR): string { lines.push('\t\t\t\t\tdelay = time.Duration(secs) * time.Second'); lines.push('\t\t\t\t}'); lines.push('\t\t\t}'); - lines.push('\t\t\ttime.Sleep(delay)'); + lines.push('\t\t\ttime.Sleep(addJitter(delay))'); + lines.push('\t\t\tcontinue'); + lines.push('\t\t}'); + lines.push('\t\tif retryable5xx[resp.StatusCode] && attempt < maxRetries {'); + lines.push('\t\t\tresp.Body.Close()'); + lines.push('\t\t\tdelay := time.Duration(attempt+1) * time.Second'); + lines.push('\t\t\ttime.Sleep(addJitter(delay))'); lines.push('\t\t\tcontinue'); lines.push('\t\t}'); lines.push('\t\treturn resp, nil'); diff --git a/packages/generator/src/lang/kotlin.ts b/packages/generator/src/lang/kotlin.ts index a0b24e86..bba22ef0 100644 --- a/packages/generator/src/lang/kotlin.ts +++ b/packages/generator/src/lang/kotlin.ts @@ -400,13 +400,13 @@ function emitService( const serviceName = tagToServiceName(svc.tag); const implName = serviceToImplName(serviceName); - lines.push(`open class ${serviceName} {`); + lines.push(`interface ${serviceName} {`); for (let i = 0; i < svc.operations.length; i++) { if (i > 0) lines.push(''); - emitThrowingOperation(lines, svc.operations[i], ir); + emitInterfaceOperation(lines, svc.operations[i], ir); if (svc.operations[i].isPaginated && svc.operations[i].successResponse.dataRef) { lines.push(''); - emitThrowingPaginationMethod(lines, svc.operations[i], ir); + emitInterfacePaginationMethod(lines, svc.operations[i], ir); } } lines.push('}'); @@ -414,7 +414,7 @@ function emitService( lines.push(`class ${implName} internal constructor(`); lines.push(' private val baseUrl: String,'); lines.push(' private val client: HttpClient,'); - lines.push(`) : ${serviceName}() {`); + lines.push(`) : ${serviceName} {`); for (let i = 0; i < svc.operations.length; i++) { if (i > 0) lines.push(''); @@ -428,7 +428,7 @@ function emitService( lines.push('}'); } -function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { +function emitInterfaceOperation(lines: string[], op: IROperation, ir: IR): void { const indent = ' '; const indent2 = ' '; const returnType = getReturnType(op, ir); @@ -437,21 +437,20 @@ function emitThrowingOperation(lines: string[], op: IROperation, ir: IR): void { if (op.deprecated) lines.push(`${indent}@Deprecated("This method is deprecated")`); if (params.length === 0) { - lines.push(`${indent}open suspend fun ${op.methodName}()${returnSuffix} {`); + lines.push(`${indent}suspend fun ${op.methodName}()${returnSuffix} =`); } else if (params.length === 1) { - lines.push(`${indent}open suspend fun ${op.methodName}(${params[0]})${returnSuffix} {`); + lines.push(`${indent}suspend fun ${op.methodName}(${params[0]})${returnSuffix} =`); } else if (params.length <= 2) { - lines.push(`${indent}open suspend fun ${op.methodName}(${params.join(', ')})${returnSuffix} {`); + lines.push(`${indent}suspend fun ${op.methodName}(${params.join(', ')})${returnSuffix} =`); } else { - lines.push(`${indent}open suspend fun ${op.methodName}(`); + lines.push(`${indent}suspend fun ${op.methodName}(`); for (const p of params) lines.push(`${indent2}${p},`); - lines.push(`${indent})${returnSuffix} {`); + lines.push(`${indent})${returnSuffix} =`); } lines.push(`${indent2}throw NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); - lines.push(`${indent}}`); } -function emitThrowingPaginationMethod(lines: string[], op: IROperation, ir: IR): void { +function emitInterfacePaginationMethod(lines: string[], op: IROperation, ir: IR): void { const indent = ' '; const indent2 = ' '; const itemType = op.successResponse.dataRef ?? 'Any'; @@ -465,14 +464,13 @@ function emitThrowingPaginationMethod(lines: string[], op: IROperation, ir: IR): } if (params.length <= 2) { - lines.push(`${indent}open suspend fun ${op.methodName}All(${params.join(', ')}): List<${itemType}> {`); + lines.push(`${indent}suspend fun ${op.methodName}All(${params.join(', ')}): List<${itemType}> =`); } else { - lines.push(`${indent}open suspend fun ${op.methodName}All(`); + lines.push(`${indent}suspend fun ${op.methodName}All(`); for (const p of params) lines.push(`${indent2}${p},`); - lines.push(`${indent}): List<${itemType}> {`); + lines.push(`${indent}): List<${itemType}> =`); } lines.push(`${indent2}throw NotImplementedError(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)})`); - lines.push(`${indent}}`); } function stripKotlinDefaultValue(param: string): string { @@ -892,7 +890,7 @@ function emitPachcaClient( for (let i = 0; i < serviceEntries.length; i++) { const s = serviceEntries[i]; const suffix = i < serviceEntries.length - 1 ? ',' : ''; - lines.push(` ${s.propName}: ${s.className} = ${s.className}()${suffix}`); + lines.push(` ${s.propName}: ${s.className} = object : ${s.className} {}${suffix}`); } lines.push(' ): PachcaClient = PachcaClient('); lines.push(' client = null,'); diff --git a/packages/generator/src/lang/python.ts b/packages/generator/src/lang/python.ts index ad7b3577..579ee02a 100644 --- a/packages/generator/src/lang/python.ts +++ b/packages/generator/src/lang/python.ts @@ -878,10 +878,16 @@ function generateUtils(): string { '', '', '_MAX_RETRIES = 3', + '_RETRYABLE_5XX = {500, 502, 503, 504}', + '', + '', + 'def _add_jitter(delay: float) -> float:', + ' import random', + ' return delay * (0.5 + random.random() * 0.5)', '', '', 'class RetryTransport(httpx.AsyncBaseTransport):', - ' """Wraps an httpx transport with retry on 429 Too Many Requests."""', + ' """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors."""', '', ' def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None:', ' self._transport = transport', @@ -894,7 +900,11 @@ function generateUtils(): string { ' if response.status_code == 429 and attempt < self._max_retries:', ' retry_after = response.headers.get("retry-after")', ' delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt', - ' await asyncio.sleep(delay)', + ' await asyncio.sleep(_add_jitter(delay))', + ' continue', + ' if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries:', + ' delay = attempt + 1', + ' await asyncio.sleep(_add_jitter(delay))', ' continue', ' return response', ' return response # unreachable', diff --git a/packages/generator/src/lang/swift.ts b/packages/generator/src/lang/swift.ts index 920a7cda..d21e43c8 100644 --- a/packages/generator/src/lang/swift.ts +++ b/packages/generator/src/lang/swift.ts @@ -675,19 +675,32 @@ function generateUtils(ir: IR): string { lines.push( 'private let maxRetries = 3', + 'private let retryable5xx: Set = [500, 502, 503, 504]', + '', + 'private func addJitter(_ delay: UInt64) -> UInt64 {', + ' let factor = 0.5 + Double.random(in: 0..<0.5)', + ' return UInt64(Double(delay) * factor)', + '}', '', 'func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) {', ' for attempt in 0...maxRetries {', ' let (data, response) = try await session.data(for: request, delegate: delegate)', - ' if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries {', - ' let delay: UInt64', - ' if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) {', - ' delay = secs * 1_000_000_000', - ' } else {', - ' delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000', + ' if let http = response as? HTTPURLResponse {', + ' if http.statusCode == 429, attempt < maxRetries {', + ' let delay: UInt64', + ' if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) {', + ' delay = secs * 1_000_000_000', + ' } else {', + ' delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000', + ' }', + ' try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay))', + ' continue', + ' }', + ' if retryable5xx.contains(http.statusCode), attempt < maxRetries {', + ' let delay = UInt64(attempt + 1) * 1_000_000_000', + ' try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay))', + ' continue', ' }', - ' try await _Concurrency.Task.sleep(nanoseconds: delay)', - ' continue', ' }', ' return (data, response)', ' }', diff --git a/packages/generator/src/lang/typescript.ts b/packages/generator/src/lang/typescript.ts index cbfa32b6..191ff061 100644 --- a/packages/generator/src/lang/typescript.ts +++ b/packages/generator/src/lang/typescript.ts @@ -469,35 +469,25 @@ function generateClient(ir: IR): { content: string; needsUtils: boolean } { const serviceEntries = ir.services .map((s) => ({ prop: tagToProperty(s.tag), cls: tagToServiceName(s.tag) })) .sort((a, b) => a.prop.localeCompare(b.prop)); - lines.push('export interface PachcaClientOptions {'); - lines.push(' token: string;'); - lines.push(` baseUrl${ir.baseUrl ? '?' : ''}: string;`); - for (const s of serviceEntries) lines.push(` ${s.prop}?: ${s.cls};`); - lines.push('}'); - lines.push(''); lines.push('export class PachcaClient {'); for (const s of serviceEntries) lines.push(` readonly ${s.prop}: ${s.cls};`); lines.push(''); - lines.push(' constructor(options: PachcaClientOptions) {'); - lines.push(' const { token } = options;'); - if (ir.baseUrl) { - lines.push(` const baseUrl = options.baseUrl ?? ${JSON.stringify(ir.baseUrl)};`); - } else { - lines.push(' const { baseUrl } = options;'); - } + const defaultUrl = ir.baseUrl ? ` = ${JSON.stringify(ir.baseUrl)}` : ''; + lines.push(` constructor(token: string, baseUrl: string${defaultUrl}) {`); lines.push(' const headers = { Authorization: `Bearer ${token}` };'); for (const s of serviceEntries) { - lines.push(` this.${s.prop} = options.${s.prop} ?? new ${serviceToImplName(s.cls)}(baseUrl, headers);`); + lines.push(` this.${s.prop} = new ${serviceToImplName(s.cls)}(baseUrl, headers);`); } lines.push(' }'); lines.push(''); - lines.push(' static stub(options: Partial = {}): PachcaClient {'); - const defaultBaseUrl = ir.baseUrl ? JSON.stringify(ir.baseUrl) : '""'; - lines.push(` return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? ${defaultBaseUrl},`); + // Static stub() factory method + const stubArgs = serviceEntries.map((s) => `${s.prop}: ${s.cls} = new ${s.cls}()`); + lines.push(` static stub(${stubArgs.join(', ')}): PachcaClient {`); + lines.push(' const client = Object.create(PachcaClient.prototype);'); for (const s of serviceEntries) { - lines.push(` ${s.prop}: options.${s.prop} ?? new ${s.cls}(),`); + lines.push(` client.${s.prop} = ${s.prop};`); } - lines.push(' });'); + lines.push(' return client;'); lines.push(' }'); lines.push('}'); } @@ -571,7 +561,7 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { const ret = responseTypeName(op, ir); const path = escapeTemplatePath(op.path, op); if (op.deprecated) lines.push(' /** @deprecated */'); - lines.push(` override async ${op.methodName}(${args}): Promise<${ret}> {`); + lines.push(` async ${op.methodName}(${args}): Promise<${ret}> {`); if (op.requestBody?.contentType === 'multipart') { lines.push(' const form = new FormData();'); @@ -706,7 +696,7 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { args.push(hasRequired ? `params: Omit<${paramsType}, 'cursor'>` : `params?: Omit<${paramsType}, 'cursor'>`); } - lines.push(` override async ${op.methodName}All(${args.join(', ')}): Promise<${itemType}[]> {`); + lines.push(` async ${op.methodName}All(${args.join(', ')}): Promise<${itemType}[]> {`); lines.push(` const items: ${itemType}[] = [];`); lines.push(' let cursor: string | undefined;'); lines.push(' do {'); @@ -861,6 +851,11 @@ function generateUtils(ir: IR): string { return [...lines, '', 'const MAX_RETRIES = 3;', + 'const RETRYABLE_5XX = new Set([500, 502, 503, 504]);', + '', + 'function addJitter(delay: number): number {', + ' return delay * (0.5 + Math.random() * 0.5);', + '}', '', 'export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise {', ' for (let attempt = 0; ; attempt++) {', @@ -868,7 +863,12 @@ function generateUtils(ir: IR): string { ' if (response.status === 429 && attempt < MAX_RETRIES) {', ' const retryAfter = response.headers.get("retry-after");', ' const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt);', - ' await new Promise((r) => setTimeout(r, delay));', + ' await new Promise((r) => setTimeout(r, addJitter(delay)));', + ' continue;', + ' }', + ' if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) {', + ' const delay = 1000 * (attempt + 1);', + ' await new Promise((r) => setTimeout(r, addJitter(delay)));', ' continue;', ' }', ' return response;', @@ -1141,7 +1141,7 @@ function generateExamples(ir: IR): string { const result: Record = {}; result['Client_Init'] = { - usage: 'const client = new PachcaClient({ token: "YOUR_TOKEN" })', + usage: 'const client = new PachcaClient("YOUR_TOKEN")', imports: ['PachcaClient'], }; diff --git a/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift b/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift index 3b0a2240..16901971 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift +++ b/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift @@ -58,19 +58,32 @@ public struct AnyCodable: Codable { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json b/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json +++ b/packages/generator/tests/additional-props-bool/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift b/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift +++ b/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/allof-sibling/snapshots/ts/examples.json b/packages/generator/tests/allof-sibling/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/allof-sibling/snapshots/ts/examples.json +++ b/packages/generator/tests/allof-sibling/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift b/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift +++ b/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/circular-ref/snapshots/ts/examples.json b/packages/generator/tests/circular-ref/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/circular-ref/snapshots/ts/examples.json +++ b/packages/generator/tests/circular-ref/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/crud/snapshots/go/client.go b/packages/generator/tests/crud/snapshots/go/client.go index 70d059c7..b32dc8ad 100644 --- a/packages/generator/tests/crud/snapshots/go/client.go +++ b/packages/generator/tests/crud/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "net/url" "strconv" @@ -23,6 +24,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -40,7 +48,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/crud/snapshots/kt/Client.kt b/packages/generator/tests/crud/snapshots/kt/Client.kt index 22d8fe9c..47b329f5 100644 --- a/packages/generator/tests/crud/snapshots/kt/Client.kt +++ b/packages/generator/tests/crud/snapshots/kt/Client.kt @@ -13,51 +13,44 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class ChatsService { - open suspend fun listChats( +interface ChatsService { + suspend fun listChats( availability: ChatAvailability? = null, limit: Int? = null, cursor: String? = null, sortField: String? = null, sortOrder: SortOrder? = null, - ): ListChatsResponse { + ): ListChatsResponse = throw NotImplementedError("Chats.listChats is not implemented") - } - open suspend fun listChatsAll( + suspend fun listChatsAll( availability: ChatAvailability? = null, limit: Int? = null, sortField: String? = null, sortOrder: SortOrder? = null, - ): List { + ): List = throw NotImplementedError("Chats.listChatsAll is not implemented") - } - open suspend fun getChat(id: Int): Chat { + suspend fun getChat(id: Int): Chat = throw NotImplementedError("Chats.getChat is not implemented") - } - open suspend fun createChat(request: ChatCreateRequest): Chat { + suspend fun createChat(request: ChatCreateRequest): Chat = throw NotImplementedError("Chats.createChat is not implemented") - } - open suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat = throw NotImplementedError("Chats.updateChat is not implemented") - } - open suspend fun archiveChat(id: Int) { + suspend fun archiveChat(id: Int) = throw NotImplementedError("Chats.archiveChat is not implemented") - } - open suspend fun deleteChat(id: Int) { + suspend fun deleteChat(id: Int) = throw NotImplementedError("Chats.deleteChat is not implemented") - } } class ChatsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ChatsService() { +) : ChatsService { override suspend fun listChats( availability: ChatAvailability?, limit: Int?, @@ -172,7 +165,7 @@ class PachcaClient private constructor( } fun stub( - chats: ChatsService = ChatsService() + chats: ChatsService = object : ChatsService {} ): PachcaClient = PachcaClient( client = null, chats = chats diff --git a/packages/generator/tests/crud/snapshots/py/utils.py b/packages/generator/tests/crud/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/crud/snapshots/py/utils.py +++ b/packages/generator/tests/crud/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/crud/snapshots/swift/Utils.swift b/packages/generator/tests/crud/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/crud/snapshots/swift/Utils.swift +++ b/packages/generator/tests/crud/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/crud/snapshots/ts/client.ts b/packages/generator/tests/crud/snapshots/ts/client.ts index c5a92055..60ea846b 100644 --- a/packages/generator/tests/crud/snapshots/ts/client.ts +++ b/packages/generator/tests/crud/snapshots/ts/client.ts @@ -47,7 +47,7 @@ export class ChatsServiceImpl extends ChatsService { super(); } - override async listChats(params?: ListChatsParams): Promise { + async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); if (params?.availability !== undefined) query.set("availability", params.availability); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -69,7 +69,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async listChatsAll(params?: Omit): Promise { + async listChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -80,7 +80,7 @@ export class ChatsServiceImpl extends ChatsService { return items; } - override async getChat(id: number): Promise { + async getChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { headers: this.headers, }); @@ -95,7 +95,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async createChat(request: ChatCreateRequest): Promise { + async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -112,7 +112,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async updateChat(id: number, request: ChatUpdateRequest): Promise { + async updateChat(id: number, request: ChatUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -129,7 +129,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async archiveChat(id: number): Promise { + async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -144,7 +144,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async deleteChat(id: number): Promise { + async deleteChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "DELETE", headers: this.headers, @@ -160,25 +160,17 @@ export class ChatsServiceImpl extends ChatsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - chats?: ChatsService; -} - export class PachcaClient { readonly chats: ChatsService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); + this.chats = new ChatsServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - chats: options.chats ?? new ChatsService(), - }); + static stub(chats: ChatsService = new ChatsService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.chats = chats; + return client; } } diff --git a/packages/generator/tests/crud/snapshots/ts/examples.json b/packages/generator/tests/crud/snapshots/ts/examples.json index c9d7f4fd..9488231a 100644 --- a/packages/generator/tests/crud/snapshots/ts/examples.json +++ b/packages/generator/tests/crud/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/crud/snapshots/ts/utils.ts b/packages/generator/tests/crud/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/crud/snapshots/ts/utils.ts +++ b/packages/generator/tests/crud/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift b/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift +++ b/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/deep-nesting/snapshots/ts/examples.json b/packages/generator/tests/deep-nesting/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/deep-nesting/snapshots/ts/examples.json +++ b/packages/generator/tests/deep-nesting/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/edge-cases/snapshots/go/client.go b/packages/generator/tests/edge-cases/snapshots/go/client.go index fea4900a..2ad13450 100644 --- a/packages/generator/tests/edge-cases/snapshots/go/client.go +++ b/packages/generator/tests/edge-cases/snapshots/go/client.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "mime/multipart" "net/http" "net/url" @@ -25,6 +26,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -42,7 +50,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt index 7fd4f14e..e923f179 100644 --- a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt +++ b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt @@ -14,24 +14,22 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class EventsService { - open suspend fun listEvents( +interface EventsService { + suspend fun listEvents( isActive: Boolean? = null, scopes: List? = null, filter: EventFilter? = null, - ): ListEventsResponse { + ): ListEventsResponse = throw NotImplementedError("Events.listEvents is not implemented") - } - open suspend fun publishEvent(id: Int, scope: OAuthScope): Event { + suspend fun publishEvent(id: Int, scope: OAuthScope): Event = throw NotImplementedError("Events.publishEvent is not implemented") - } } class EventsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : EventsService() { +) : EventsService { override suspend fun listEvents( isActive: Boolean?, scopes: List?, @@ -60,16 +58,15 @@ class EventsServiceImpl internal constructor( } } -open class UploadsService { - open suspend fun createUpload(request: UploadRequest) { +interface UploadsService { + suspend fun createUpload(request: UploadRequest) = throw NotImplementedError("Uploads.createUpload is not implemented") - } } class UploadsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : UploadsService() { +) : UploadsService { override suspend fun createUpload(request: UploadRequest) { val response = client.submitFormWithBinaryData( "$baseUrl/uploads", @@ -109,8 +106,8 @@ class PachcaClient private constructor( } fun stub( - events: EventsService = EventsService(), - uploads: UploadsService = UploadsService() + events: EventsService = object : EventsService {}, + uploads: UploadsService = object : UploadsService {} ): PachcaClient = PachcaClient( client = null, events = events, diff --git a/packages/generator/tests/edge-cases/snapshots/py/utils.py b/packages/generator/tests/edge-cases/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/utils.py +++ b/packages/generator/tests/edge-cases/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift b/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/client.ts b/packages/generator/tests/edge-cases/snapshots/ts/client.ts index 6a097555..314526ee 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/client.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/client.ts @@ -25,7 +25,7 @@ export class EventsServiceImpl extends EventsService { super(); } - override async listEvents(params?: ListEventsParams): Promise { + async listEvents(params?: ListEventsParams): Promise { const query = new URLSearchParams(); if (params?.isActive !== undefined) query.set("is_active", String(params.isActive)); if (params?.scopes !== undefined) query.set("scopes", String(params.scopes)); @@ -43,7 +43,7 @@ export class EventsServiceImpl extends EventsService { } } - override async publishEvent(id: number, scope: OAuthScope): Promise { + async publishEvent(id: number, scope: OAuthScope): Promise { const response = await fetchWithRetry(`${this.baseUrl}/events/${id}/publish`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -73,7 +73,7 @@ export class UploadsServiceImpl extends UploadsService { super(); } - override async createUpload(request: UploadRequest): Promise { + async createUpload(request: UploadRequest): Promise { const form = new FormData(); form.set("Content-Disposition", request.contentDisposition); form.set("file", request.file, "upload"); @@ -91,29 +91,20 @@ export class UploadsServiceImpl extends UploadsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl: string; - events?: EventsService; - uploads?: UploadsService; -} - export class PachcaClient { readonly events: EventsService; readonly uploads: UploadsService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const { baseUrl } = options; + constructor(token: string, baseUrl: string) { const headers = { Authorization: `Bearer ${token}` }; - this.events = options.events ?? new EventsServiceImpl(baseUrl, headers); - this.uploads = options.uploads ?? new UploadsServiceImpl(baseUrl, headers); + this.events = new EventsServiceImpl(baseUrl, headers); + this.uploads = new UploadsServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "", - events: options.events ?? new EventsService(), - uploads: options.uploads ?? new UploadsService(), - }); + static stub(events: EventsService = new EventsService(), uploads: UploadsService = new UploadsService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.events = events; + client.uploads = uploads; + return client; } } diff --git a/packages/generator/tests/edge-cases/snapshots/ts/examples.json b/packages/generator/tests/edge-cases/snapshots/ts/examples.json index 801e9810..56dad943 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/examples.json +++ b/packages/generator/tests/edge-cases/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/edge-cases/snapshots/ts/utils.ts b/packages/generator/tests/edge-cases/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/utils.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/enums/snapshots/swift/Utils.swift b/packages/generator/tests/enums/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/enums/snapshots/swift/Utils.swift +++ b/packages/generator/tests/enums/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/enums/snapshots/ts/examples.json b/packages/generator/tests/enums/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/enums/snapshots/ts/examples.json +++ b/packages/generator/tests/enums/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/models/snapshots/swift/Utils.swift b/packages/generator/tests/models/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/models/snapshots/swift/Utils.swift +++ b/packages/generator/tests/models/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/models/snapshots/ts/examples.json b/packages/generator/tests/models/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/models/snapshots/ts/examples.json +++ b/packages/generator/tests/models/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/multi-path-params/snapshots/go/client.go b/packages/generator/tests/multi-path-params/snapshots/go/client.go index f99688a9..edd6b452 100644 --- a/packages/generator/tests/multi-path-params/snapshots/go/client.go +++ b/packages/generator/tests/multi-path-params/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "strconv" "time" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt index 4578e675..94385b47 100644 --- a/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt +++ b/packages/generator/tests/multi-path-params/snapshots/kt/Client.kt @@ -13,32 +13,29 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class TasksService { - open suspend fun getTask(projectId: Int, taskId: Int): Task { +interface TasksService { + suspend fun getTask(projectId: Int, taskId: Int): Task = throw NotImplementedError("Tasks.getTask is not implemented") - } - open suspend fun updateTask( + suspend fun updateTask( projectId: Int, taskId: Int, request: TaskUpdateRequest, - ): Task { + ): Task = throw NotImplementedError("Tasks.updateTask is not implemented") - } - open suspend fun deleteComment( + suspend fun deleteComment( projectId: Int, taskId: Int, commentId: Int, - ) { + ) = throw NotImplementedError("Tasks.deleteComment is not implemented") - } } class TasksServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : TasksService() { +) : TasksService { override suspend fun getTask(projectId: Int, taskId: Int): Task { val response = client.get("$baseUrl/projects/$projectId/tasks/$taskId") return when (response.status.value) { @@ -94,7 +91,7 @@ class PachcaClient private constructor( } fun stub( - tasks: TasksService = TasksService() + tasks: TasksService = object : TasksService {} ): PachcaClient = PachcaClient( client = null, tasks = tasks diff --git a/packages/generator/tests/multi-path-params/snapshots/py/utils.py b/packages/generator/tests/multi-path-params/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/multi-path-params/snapshots/py/utils.py +++ b/packages/generator/tests/multi-path-params/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift b/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift +++ b/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts index 7a752f88..ae1bff04 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/client.ts +++ b/packages/generator/tests/multi-path-params/snapshots/ts/client.ts @@ -23,7 +23,7 @@ export class TasksServiceImpl extends TasksService { super(); } - override async getTask(projectId: number, taskId: number): Promise { + async getTask(projectId: number, taskId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}`, { headers: this.headers, }); @@ -36,7 +36,7 @@ export class TasksServiceImpl extends TasksService { } } - override async updateTask(projectId: number, taskId: number, request: TaskUpdateRequest): Promise { + async updateTask(projectId: number, taskId: number, request: TaskUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -51,7 +51,7 @@ export class TasksServiceImpl extends TasksService { } } - override async deleteComment(projectId: number, taskId: number, commentId: number): Promise { + async deleteComment(projectId: number, taskId: number, commentId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/projects/${projectId}/tasks/${taskId}/comments/${commentId}`, { method: "DELETE", headers: this.headers, @@ -65,25 +65,17 @@ export class TasksServiceImpl extends TasksService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - tasks?: TasksService; -} - export class PachcaClient { readonly tasks: TasksService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.example.com/v1"; + constructor(token: string, baseUrl: string = "https://api.example.com/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.tasks = options.tasks ?? new TasksServiceImpl(baseUrl, headers); + this.tasks = new TasksServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.example.com/v1", - tasks: options.tasks ?? new TasksService(), - }); + static stub(tasks: TasksService = new TasksService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.tasks = tasks; + return client; } } diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/examples.json b/packages/generator/tests/multi-path-params/snapshots/ts/examples.json index d4cb304a..fd311487 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/examples.json +++ b/packages/generator/tests/multi-path-params/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/multi-path-params/snapshots/ts/utils.ts b/packages/generator/tests/multi-path-params/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/multi-path-params/snapshots/ts/utils.ts +++ b/packages/generator/tests/multi-path-params/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift b/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift +++ b/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/nullable-ref/snapshots/ts/examples.json b/packages/generator/tests/nullable-ref/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/nullable-ref/snapshots/ts/examples.json +++ b/packages/generator/tests/nullable-ref/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/oneof/snapshots/swift/Utils.swift b/packages/generator/tests/oneof/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/oneof/snapshots/swift/Utils.swift +++ b/packages/generator/tests/oneof/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/oneof/snapshots/ts/examples.json b/packages/generator/tests/oneof/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/oneof/snapshots/ts/examples.json +++ b/packages/generator/tests/oneof/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/patch/snapshots/go/client.go b/packages/generator/tests/patch/snapshots/go/client.go index 7ce5e3a4..e5f7118b 100644 --- a/packages/generator/tests/patch/snapshots/go/client.go +++ b/packages/generator/tests/patch/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "strconv" "time" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/patch/snapshots/kt/Client.kt b/packages/generator/tests/patch/snapshots/kt/Client.kt index 213b970e..83f3d9d3 100644 --- a/packages/generator/tests/patch/snapshots/kt/Client.kt +++ b/packages/generator/tests/patch/snapshots/kt/Client.kt @@ -13,16 +13,15 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class ItemsService { - open suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { +interface ItemsService { + suspend fun patchItem(id: Int, request: ItemPatchRequest): Item = throw NotImplementedError("Items.patchItem is not implemented") - } } class ItemsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ItemsService() { +) : ItemsService { override suspend fun patchItem(id: Int, request: ItemPatchRequest): Item { val response = client.patch("$baseUrl/items/$id") { contentType(ContentType.Application.Json) @@ -54,7 +53,7 @@ class PachcaClient private constructor( } fun stub( - items: ItemsService = ItemsService() + items: ItemsService = object : ItemsService {} ): PachcaClient = PachcaClient( client = null, items = items diff --git a/packages/generator/tests/patch/snapshots/py/utils.py b/packages/generator/tests/patch/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/patch/snapshots/py/utils.py +++ b/packages/generator/tests/patch/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/patch/snapshots/swift/Utils.swift b/packages/generator/tests/patch/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/patch/snapshots/swift/Utils.swift +++ b/packages/generator/tests/patch/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/patch/snapshots/ts/client.ts b/packages/generator/tests/patch/snapshots/ts/client.ts index a2899829..125781af 100644 --- a/packages/generator/tests/patch/snapshots/ts/client.ts +++ b/packages/generator/tests/patch/snapshots/ts/client.ts @@ -15,7 +15,7 @@ export class ItemsServiceImpl extends ItemsService { super(); } - override async patchItem(id: number, request: ItemPatchRequest): Promise { + async patchItem(id: number, request: ItemPatchRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/items/${id}`, { method: "PATCH", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -31,25 +31,17 @@ export class ItemsServiceImpl extends ItemsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - items?: ItemsService; -} - export class PachcaClient { readonly items: ItemsService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.example.com/v1"; + constructor(token: string, baseUrl: string = "https://api.example.com/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.items = options.items ?? new ItemsServiceImpl(baseUrl, headers); + this.items = new ItemsServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.example.com/v1", - items: options.items ?? new ItemsService(), - }); + static stub(items: ItemsService = new ItemsService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.items = items; + return client; } } diff --git a/packages/generator/tests/patch/snapshots/ts/examples.json b/packages/generator/tests/patch/snapshots/ts/examples.json index 6fa4df81..ab60f3f7 100644 --- a/packages/generator/tests/patch/snapshots/ts/examples.json +++ b/packages/generator/tests/patch/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/patch/snapshots/ts/utils.ts b/packages/generator/tests/patch/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/patch/snapshots/ts/utils.ts +++ b/packages/generator/tests/patch/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/record/snapshots/go/client.go b/packages/generator/tests/record/snapshots/go/client.go index a064c6cf..237a5a54 100644 --- a/packages/generator/tests/record/snapshots/go/client.go +++ b/packages/generator/tests/record/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "strconv" "time" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/record/snapshots/kt/Client.kt b/packages/generator/tests/record/snapshots/kt/Client.kt index e4394489..8ff6b447 100644 --- a/packages/generator/tests/record/snapshots/kt/Client.kt +++ b/packages/generator/tests/record/snapshots/kt/Client.kt @@ -13,16 +13,15 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class LinkPreviewsService { - open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { +interface LinkPreviewsService { + suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) = throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") - } } class LinkPreviewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : LinkPreviewsService() { +) : LinkPreviewsService { override suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { val response = client.post("$baseUrl/messages/$id/link_previews") { contentType(ContentType.Application.Json) @@ -55,7 +54,7 @@ class PachcaClient private constructor( } fun stub( - linkPreviews: LinkPreviewsService = LinkPreviewsService() + linkPreviews: LinkPreviewsService = object : LinkPreviewsService {} ): PachcaClient = PachcaClient( client = null, linkPreviews = linkPreviews diff --git a/packages/generator/tests/record/snapshots/py/utils.py b/packages/generator/tests/record/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/record/snapshots/py/utils.py +++ b/packages/generator/tests/record/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/record/snapshots/swift/Utils.swift b/packages/generator/tests/record/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/record/snapshots/swift/Utils.swift +++ b/packages/generator/tests/record/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/record/snapshots/ts/client.ts b/packages/generator/tests/record/snapshots/ts/client.ts index e8fc863d..500513f8 100644 --- a/packages/generator/tests/record/snapshots/ts/client.ts +++ b/packages/generator/tests/record/snapshots/ts/client.ts @@ -15,7 +15,7 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { super(); } - override async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/link_previews`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -32,25 +32,17 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - linkPreviews?: LinkPreviewsService; -} - export class PachcaClient { readonly linkPreviews: LinkPreviewsService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.linkPreviews = options.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); + this.linkPreviews = new LinkPreviewsServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - linkPreviews: options.linkPreviews ?? new LinkPreviewsService(), - }); + static stub(linkPreviews: LinkPreviewsService = new LinkPreviewsService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.linkPreviews = linkPreviews; + return client; } } diff --git a/packages/generator/tests/record/snapshots/ts/examples.json b/packages/generator/tests/record/snapshots/ts/examples.json index 4fef6b27..17c2ac86 100644 --- a/packages/generator/tests/record/snapshots/ts/examples.json +++ b/packages/generator/tests/record/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/record/snapshots/ts/utils.ts b/packages/generator/tests/record/snapshots/ts/utils.ts index 873cc4e4..9e39032c 100644 --- a/packages/generator/tests/record/snapshots/ts/utils.ts +++ b/packages/generator/tests/record/snapshots/ts/utils.ts @@ -60,6 +60,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -67,7 +72,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/redirect/snapshots/go/client.go b/packages/generator/tests/redirect/snapshots/go/client.go index 948f84a0..9ac55800 100644 --- a/packages/generator/tests/redirect/snapshots/go/client.go +++ b/packages/generator/tests/redirect/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math/rand" "net/http" "strconv" "time" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/redirect/snapshots/kt/Client.kt b/packages/generator/tests/redirect/snapshots/kt/Client.kt index 58b1c433..40db5189 100644 --- a/packages/generator/tests/redirect/snapshots/kt/Client.kt +++ b/packages/generator/tests/redirect/snapshots/kt/Client.kt @@ -13,16 +13,15 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class CommonService { - open suspend fun downloadExport(id: Int): String { +interface CommonService { + suspend fun downloadExport(id: Int): String = throw NotImplementedError("Common.downloadExport is not implemented") - } } class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : CommonService() { +) : CommonService { override suspend fun downloadExport(id: Int): String { val response = client.get("$baseUrl/exports/$id") return when (response.status.value) { @@ -53,7 +52,7 @@ class PachcaClient private constructor( } fun stub( - common: CommonService = CommonService() + common: CommonService = object : CommonService {} ): PachcaClient = PachcaClient( client = null, common = common diff --git a/packages/generator/tests/redirect/snapshots/py/utils.py b/packages/generator/tests/redirect/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/redirect/snapshots/py/utils.py +++ b/packages/generator/tests/redirect/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/redirect/snapshots/swift/Utils.swift b/packages/generator/tests/redirect/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/redirect/snapshots/swift/Utils.swift +++ b/packages/generator/tests/redirect/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/redirect/snapshots/ts/client.ts b/packages/generator/tests/redirect/snapshots/ts/client.ts index 7ebb19c8..83ca68a7 100644 --- a/packages/generator/tests/redirect/snapshots/ts/client.ts +++ b/packages/generator/tests/redirect/snapshots/ts/client.ts @@ -15,7 +15,7 @@ export class CommonServiceImpl extends CommonService { super(); } - override async downloadExport(id: number): Promise { + async downloadExport(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/exports/${id}`, { headers: this.headers, redirect: "manual", @@ -36,25 +36,17 @@ export class CommonServiceImpl extends CommonService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - common?: CommonService; -} - export class PachcaClient { readonly common: CommonService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); + this.common = new CommonServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - common: options.common ?? new CommonService(), - }); + static stub(common: CommonService = new CommonService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.common = common; + return client; } } diff --git a/packages/generator/tests/redirect/snapshots/ts/examples.json b/packages/generator/tests/redirect/snapshots/ts/examples.json index fdc92450..90a72057 100644 --- a/packages/generator/tests/redirect/snapshots/ts/examples.json +++ b/packages/generator/tests/redirect/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/redirect/snapshots/ts/utils.ts b/packages/generator/tests/redirect/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/redirect/snapshots/ts/utils.ts +++ b/packages/generator/tests/redirect/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift b/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift +++ b/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json b/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json +++ b/packages/generator/tests/reserved-keywords/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/search/snapshots/go/client.go b/packages/generator/tests/search/snapshots/go/client.go index d8a66110..56c1badf 100644 --- a/packages/generator/tests/search/snapshots/go/client.go +++ b/packages/generator/tests/search/snapshots/go/client.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "net/url" "strconv" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/search/snapshots/kt/Client.kt b/packages/generator/tests/search/snapshots/kt/Client.kt index f7548266..2e822b7a 100644 --- a/packages/generator/tests/search/snapshots/kt/Client.kt +++ b/packages/generator/tests/search/snapshots/kt/Client.kt @@ -13,8 +13,8 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class SearchService { - open suspend fun searchMessages( +interface SearchService { + suspend fun searchMessages( query: String, chatIds: List? = null, userIds: List? = null, @@ -23,11 +23,10 @@ open class SearchService { sort: SearchSort? = null, limit: Int? = null, cursor: String? = null, - ): SearchMessagesResponse { + ): SearchMessagesResponse = throw NotImplementedError("Search.searchMessages is not implemented") - } - open suspend fun searchMessagesAll( + suspend fun searchMessagesAll( query: String, chatIds: List? = null, userIds: List? = null, @@ -35,15 +34,14 @@ open class SearchService { createdTo: String? = null, sort: SearchSort? = null, limit: Int? = null, - ): List { + ): List = throw NotImplementedError("Search.searchMessagesAll is not implemented") - } } class SearchServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : SearchService() { +) : SearchService { override suspend fun searchMessages( query: String, chatIds: List?, @@ -119,7 +117,7 @@ class PachcaClient private constructor( } fun stub( - search: SearchService = SearchService() + search: SearchService = object : SearchService {} ): PachcaClient = PachcaClient( client = null, search = search diff --git a/packages/generator/tests/search/snapshots/py/utils.py b/packages/generator/tests/search/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/search/snapshots/py/utils.py +++ b/packages/generator/tests/search/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/search/snapshots/swift/Utils.swift b/packages/generator/tests/search/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/search/snapshots/swift/Utils.swift +++ b/packages/generator/tests/search/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/search/snapshots/ts/client.ts b/packages/generator/tests/search/snapshots/ts/client.ts index 22ed06af..3a7f4246 100644 --- a/packages/generator/tests/search/snapshots/ts/client.ts +++ b/packages/generator/tests/search/snapshots/ts/client.ts @@ -24,7 +24,7 @@ export class SearchServiceImpl extends SearchService { super(); } - override async searchMessages(params: SearchMessagesParams): Promise { + async searchMessages(params: SearchMessagesParams): Promise { const query = new URLSearchParams(); query.set("query", params.query); if (params?.chatIds !== undefined) { @@ -52,7 +52,7 @@ export class SearchServiceImpl extends SearchService { } } - override async searchMessagesAll(params: Omit): Promise { + async searchMessagesAll(params: Omit): Promise { const items: MessageSearchResult[] = []; let cursor: string | undefined; do { @@ -64,25 +64,17 @@ export class SearchServiceImpl extends SearchService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - search?: SearchService; -} - export class PachcaClient { readonly search: SearchService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.search = options.search ?? new SearchServiceImpl(baseUrl, headers); + this.search = new SearchServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - search: options.search ?? new SearchService(), - }); + static stub(search: SearchService = new SearchService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.search = search; + return client; } } diff --git a/packages/generator/tests/search/snapshots/ts/examples.json b/packages/generator/tests/search/snapshots/ts/examples.json index 64cf6196..74cc3202 100644 --- a/packages/generator/tests/search/snapshots/ts/examples.json +++ b/packages/generator/tests/search/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/search/snapshots/ts/utils.ts b/packages/generator/tests/search/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/search/snapshots/ts/utils.ts +++ b/packages/generator/tests/search/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/unions/snapshots/swift/Utils.swift b/packages/generator/tests/unions/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/unions/snapshots/swift/Utils.swift +++ b/packages/generator/tests/unions/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/unions/snapshots/ts/examples.json b/packages/generator/tests/unions/snapshots/ts/examples.json index b42cb203..176e6945 100644 --- a/packages/generator/tests/unions/snapshots/ts/examples.json +++ b/packages/generator/tests/unions/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/unwrap/snapshots/go/client.go b/packages/generator/tests/unwrap/snapshots/go/client.go index 4b90d95f..65623779 100644 --- a/packages/generator/tests/unwrap/snapshots/go/client.go +++ b/packages/generator/tests/unwrap/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "net/http" "strconv" "time" @@ -22,6 +23,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -39,7 +47,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/unwrap/snapshots/kt/Client.kt b/packages/generator/tests/unwrap/snapshots/kt/Client.kt index 2db70fe9..4e5bc9bd 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/Client.kt +++ b/packages/generator/tests/unwrap/snapshots/kt/Client.kt @@ -13,16 +13,15 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class MembersService { - open suspend fun addMembers(id: Int, memberIds: List) { +interface MembersService { + suspend fun addMembers(id: Int, memberIds: List) = throw NotImplementedError("Members.addMembers is not implemented") - } } class MembersServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : MembersService() { +) : MembersService { override suspend fun addMembers(id: Int, memberIds: List) { val response = client.post("$baseUrl/chats/$id/members") { contentType(ContentType.Application.Json) @@ -36,20 +35,18 @@ class MembersServiceImpl internal constructor( } } -open class ChatsService { - open suspend fun createChat(request: ChatCreateRequest): Chat { +interface ChatsService { + suspend fun createChat(request: ChatCreateRequest): Chat = throw NotImplementedError("Chats.createChat is not implemented") - } - open suspend fun archiveChat(id: Int) { + suspend fun archiveChat(id: Int) = throw NotImplementedError("Chats.archiveChat is not implemented") - } } class ChatsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ChatsService() { +) : ChatsService { override suspend fun createChat(request: ChatCreateRequest): Chat { val response = client.post("$baseUrl/chats") { contentType(ContentType.Application.Json) @@ -94,8 +91,8 @@ class PachcaClient private constructor( } fun stub( - chats: ChatsService = ChatsService(), - members: MembersService = MembersService() + chats: ChatsService = object : ChatsService {}, + members: MembersService = object : MembersService {} ): PachcaClient = PachcaClient( client = null, chats = chats, diff --git a/packages/generator/tests/unwrap/snapshots/py/utils.py b/packages/generator/tests/unwrap/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/unwrap/snapshots/py/utils.py +++ b/packages/generator/tests/unwrap/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/unwrap/snapshots/swift/Utils.swift b/packages/generator/tests/unwrap/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Utils.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/unwrap/snapshots/ts/client.ts b/packages/generator/tests/unwrap/snapshots/ts/client.ts index 4f68dd60..a1fb992d 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/client.ts +++ b/packages/generator/tests/unwrap/snapshots/ts/client.ts @@ -20,7 +20,7 @@ export class MembersServiceImpl extends MembersService { super(); } - override async addMembers(id: number, memberIds: number[]): Promise { + async addMembers(id: number, memberIds: number[]): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -55,7 +55,7 @@ export class ChatsServiceImpl extends ChatsService { super(); } - override async createChat(request: ChatCreateRequest): Promise { + async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -72,7 +72,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async archiveChat(id: number): Promise { + async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -88,29 +88,20 @@ export class ChatsServiceImpl extends ChatsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - chats?: ChatsService; - members?: MembersService; -} - export class PachcaClient { readonly chats: ChatsService; readonly members: MembersService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); - this.members = options.members ?? new MembersServiceImpl(baseUrl, headers); + this.chats = new ChatsServiceImpl(baseUrl, headers); + this.members = new MembersServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - chats: options.chats ?? new ChatsService(), - members: options.members ?? new MembersService(), - }); + static stub(chats: ChatsService = new ChatsService(), members: MembersService = new MembersService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.chats = chats; + client.members = members; + return client; } } diff --git a/packages/generator/tests/unwrap/snapshots/ts/examples.json b/packages/generator/tests/unwrap/snapshots/ts/examples.json index bfcb96e6..e9620e7c 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/examples.json +++ b/packages/generator/tests/unwrap/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/unwrap/snapshots/ts/utils.ts b/packages/generator/tests/unwrap/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/unwrap/snapshots/ts/utils.ts +++ b/packages/generator/tests/unwrap/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; diff --git a/packages/generator/tests/upload/snapshots/go/client.go b/packages/generator/tests/upload/snapshots/go/client.go index 70c3f017..010f0222 100644 --- a/packages/generator/tests/upload/snapshots/go/client.go +++ b/packages/generator/tests/upload/snapshots/go/client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "mime/multipart" "net/http" "strconv" @@ -23,6 +24,13 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { const maxRetries = 3 +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func addJitter(delay time.Duration) time.Duration { + factor := 0.5 + rand.Float64()*0.5 + return time.Duration(float64(delay) * factor) +} + func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { for attempt := 0; ; attempt++ { if attempt > 0 && req.GetBody != nil { @@ -40,7 +48,13 @@ func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) delay = time.Duration(secs) * time.Second } } - time.Sleep(delay) + time.Sleep(addJitter(delay)) + continue + } + if retryable5xx[resp.StatusCode] && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(attempt+1) * time.Second + time.Sleep(addJitter(delay)) continue } return resp, nil diff --git a/packages/generator/tests/upload/snapshots/kt/Client.kt b/packages/generator/tests/upload/snapshots/kt/Client.kt index 276f6df8..ab5f03ae 100644 --- a/packages/generator/tests/upload/snapshots/kt/Client.kt +++ b/packages/generator/tests/upload/snapshots/kt/Client.kt @@ -14,20 +14,18 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable -open class CommonService { - open suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { +interface CommonService { + suspend fun uploadFile(directUrl: String, request: FileUploadRequest) = throw NotImplementedError("Common.uploadFile is not implemented") - } - open suspend fun getUploadParams(): UploadParams { + suspend fun getUploadParams(): UploadParams = throw NotImplementedError("Common.getUploadParams is not implemented") - } } class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : CommonService() { +) : CommonService { override suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { val response = client.submitFormWithBinaryData( directUrl, @@ -83,7 +81,7 @@ class PachcaClient private constructor( } fun stub( - common: CommonService = CommonService() + common: CommonService = object : CommonService {} ): PachcaClient = PachcaClient( client = null, common = common diff --git a/packages/generator/tests/upload/snapshots/py/utils.py b/packages/generator/tests/upload/snapshots/py/utils.py index 44d19034..950682b4 100644 --- a/packages/generator/tests/upload/snapshots/py/utils.py +++ b/packages/generator/tests/upload/snapshots/py/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/packages/generator/tests/upload/snapshots/swift/Utils.swift b/packages/generator/tests/upload/snapshots/swift/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/packages/generator/tests/upload/snapshots/swift/Utils.swift +++ b/packages/generator/tests/upload/snapshots/swift/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/packages/generator/tests/upload/snapshots/ts/client.ts b/packages/generator/tests/upload/snapshots/ts/client.ts index 4609523a..6bc4d429 100644 --- a/packages/generator/tests/upload/snapshots/ts/client.ts +++ b/packages/generator/tests/upload/snapshots/ts/client.ts @@ -19,7 +19,7 @@ export class CommonServiceImpl extends CommonService { super(); } - override async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + async uploadFile(directUrl: string, request: FileUploadRequest): Promise { const form = new FormData(); form.set("content-disposition", request.contentDisposition); form.set("acl", request.acl); @@ -44,7 +44,7 @@ export class CommonServiceImpl extends CommonService { } } - override async getUploadParams(): Promise { + async getUploadParams(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/uploads`, { method: "POST", headers: this.headers, @@ -61,25 +61,17 @@ export class CommonServiceImpl extends CommonService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - common?: CommonService; -} - export class PachcaClient { readonly common: CommonService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); + this.common = new CommonServiceImpl(baseUrl, headers); } - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - common: options.common ?? new CommonService(), - }); + static stub(common: CommonService = new CommonService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.common = common; + return client; } } diff --git a/packages/generator/tests/upload/snapshots/ts/examples.json b/packages/generator/tests/upload/snapshots/ts/examples.json index cefe2863..b1103232 100644 --- a/packages/generator/tests/upload/snapshots/ts/examples.json +++ b/packages/generator/tests/upload/snapshots/ts/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/packages/generator/tests/upload/snapshots/ts/utils.ts b/packages/generator/tests/upload/snapshots/ts/utils.ts index 4c979225..ac5d6549 100644 --- a/packages/generator/tests/upload/snapshots/ts/utils.ts +++ b/packages/generator/tests/upload/snapshots/ts/utils.ts @@ -33,6 +33,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -40,7 +45,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response; From 5322afb0d544d00cbb30deff46cb5c6e07d64419 Mon Sep 17 00:00:00 2001 From: aenadgrleey Date: Sat, 28 Mar 2026 01:05:51 +0100 Subject: [PATCH 8/8] fix(generator): move Go retry logic to utils.go, add typed NotImplementedError - Move jitter, retry constants, and doWithRetry to utils.go (matches other SDKs) - Add NotImplementedError struct type for better error handling in stubs - Fix TypeScript docs: revert incorrect object syntax back to positional args Co-Authored-By: Claude Opus 4.5 --- apps/docs/public/guides/sdk/typescript.md | 4 +- apps/docs/public/llms-full.txt | 4 +- packages/generator/src/lang/go.ts | 105 ++--- .../snapshots/go/utils.go | 55 +++ .../tests/allof-sibling/snapshots/go/utils.go | 55 +++ .../tests/circular-ref/snapshots/go/utils.go | 55 +++ .../tests/crud/snapshots/go/client.go | 55 +-- .../tests/crud/snapshots/go/utils.go | 55 +++ .../tests/deep-nesting/snapshots/go/utils.go | 55 +++ .../tests/edge-cases/snapshots/go/client.go | 47 +-- .../tests/edge-cases/snapshots/go/utils.go | 55 +++ .../tests/enums/snapshots/go/utils.go | 55 +++ .../tests/models/snapshots/go/utils.go | 55 +++ .../multi-path-params/snapshots/go/client.go | 47 +-- .../multi-path-params/snapshots/go/utils.go | 55 +++ .../tests/nullable-ref/snapshots/go/utils.go | 55 +++ .../tests/oneof/snapshots/go/utils.go | 55 +++ .../tests/patch/snapshots/go/client.go | 43 +- .../tests/patch/snapshots/go/utils.go | 55 +++ .../tests/record/snapshots/go/client.go | 43 +- .../tests/record/snapshots/go/utils.go | 55 +++ .../tests/redirect/snapshots/go/client.go | 43 +- .../tests/redirect/snapshots/go/utils.go | 55 +++ .../reserved-keywords/snapshots/go/utils.go | 55 +++ .../tests/search/snapshots/go/client.go | 45 +-- .../tests/search/snapshots/go/utils.go | 55 +++ .../tests/unions/snapshots/go/utils.go | 55 +++ .../tests/unwrap/snapshots/go/client.go | 47 +-- .../tests/unwrap/snapshots/go/utils.go | 55 +++ .../tests/upload/snapshots/go/client.go | 45 +-- .../tests/upload/snapshots/go/utils.go | 55 +++ sdk/go/generated/client.go | 181 ++++----- sdk/go/generated/utils.go | 55 +++ .../src/main/kotlin/com/pachca/Client.kt | 369 +++++++----------- sdk/python/generated/pachca/utils.py | 14 +- .../Pachca/GeneratedSources/Utils.swift | 29 +- sdk/typescript/src/generated/client.ts | 253 ++++++------ sdk/typescript/src/generated/examples.json | 2 +- sdk/typescript/src/generated/utils.ts | 12 +- 39 files changed, 1571 insertions(+), 917 deletions(-) diff --git a/apps/docs/public/guides/sdk/typescript.md b/apps/docs/public/guides/sdk/typescript.md index 7d9b8f6e..3fd242a3 100644 --- a/apps/docs/public/guides/sdk/typescript.md +++ b/apps/docs/public/guides/sdk/typescript.md @@ -23,7 +23,7 @@ npm install @pachca/sdk ```typescript import { PachcaClient } from "@pachca/sdk" -const client = new PachcaClient({ token: "YOUR_TOKEN" }) +const client = new PachcaClient("YOUR_TOKEN") ``` @@ -329,7 +329,7 @@ import { ```typescript import { Button, FileType, MessageCreateRequest, MessageCreateRequestFile, MessageCreateRequestMessage, MessageEntityType, PachcaClient, TaskCreateRequest, TaskCreateRequestCustomProperty, TaskCreateRequestTask, TaskKind } from "@pachca/sdk" -const client = new PachcaClient({ token: "YOUR_TOKEN" }) +const client = new PachcaClient("YOUR_TOKEN") // Отправка сообщения const request: MessageCreateRequest = { diff --git a/apps/docs/public/llms-full.txt b/apps/docs/public/llms-full.txt index 75936cf2..62f13308 100644 --- a/apps/docs/public/llms-full.txt +++ b/apps/docs/public/llms-full.txt @@ -1182,7 +1182,7 @@ npm install @pachca/sdk ```typescript import { PachcaClient } from "@pachca/sdk" -const client = new PachcaClient({ token: "YOUR_TOKEN" }) +const client = new PachcaClient("YOUR_TOKEN") ``` @@ -1488,7 +1488,7 @@ import { ```typescript import { Button, FileType, MessageCreateRequest, MessageCreateRequestFile, MessageCreateRequestMessage, MessageEntityType, PachcaClient, TaskCreateRequest, TaskCreateRequestCustomProperty, TaskCreateRequestTask, TaskKind } from "@pachca/sdk" -const client = new PachcaClient({ token: "YOUR_TOKEN" }) +const client = new PachcaClient("YOUR_TOKEN") // Отправка сообщения const request: MessageCreateRequest = { diff --git a/packages/generator/src/lang/go.ts b/packages/generator/src/lang/go.ts index c44f2af4..173a2d11 100644 --- a/packages/generator/src/lang/go.ts +++ b/packages/generator/src/lang/go.ts @@ -684,10 +684,11 @@ function emitStubMethod(lines: string[], op: IROperation, ir: IR): void { const hasReq = op.queryParams.some((p) => p.required); args.push(`${snakeToCamel('params')} ${hasReq ? pName : `*${pName}`}`); } + const methodRef = JSON.stringify(`${op.tag}.${op.methodName}`); lines.push(`func (s *${stubName}) ${goMethodName(op)}(${args.join(', ')}) ${goReturn(op, ir)} {`); - if (op.successResponse.isRedirect) lines.push(`\treturn "", fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); - else if (!op.successResponse.hasBody) lines.push(`\treturn fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); - else lines.push(`\treturn nil, fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName} is not implemented`)})`); + if (op.successResponse.isRedirect) lines.push(`\treturn "", NotImplementedError{Method: ${methodRef}}`); + else if (!op.successResponse.hasBody) lines.push(`\treturn NotImplementedError{Method: ${methodRef}}`); + else lines.push(`\treturn nil, NotImplementedError{Method: ${methodRef}}`); lines.push('}'); } @@ -699,7 +700,7 @@ function emitStubPaginationMethod(lines: string[], op: IROperation): void { for (const p of op.pathParams) args.push(`${snakeToCamel(p.sdkName)} ${goType(p.type)}`); if (op.queryParams.length > 0) args.push(`params *${upperFirst(op.methodName)}Params`); lines.push(`func (s *${stubName}) ${goMethodName(op)}All(${args.join(', ')}) ([]${itemType}, error) {`); - lines.push(`\treturn nil, fmt.Errorf(${JSON.stringify(`${op.tag}.${op.methodName}All is not implemented`)})`); + lines.push(`\treturn nil, NotImplementedError{Method: ${JSON.stringify(`${op.tag}.${op.methodName}All`)}}`); lines.push('}'); } @@ -716,7 +717,7 @@ function generateClient(ir: IR): string { const needURL = ir.services.some((s) => s.operations.some((o) => o.queryParams.length > 0)); const needErrors = ir.services.some((s) => s.operations.some((o) => o.successResponse.isRedirect)); const needMultipart = ir.services.some((s) => s.operations.some((o) => o.requestBody?.contentType === 'multipart')); - const imports: string[] = ['"context"', '"encoding/json"', '"fmt"', '"math/rand"', '"net/http"', '"strconv"', '"time"']; + const imports: string[] = ['"context"', '"encoding/json"', '"fmt"', '"net/http"', '"time"']; if (needBytes) imports.push('"bytes"'); if (needURL) imports.push('"net/url"'); if (needErrors) imports.push('"errors"'); @@ -741,45 +742,6 @@ function generateClient(ir: IR): string { lines.push('\treturn t.base.RoundTrip(req)'); lines.push('}'); lines.push(''); - lines.push('const maxRetries = 3'); - lines.push(''); - lines.push('var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true}'); - lines.push(''); - lines.push('func addJitter(delay time.Duration) time.Duration {'); - lines.push('\tfactor := 0.5 + rand.Float64()*0.5'); - lines.push('\treturn time.Duration(float64(delay) * factor)'); - lines.push('}'); - lines.push(''); - lines.push('func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) {'); - lines.push('\tfor attempt := 0; ; attempt++ {'); - lines.push('\t\tif attempt > 0 && req.GetBody != nil {'); - lines.push('\t\t\treq.Body, _ = req.GetBody()'); - lines.push('\t\t}'); - lines.push('\t\tresp, err := client.Do(req)'); - lines.push('\t\tif err != nil {'); - lines.push('\t\t\treturn nil, err'); - lines.push('\t\t}'); - lines.push('\t\tif resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries {'); - lines.push('\t\t\tresp.Body.Close()'); - lines.push('\t\t\tdelay := time.Duration(1< 0 && req.GetBody != nil {', + '\t\t\treq.Body, _ = req.GetBody()', + '\t\t}', + '\t\tresp, err := client.Do(req)', + '\t\tif err != nil {', + '\t\t\treturn nil, err', + '\t\t}', + '\t\tif resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries {', + '\t\t\tresp.Body.Close()', + '\t\t\tdelay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 && req.GetBody != nil { - req.Body, _ = req.GetBody() - } - resp, err := client.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { - resp.Body.Close() - delay := time.Duration(1< 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< { + ): List = throw NotImplementedError("Security.getAuditEventsAll is not implemented") - } } class SecurityServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : SecurityService() { +) : SecurityService { override suspend fun getAuditEvents( startTime: String?, endTime: String?, @@ -107,28 +105,24 @@ class SecurityServiceImpl internal constructor( } } -open class BotsService { - open suspend fun getWebhookEvents(limit: Int? = null, cursor: String? = null): GetWebhookEventsResponse { +interface BotsService { + suspend fun getWebhookEvents(limit: Int? = null, cursor: String? = null): GetWebhookEventsResponse = throw NotImplementedError("Bots.getWebhookEvents is not implemented") - } - open suspend fun getWebhookEventsAll(limit: Int? = null): List { + suspend fun getWebhookEventsAll(limit: Int? = null): List = throw NotImplementedError("Bots.getWebhookEventsAll is not implemented") - } - open suspend fun updateBot(id: Int, request: BotUpdateRequest): BotResponse { + suspend fun updateBot(id: Int, request: BotUpdateRequest): BotResponse = throw NotImplementedError("Bots.updateBot is not implemented") - } - open suspend fun deleteWebhookEvent(id: String) { + suspend fun deleteWebhookEvent(id: String) = throw NotImplementedError("Bots.deleteWebhookEvent is not implemented") - } } class BotsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : BotsService() { +) : BotsService { override suspend fun getWebhookEvents(limit: Int?, cursor: String?): GetWebhookEventsResponse { val response = client.get("$baseUrl/webhooks/events") { limit?.let { parameter("limit", it) } @@ -174,8 +168,8 @@ class BotsServiceImpl internal constructor( } } -open class ChatsService { - open suspend fun listChats( +interface ChatsService { + suspend fun listChats( sortId: SortOrder? = null, availability: ChatAvailability? = null, lastMessageAtAfter: String? = null, @@ -183,46 +177,39 @@ open class ChatsService { personal: Boolean? = null, limit: Int? = null, cursor: String? = null, - ): ListChatsResponse { + ): ListChatsResponse = throw NotImplementedError("Chats.listChats is not implemented") - } - open suspend fun listChatsAll( + suspend fun listChatsAll( sortId: SortOrder? = null, availability: ChatAvailability? = null, lastMessageAtAfter: String? = null, lastMessageAtBefore: String? = null, personal: Boolean? = null, limit: Int? = null, - ): List { + ): List = throw NotImplementedError("Chats.listChatsAll is not implemented") - } - open suspend fun getChat(id: Int): Chat { + suspend fun getChat(id: Int): Chat = throw NotImplementedError("Chats.getChat is not implemented") - } - open suspend fun createChat(request: ChatCreateRequest): Chat { + suspend fun createChat(request: ChatCreateRequest): Chat = throw NotImplementedError("Chats.createChat is not implemented") - } - open suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat { + suspend fun updateChat(id: Int, request: ChatUpdateRequest): Chat = throw NotImplementedError("Chats.updateChat is not implemented") - } - open suspend fun archiveChat(id: Int) { + suspend fun archiveChat(id: Int) = throw NotImplementedError("Chats.archiveChat is not implemented") - } - open suspend fun unarchiveChat(id: Int) { + suspend fun unarchiveChat(id: Int) = throw NotImplementedError("Chats.unarchiveChat is not implemented") - } } class ChatsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ChatsService() { +) : ChatsService { override suspend fun listChats( sortId: SortOrder?, availability: ChatAvailability?, @@ -326,32 +313,27 @@ class ChatsServiceImpl internal constructor( } } -open class CommonService { - open suspend fun downloadExport(id: Int): String { +interface CommonService { + suspend fun downloadExport(id: Int): String = throw NotImplementedError("Common.downloadExport is not implemented") - } - open suspend fun listProperties(entityType: SearchEntityType): ListPropertiesResponse { + suspend fun listProperties(entityType: SearchEntityType): ListPropertiesResponse = throw NotImplementedError("Common.listProperties is not implemented") - } - open suspend fun requestExport(request: ExportRequest) { + suspend fun requestExport(request: ExportRequest) = throw NotImplementedError("Common.requestExport is not implemented") - } - open suspend fun uploadFile(directUrl: String, request: FileUploadRequest) { + suspend fun uploadFile(directUrl: String, request: FileUploadRequest) = throw NotImplementedError("Common.uploadFile is not implemented") - } - open suspend fun getUploadParams(): UploadParams { + suspend fun getUploadParams(): UploadParams = throw NotImplementedError("Common.getUploadParams is not implemented") - } } class CommonServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : CommonService() { +) : CommonService { override suspend fun downloadExport(id: Int): String { val response = client.get("$baseUrl/chats/exports/$id") return when (response.status.value) { @@ -420,57 +402,49 @@ class CommonServiceImpl internal constructor( } } -open class MembersService { - open suspend fun listMembers( +interface MembersService { + suspend fun listMembers( id: Int, role: ChatMemberRoleFilter? = null, limit: Int? = null, cursor: String? = null, - ): ListMembersResponse { + ): ListMembersResponse = throw NotImplementedError("Members.listMembers is not implemented") - } - open suspend fun listMembersAll( + suspend fun listMembersAll( id: Int, role: ChatMemberRoleFilter? = null, limit: Int? = null, - ): List { + ): List = throw NotImplementedError("Members.listMembersAll is not implemented") - } - open suspend fun addTags(id: Int, groupTagIds: List) { + suspend fun addTags(id: Int, groupTagIds: List) = throw NotImplementedError("Members.addTags is not implemented") - } - open suspend fun addMembers(id: Int, request: AddMembersRequest) { + suspend fun addMembers(id: Int, request: AddMembersRequest) = throw NotImplementedError("Members.addMembers is not implemented") - } - open suspend fun updateMemberRole( + suspend fun updateMemberRole( id: Int, userId: Int, role: ChatMemberRole, - ) { + ) = throw NotImplementedError("Members.updateMemberRole is not implemented") - } - open suspend fun removeTag(id: Int, tagId: Int) { + suspend fun removeTag(id: Int, tagId: Int) = throw NotImplementedError("Members.removeTag is not implemented") - } - open suspend fun leaveChat(id: Int) { + suspend fun leaveChat(id: Int) = throw NotImplementedError("Members.leaveChat is not implemented") - } - open suspend fun removeMember(id: Int, userId: Int) { + suspend fun removeMember(id: Int, userId: Int) = throw NotImplementedError("Members.removeMember is not implemented") - } } class MembersServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : MembersService() { +) : MembersService { override suspend fun listMembers( id: Int, role: ChatMemberRoleFilter?, @@ -577,52 +551,44 @@ class MembersServiceImpl internal constructor( } } -open class GroupTagsService { - open suspend fun listTags( +interface GroupTagsService { + suspend fun listTags( names: TagNamesFilter? = null, limit: Int? = null, cursor: String? = null, - ): ListTagsResponse { + ): ListTagsResponse = throw NotImplementedError("Group tags.listTags is not implemented") - } - open suspend fun listTagsAll(names: TagNamesFilter? = null, limit: Int? = null): List { + suspend fun listTagsAll(names: TagNamesFilter? = null, limit: Int? = null): List = throw NotImplementedError("Group tags.listTagsAll is not implemented") - } - open suspend fun getTag(id: Int): GroupTag { + suspend fun getTag(id: Int): GroupTag = throw NotImplementedError("Group tags.getTag is not implemented") - } - open suspend fun getTagUsers( + suspend fun getTagUsers( id: Int, limit: Int? = null, cursor: String? = null, - ): ListMembersResponse { + ): ListMembersResponse = throw NotImplementedError("Group tags.getTagUsers is not implemented") - } - open suspend fun getTagUsersAll(id: Int, limit: Int? = null): List { + suspend fun getTagUsersAll(id: Int, limit: Int? = null): List = throw NotImplementedError("Group tags.getTagUsersAll is not implemented") - } - open suspend fun createTag(request: GroupTagRequest): GroupTag { + suspend fun createTag(request: GroupTagRequest): GroupTag = throw NotImplementedError("Group tags.createTag is not implemented") - } - open suspend fun updateTag(id: Int, request: GroupTagRequest): GroupTag { + suspend fun updateTag(id: Int, request: GroupTagRequest): GroupTag = throw NotImplementedError("Group tags.updateTag is not implemented") - } - open suspend fun deleteTag(id: Int) { + suspend fun deleteTag(id: Int) = throw NotImplementedError("Group tags.deleteTag is not implemented") - } } class GroupTagsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : GroupTagsService() { +) : GroupTagsService { override suspend fun listTags( names: TagNamesFilter?, limit: Int?, @@ -721,53 +687,45 @@ class GroupTagsServiceImpl internal constructor( } } -open class MessagesService { - open suspend fun listChatMessages( +interface MessagesService { + suspend fun listChatMessages( chatId: Int, sortId: SortOrder? = null, limit: Int? = null, cursor: String? = null, - ): ListChatMessagesResponse { + ): ListChatMessagesResponse = throw NotImplementedError("Messages.listChatMessages is not implemented") - } - open suspend fun listChatMessagesAll( + suspend fun listChatMessagesAll( chatId: Int, sortId: SortOrder? = null, limit: Int? = null, - ): List { + ): List = throw NotImplementedError("Messages.listChatMessagesAll is not implemented") - } - open suspend fun getMessage(id: Int): Message { + suspend fun getMessage(id: Int): Message = throw NotImplementedError("Messages.getMessage is not implemented") - } - open suspend fun createMessage(request: MessageCreateRequest): Message { + suspend fun createMessage(request: MessageCreateRequest): Message = throw NotImplementedError("Messages.createMessage is not implemented") - } - open suspend fun pinMessage(id: Int) { + suspend fun pinMessage(id: Int) = throw NotImplementedError("Messages.pinMessage is not implemented") - } - open suspend fun updateMessage(id: Int, request: MessageUpdateRequest): Message { + suspend fun updateMessage(id: Int, request: MessageUpdateRequest): Message = throw NotImplementedError("Messages.updateMessage is not implemented") - } - open suspend fun deleteMessage(id: Int) { + suspend fun deleteMessage(id: Int) = throw NotImplementedError("Messages.deleteMessage is not implemented") - } - open suspend fun unpinMessage(id: Int) { + suspend fun unpinMessage(id: Int) = throw NotImplementedError("Messages.unpinMessage is not implemented") - } } class MessagesServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : MessagesService() { +) : MessagesService { override suspend fun listChatMessages( chatId: Int, sortId: SortOrder?, @@ -868,16 +826,15 @@ class MessagesServiceImpl internal constructor( } } -open class LinkPreviewsService { - open suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { +interface LinkPreviewsService { + suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) = throw NotImplementedError("Link Previews.createLinkPreviews is not implemented") - } } class LinkPreviewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : LinkPreviewsService() { +) : LinkPreviewsService { override suspend fun createLinkPreviews(id: Int, request: LinkPreviewsRequest) { val response = client.post("$baseUrl/messages/$id/link_previews") { contentType(ContentType.Application.Json) @@ -891,36 +848,32 @@ class LinkPreviewsServiceImpl internal constructor( } } -open class ReactionsService { - open suspend fun listReactions( +interface ReactionsService { + suspend fun listReactions( id: Int, limit: Int? = null, cursor: String? = null, - ): ListReactionsResponse { + ): ListReactionsResponse = throw NotImplementedError("Reactions.listReactions is not implemented") - } - open suspend fun listReactionsAll(id: Int, limit: Int? = null): List { + suspend fun listReactionsAll(id: Int, limit: Int? = null): List = throw NotImplementedError("Reactions.listReactionsAll is not implemented") - } - open suspend fun addReaction(id: Int, request: ReactionRequest): Reaction { + suspend fun addReaction(id: Int, request: ReactionRequest): Reaction = throw NotImplementedError("Reactions.addReaction is not implemented") - } - open suspend fun removeReaction( + suspend fun removeReaction( id: Int, code: String, name: String? = null, - ) { + ) = throw NotImplementedError("Reactions.removeReaction is not implemented") - } } class ReactionsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ReactionsService() { +) : ReactionsService { override suspend fun listReactions( id: Int, limit: Int?, @@ -977,20 +930,19 @@ class ReactionsServiceImpl internal constructor( } } -open class ReadMembersService { - open suspend fun listReadMembers( +interface ReadMembersService { + suspend fun listReadMembers( id: Int, limit: Int? = null, cursor: String? = null, - ): Any { + ): Any = throw NotImplementedError("Read members.listReadMembers is not implemented") - } } class ReadMembersServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ReadMembersService() { +) : ReadMembersService { override suspend fun listReadMembers( id: Int, limit: Int?, @@ -1008,20 +960,18 @@ class ReadMembersServiceImpl internal constructor( } } -open class ThreadsService { - open suspend fun getThread(id: Int): Thread { +interface ThreadsService { + suspend fun getThread(id: Int): Thread = throw NotImplementedError("Threads.getThread is not implemented") - } - open suspend fun createThread(id: Int): Thread { + suspend fun createThread(id: Int): Thread = throw NotImplementedError("Threads.createThread is not implemented") - } } class ThreadsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ThreadsService() { +) : ThreadsService { override suspend fun getThread(id: Int): Thread { val response = client.get("$baseUrl/threads/$id") return when (response.status.value) { @@ -1041,32 +991,27 @@ class ThreadsServiceImpl internal constructor( } } -open class ProfileService { - open suspend fun getTokenInfo(): AccessTokenInfo { +interface ProfileService { + suspend fun getTokenInfo(): AccessTokenInfo = throw NotImplementedError("Profile.getTokenInfo is not implemented") - } - open suspend fun getProfile(): User { + suspend fun getProfile(): User = throw NotImplementedError("Profile.getProfile is not implemented") - } - open suspend fun getStatus(): Any { + suspend fun getStatus(): Any = throw NotImplementedError("Profile.getStatus is not implemented") - } - open suspend fun updateStatus(request: StatusUpdateRequest): UserStatus { + suspend fun updateStatus(request: StatusUpdateRequest): UserStatus = throw NotImplementedError("Profile.updateStatus is not implemented") - } - open suspend fun deleteStatus() { + suspend fun deleteStatus() = throw NotImplementedError("Profile.deleteStatus is not implemented") - } } class ProfileServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ProfileService() { +) : ProfileService { override suspend fun getTokenInfo(): AccessTokenInfo { val response = client.get("$baseUrl/oauth/token/info") return when (response.status.value) { @@ -1116,8 +1061,8 @@ class ProfileServiceImpl internal constructor( } } -open class SearchService { - open suspend fun searchChats( +interface SearchService { + suspend fun searchChats( query: String? = null, limit: Int? = null, cursor: String? = null, @@ -1127,11 +1072,10 @@ open class SearchService { active: Boolean? = null, chatSubtype: ChatSubtype? = null, personal: Boolean? = null, - ): ListChatsResponse { + ): ListChatsResponse = throw NotImplementedError("Search.searchChats is not implemented") - } - open suspend fun searchChatsAll( + suspend fun searchChatsAll( query: String? = null, limit: Int? = null, order: SortOrder? = null, @@ -1140,11 +1084,10 @@ open class SearchService { active: Boolean? = null, chatSubtype: ChatSubtype? = null, personal: Boolean? = null, - ): List { + ): List = throw NotImplementedError("Search.searchChatsAll is not implemented") - } - open suspend fun searchMessages( + suspend fun searchMessages( query: String? = null, limit: Int? = null, cursor: String? = null, @@ -1154,11 +1097,10 @@ open class SearchService { chatIds: List? = null, userIds: List? = null, active: Boolean? = null, - ): ListChatMessagesResponse { + ): ListChatMessagesResponse = throw NotImplementedError("Search.searchMessages is not implemented") - } - open suspend fun searchMessagesAll( + suspend fun searchMessagesAll( query: String? = null, limit: Int? = null, order: SortOrder? = null, @@ -1167,11 +1109,10 @@ open class SearchService { chatIds: List? = null, userIds: List? = null, active: Boolean? = null, - ): List { + ): List = throw NotImplementedError("Search.searchMessagesAll is not implemented") - } - open suspend fun searchUsers( + suspend fun searchUsers( query: String? = null, limit: Int? = null, cursor: String? = null, @@ -1180,11 +1121,10 @@ open class SearchService { createdFrom: String? = null, createdTo: String? = null, companyRoles: List? = null, - ): ListMembersResponse { + ): ListMembersResponse = throw NotImplementedError("Search.searchUsers is not implemented") - } - open suspend fun searchUsersAll( + suspend fun searchUsersAll( query: String? = null, limit: Int? = null, sort: SearchSortOrder? = null, @@ -1192,15 +1132,14 @@ open class SearchService { createdFrom: String? = null, createdTo: String? = null, companyRoles: List? = null, - ): List { + ): List = throw NotImplementedError("Search.searchUsersAll is not implemented") - } } class SearchServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : SearchService() { +) : SearchService { override suspend fun searchChats( query: String?, limit: Int?, @@ -1375,36 +1314,30 @@ class SearchServiceImpl internal constructor( } } -open class TasksService { - open suspend fun listTasks(limit: Int? = null, cursor: String? = null): ListTasksResponse { +interface TasksService { + suspend fun listTasks(limit: Int? = null, cursor: String? = null): ListTasksResponse = throw NotImplementedError("Tasks.listTasks is not implemented") - } - open suspend fun listTasksAll(limit: Int? = null): List { + suspend fun listTasksAll(limit: Int? = null): List = throw NotImplementedError("Tasks.listTasksAll is not implemented") - } - open suspend fun getTask(id: Int): Task { + suspend fun getTask(id: Int): Task = throw NotImplementedError("Tasks.getTask is not implemented") - } - open suspend fun createTask(request: TaskCreateRequest): Task { + suspend fun createTask(request: TaskCreateRequest): Task = throw NotImplementedError("Tasks.createTask is not implemented") - } - open suspend fun updateTask(id: Int, request: TaskUpdateRequest): Task { + suspend fun updateTask(id: Int, request: TaskUpdateRequest): Task = throw NotImplementedError("Tasks.updateTask is not implemented") - } - open suspend fun deleteTask(id: Int) { + suspend fun deleteTask(id: Int) = throw NotImplementedError("Tasks.deleteTask is not implemented") - } } class TasksServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : TasksService() { +) : TasksService { override suspend fun listTasks(limit: Int?, cursor: String?): ListTasksResponse { val response = client.get("$baseUrl/tasks") { limit?.let { parameter("limit", it) } @@ -1471,52 +1404,43 @@ class TasksServiceImpl internal constructor( } } -open class UsersService { - open suspend fun listUsers( +interface UsersService { + suspend fun listUsers( query: String? = null, limit: Int? = null, cursor: String? = null, - ): ListMembersResponse { + ): ListMembersResponse = throw NotImplementedError("Users.listUsers is not implemented") - } - open suspend fun listUsersAll(query: String? = null, limit: Int? = null): List { + suspend fun listUsersAll(query: String? = null, limit: Int? = null): List = throw NotImplementedError("Users.listUsersAll is not implemented") - } - open suspend fun getUser(id: Int): User { + suspend fun getUser(id: Int): User = throw NotImplementedError("Users.getUser is not implemented") - } - open suspend fun getUserStatus(userId: Int): Any { + suspend fun getUserStatus(userId: Int): Any = throw NotImplementedError("Users.getUserStatus is not implemented") - } - open suspend fun createUser(request: UserCreateRequest): User { + suspend fun createUser(request: UserCreateRequest): User = throw NotImplementedError("Users.createUser is not implemented") - } - open suspend fun updateUser(id: Int, request: UserUpdateRequest): User { + suspend fun updateUser(id: Int, request: UserUpdateRequest): User = throw NotImplementedError("Users.updateUser is not implemented") - } - open suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus { + suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus = throw NotImplementedError("Users.updateUserStatus is not implemented") - } - open suspend fun deleteUser(id: Int) { + suspend fun deleteUser(id: Int) = throw NotImplementedError("Users.deleteUser is not implemented") - } - open suspend fun deleteUserStatus(userId: Int) { + suspend fun deleteUserStatus(userId: Int) = throw NotImplementedError("Users.deleteUserStatus is not implemented") - } } class UsersServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : UsersService() { +) : UsersService { override suspend fun listUsers( query: String?, limit: Int?, @@ -1618,16 +1542,15 @@ class UsersServiceImpl internal constructor( } } -open class ViewsService { - open suspend fun openView(request: OpenViewRequest) { +interface ViewsService { + suspend fun openView(request: OpenViewRequest) = throw NotImplementedError("Views.openView is not implemented") - } } class ViewsServiceImpl internal constructor( private val baseUrl: String, private val client: HttpClient, -) : ViewsService() { +) : ViewsService { override suspend fun openView(request: OpenViewRequest) { val response = client.post("$baseUrl/views/open") { contentType(ContentType.Application.Json) @@ -1705,22 +1628,22 @@ class PachcaClient private constructor( } fun stub( - bots: BotsService = BotsService(), - chats: ChatsService = ChatsService(), - common: CommonService = CommonService(), - groupTags: GroupTagsService = GroupTagsService(), - linkPreviews: LinkPreviewsService = LinkPreviewsService(), - members: MembersService = MembersService(), - messages: MessagesService = MessagesService(), - profile: ProfileService = ProfileService(), - reactions: ReactionsService = ReactionsService(), - readMembers: ReadMembersService = ReadMembersService(), - search: SearchService = SearchService(), - security: SecurityService = SecurityService(), - tasks: TasksService = TasksService(), - threads: ThreadsService = ThreadsService(), - users: UsersService = UsersService(), - views: ViewsService = ViewsService() + bots: BotsService = object : BotsService {}, + chats: ChatsService = object : ChatsService {}, + common: CommonService = object : CommonService {}, + groupTags: GroupTagsService = object : GroupTagsService {}, + linkPreviews: LinkPreviewsService = object : LinkPreviewsService {}, + members: MembersService = object : MembersService {}, + messages: MessagesService = object : MessagesService {}, + profile: ProfileService = object : ProfileService {}, + reactions: ReactionsService = object : ReactionsService {}, + readMembers: ReadMembersService = object : ReadMembersService {}, + search: SearchService = object : SearchService {}, + security: SecurityService = object : SecurityService {}, + tasks: TasksService = object : TasksService {}, + threads: ThreadsService = object : ThreadsService {}, + users: UsersService = object : UsersService {}, + views: ViewsService = object : ViewsService {} ): PachcaClient = PachcaClient( client = null, bots = bots, diff --git a/sdk/python/generated/pachca/utils.py b/sdk/python/generated/pachca/utils.py index 44d19034..950682b4 100644 --- a/sdk/python/generated/pachca/utils.py +++ b/sdk/python/generated/pachca/utils.py @@ -79,10 +79,16 @@ def serialize(obj: object) -> dict: _MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _add_jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) class RetryTransport(httpx.AsyncBaseTransport): - """Wraps an httpx transport with retry on 429 Too Many Requests.""" + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: self._transport = transport @@ -95,7 +101,11 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: if response.status_code == 429 and attempt < self._max_retries: retry_after = response.headers.get("retry-after") delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt - await asyncio.sleep(delay) + await asyncio.sleep(_add_jitter(delay)) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = attempt + 1 + await asyncio.sleep(_add_jitter(delay)) continue return response return response # unreachable diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift index fe3a04ac..d3fbd569 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift @@ -26,19 +26,32 @@ func deserialize(_ type: T.Type, from data: Data) throws -> T { } private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func addJitter(_ delay: UInt64) -> UInt64 { + let factor = 0.5 + Double.random(in: 0..<0.5) + return UInt64(Double(delay) * factor) +} func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { for attempt in 0...maxRetries { let (data, response) = try await session.data(for: request, delegate: delegate) - if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { - let delay: UInt64 - if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { - delay = secs * 1_000_000_000 - } else { - delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + if let http = response as? HTTPURLResponse { + if http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue + } + if retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = UInt64(attempt + 1) * 1_000_000_000 + try await _Concurrency.Task.sleep(nanoseconds: addJitter(delay)) + continue } - try await _Concurrency.Task.sleep(nanoseconds: delay) - continue } return (data, response) } diff --git a/sdk/typescript/src/generated/client.ts b/sdk/typescript/src/generated/client.ts index 2fa03e2a..d142afe0 100644 --- a/sdk/typescript/src/generated/client.ts +++ b/sdk/typescript/src/generated/client.ts @@ -78,7 +78,7 @@ export class SecurityServiceImpl extends SecurityService { super(); } - override async getAuditEvents(params?: GetAuditEventsParams): Promise { + async getAuditEvents(params?: GetAuditEventsParams): Promise { const query = new URLSearchParams(); if (params?.startTime !== undefined) query.set("start_time", params.startTime); if (params?.endTime !== undefined) query.set("end_time", params.endTime); @@ -104,7 +104,7 @@ export class SecurityServiceImpl extends SecurityService { } } - override async getAuditEventsAll(params?: Omit): Promise { + async getAuditEventsAll(params?: Omit): Promise { const items: AuditEvent[] = []; let cursor: string | undefined; do { @@ -142,7 +142,7 @@ export class BotsServiceImpl extends BotsService { super(); } - override async getWebhookEvents(params?: GetWebhookEventsParams): Promise { + async getWebhookEvents(params?: GetWebhookEventsParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -161,7 +161,7 @@ export class BotsServiceImpl extends BotsService { } } - override async getWebhookEventsAll(params?: Omit): Promise { + async getWebhookEventsAll(params?: Omit): Promise { const items: WebhookEvent[] = []; let cursor: string | undefined; do { @@ -172,7 +172,7 @@ export class BotsServiceImpl extends BotsService { return items; } - override async updateBot(id: number, request: BotUpdateRequest): Promise { + async updateBot(id: number, request: BotUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/bots/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -189,7 +189,7 @@ export class BotsServiceImpl extends BotsService { } } - override async deleteWebhookEvent(id: string): Promise { + async deleteWebhookEvent(id: string): Promise { const response = await fetchWithRetry(`${this.baseUrl}/webhooks/events/${id}`, { method: "DELETE", headers: this.headers, @@ -243,7 +243,7 @@ export class ChatsServiceImpl extends ChatsService { super(); } - override async listChats(params?: ListChatsParams): Promise { + async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); if (params?.availability !== undefined) query.set("availability", params.availability); @@ -267,7 +267,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async listChatsAll(params?: Omit): Promise { + async listChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -278,7 +278,7 @@ export class ChatsServiceImpl extends ChatsService { return items; } - override async getChat(id: number): Promise { + async getChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { headers: this.headers, }); @@ -293,7 +293,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async createChat(request: ChatCreateRequest): Promise { + async createChat(request: ChatCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -310,7 +310,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async updateChat(id: number, request: ChatUpdateRequest): Promise { + async updateChat(id: number, request: ChatUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -327,7 +327,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async archiveChat(id: number): Promise { + async archiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/archive`, { method: "PUT", headers: this.headers, @@ -342,7 +342,7 @@ export class ChatsServiceImpl extends ChatsService { } } - override async unarchiveChat(id: number): Promise { + async unarchiveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/unarchive`, { method: "PUT", headers: this.headers, @@ -388,7 +388,7 @@ export class CommonServiceImpl extends CommonService { super(); } - override async downloadExport(id: number): Promise { + async downloadExport(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/exports/${id}`, { headers: this.headers, redirect: "manual", @@ -408,7 +408,7 @@ export class CommonServiceImpl extends CommonService { } } - override async listProperties(params: ListPropertiesParams): Promise { + async listProperties(params: ListPropertiesParams): Promise { const query = new URLSearchParams(); query.set("entity_type", params.entityType); const response = await fetchWithRetry(`${this.baseUrl}/custom_properties?${query}`, { @@ -425,7 +425,7 @@ export class CommonServiceImpl extends CommonService { } } - override async requestExport(request: ExportRequest): Promise { + async requestExport(request: ExportRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/exports`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -441,7 +441,7 @@ export class CommonServiceImpl extends CommonService { } } - override async uploadFile(directUrl: string, request: FileUploadRequest): Promise { + async uploadFile(directUrl: string, request: FileUploadRequest): Promise { const form = new FormData(); form.set("Content-Disposition", request.contentDisposition); form.set("acl", request.acl); @@ -464,7 +464,7 @@ export class CommonServiceImpl extends CommonService { } } - override async getUploadParams(): Promise { + async getUploadParams(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/uploads`, { method: "POST", headers: this.headers, @@ -523,7 +523,7 @@ export class MembersServiceImpl extends MembersService { super(); } - override async listMembers(id: number, params?: ListMembersParams): Promise { + async listMembers(id: number, params?: ListMembersParams): Promise { const query = new URLSearchParams(); if (params?.role !== undefined) query.set("role", params.role); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -543,7 +543,7 @@ export class MembersServiceImpl extends MembersService { } } - override async listMembersAll(id: number, params?: Omit): Promise { + async listMembersAll(id: number, params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -554,7 +554,7 @@ export class MembersServiceImpl extends MembersService { return items; } - override async addTags(id: number, groupTagIds: number[]): Promise { + async addTags(id: number, groupTagIds: number[]): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/group_tags`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -570,7 +570,7 @@ export class MembersServiceImpl extends MembersService { } } - override async addMembers(id: number, request: AddMembersRequest): Promise { + async addMembers(id: number, request: AddMembersRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -586,7 +586,7 @@ export class MembersServiceImpl extends MembersService { } } - override async updateMemberRole(id: number, userId: number, role: ChatMemberRole): Promise { + async updateMemberRole(id: number, userId: number, role: ChatMemberRole): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members/${userId}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -602,7 +602,7 @@ export class MembersServiceImpl extends MembersService { } } - override async removeTag(id: number, tagId: number): Promise { + async removeTag(id: number, tagId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/group_tags/${tagId}`, { method: "DELETE", headers: this.headers, @@ -617,7 +617,7 @@ export class MembersServiceImpl extends MembersService { } } - override async leaveChat(id: number): Promise { + async leaveChat(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/leave`, { method: "DELETE", headers: this.headers, @@ -632,7 +632,7 @@ export class MembersServiceImpl extends MembersService { } } - override async removeMember(id: number, userId: number): Promise { + async removeMember(id: number, userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/chats/${id}/members/${userId}`, { method: "DELETE", headers: this.headers, @@ -690,7 +690,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { super(); } - override async listTags(params?: ListTagsParams): Promise { + async listTags(params?: ListTagsParams): Promise { const query = new URLSearchParams(); if (params?.names !== undefined) query.set("names", String(params.names)); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -710,7 +710,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } - override async listTagsAll(params?: Omit): Promise { + async listTagsAll(params?: Omit): Promise { const items: GroupTag[] = []; let cursor: string | undefined; do { @@ -721,7 +721,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { return items; } - override async getTag(id: number): Promise { + async getTag(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { headers: this.headers, }); @@ -736,7 +736,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } - override async getTagUsers(id: number, params?: GetTagUsersParams): Promise { + async getTagUsers(id: number, params?: GetTagUsersParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -755,7 +755,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } - override async getTagUsersAll(id: number, params?: Omit): Promise { + async getTagUsersAll(id: number, params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -766,7 +766,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { return items; } - override async createTag(request: GroupTagRequest): Promise { + async createTag(request: GroupTagRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -783,7 +783,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } - override async updateTag(id: number, request: GroupTagRequest): Promise { + async updateTag(id: number, request: GroupTagRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -800,7 +800,7 @@ export class GroupTagsServiceImpl extends GroupTagsService { } } - override async deleteTag(id: number): Promise { + async deleteTag(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/group_tags/${id}`, { method: "DELETE", headers: this.headers, @@ -858,7 +858,7 @@ export class MessagesServiceImpl extends MessagesService { super(); } - override async listChatMessages(params: ListChatMessagesParams): Promise { + async listChatMessages(params: ListChatMessagesParams): Promise { const query = new URLSearchParams(); query.set("chat_id", String(params.chatId)); if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); @@ -878,7 +878,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async listChatMessagesAll(params: Omit): Promise { + async listChatMessagesAll(params: Omit): Promise { const items: Message[] = []; let cursor: string | undefined; do { @@ -889,7 +889,7 @@ export class MessagesServiceImpl extends MessagesService { return items; } - override async getMessage(id: number): Promise { + async getMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { headers: this.headers, }); @@ -904,7 +904,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async createMessage(request: MessageCreateRequest): Promise { + async createMessage(request: MessageCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -921,7 +921,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async pinMessage(id: number): Promise { + async pinMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/pin`, { method: "POST", headers: this.headers, @@ -936,7 +936,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async updateMessage(id: number, request: MessageUpdateRequest): Promise { + async updateMessage(id: number, request: MessageUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -953,7 +953,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async deleteMessage(id: number): Promise { + async deleteMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}`, { method: "DELETE", headers: this.headers, @@ -968,7 +968,7 @@ export class MessagesServiceImpl extends MessagesService { } } - override async unpinMessage(id: number): Promise { + async unpinMessage(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/pin`, { method: "DELETE", headers: this.headers, @@ -998,7 +998,7 @@ export class LinkPreviewsServiceImpl extends LinkPreviewsService { super(); } - override async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { + async createLinkPreviews(id: number, request: LinkPreviewsRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/link_previews`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1041,7 +1041,7 @@ export class ReactionsServiceImpl extends ReactionsService { super(); } - override async listReactions(id: number, params?: ListReactionsParams): Promise { + async listReactions(id: number, params?: ListReactionsParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -1060,7 +1060,7 @@ export class ReactionsServiceImpl extends ReactionsService { } } - override async listReactionsAll(id: number, params?: Omit): Promise { + async listReactionsAll(id: number, params?: Omit): Promise { const items: Reaction[] = []; let cursor: string | undefined; do { @@ -1071,7 +1071,7 @@ export class ReactionsServiceImpl extends ReactionsService { return items; } - override async addReaction(id: number, request: ReactionRequest): Promise { + async addReaction(id: number, request: ReactionRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/reactions`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1088,7 +1088,7 @@ export class ReactionsServiceImpl extends ReactionsService { } } - override async removeReaction(id: number, params: RemoveReactionParams): Promise { + async removeReaction(id: number, params: RemoveReactionParams): Promise { const query = new URLSearchParams(); query.set("code", params.code); if (params?.name !== undefined) query.set("name", params.name); @@ -1121,7 +1121,7 @@ export class ReadMembersServiceImpl extends ReadMembersService { super(); } - override async listReadMembers(id: number, params?: ListReadMembersParams): Promise { + async listReadMembers(id: number, params?: ListReadMembersParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -1159,7 +1159,7 @@ export class ThreadsServiceImpl extends ThreadsService { super(); } - override async getThread(id: number): Promise { + async getThread(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/threads/${id}`, { headers: this.headers, }); @@ -1174,7 +1174,7 @@ export class ThreadsServiceImpl extends ThreadsService { } } - override async createThread(id: number): Promise { + async createThread(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/messages/${id}/thread`, { method: "POST", headers: this.headers, @@ -1221,7 +1221,7 @@ export class ProfileServiceImpl extends ProfileService { super(); } - override async getTokenInfo(): Promise { + async getTokenInfo(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/oauth/token/info`, { headers: this.headers, }); @@ -1236,7 +1236,7 @@ export class ProfileServiceImpl extends ProfileService { } } - override async getProfile(): Promise { + async getProfile(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile`, { headers: this.headers, }); @@ -1251,7 +1251,7 @@ export class ProfileServiceImpl extends ProfileService { } } - override async getStatus(): Promise { + async getStatus(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { headers: this.headers, }); @@ -1266,7 +1266,7 @@ export class ProfileServiceImpl extends ProfileService { } } - override async updateStatus(request: StatusUpdateRequest): Promise { + async updateStatus(request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1283,7 +1283,7 @@ export class ProfileServiceImpl extends ProfileService { } } - override async deleteStatus(): Promise { + async deleteStatus(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "DELETE", headers: this.headers, @@ -1333,7 +1333,7 @@ export class SearchServiceImpl extends SearchService { super(); } - override async searchChats(params?: SearchChatsParams): Promise { + async searchChats(params?: SearchChatsParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1359,7 +1359,7 @@ export class SearchServiceImpl extends SearchService { } } - override async searchChatsAll(params?: Omit): Promise { + async searchChatsAll(params?: Omit): Promise { const items: Chat[] = []; let cursor: string | undefined; do { @@ -1370,7 +1370,7 @@ export class SearchServiceImpl extends SearchService { return items; } - override async searchMessages(params?: SearchMessagesParams): Promise { + async searchMessages(params?: SearchMessagesParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1396,7 +1396,7 @@ export class SearchServiceImpl extends SearchService { } } - override async searchMessagesAll(params?: Omit): Promise { + async searchMessagesAll(params?: Omit): Promise { const items: Message[] = []; let cursor: string | undefined; do { @@ -1407,7 +1407,7 @@ export class SearchServiceImpl extends SearchService { return items; } - override async searchUsers(params?: SearchUsersParams): Promise { + async searchUsers(params?: SearchUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1432,7 +1432,7 @@ export class SearchServiceImpl extends SearchService { } } - override async searchUsersAll(params?: Omit): Promise { + async searchUsersAll(params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -1478,7 +1478,7 @@ export class TasksServiceImpl extends TasksService { super(); } - override async listTasks(params?: ListTasksParams): Promise { + async listTasks(params?: ListTasksParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -1497,7 +1497,7 @@ export class TasksServiceImpl extends TasksService { } } - override async listTasksAll(params?: Omit): Promise { + async listTasksAll(params?: Omit): Promise { const items: Task[] = []; let cursor: string | undefined; do { @@ -1508,7 +1508,7 @@ export class TasksServiceImpl extends TasksService { return items; } - override async getTask(id: number): Promise { + async getTask(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { headers: this.headers, }); @@ -1523,7 +1523,7 @@ export class TasksServiceImpl extends TasksService { } } - override async createTask(request: TaskCreateRequest): Promise { + async createTask(request: TaskCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1540,7 +1540,7 @@ export class TasksServiceImpl extends TasksService { } } - override async updateTask(id: number, request: TaskUpdateRequest): Promise { + async updateTask(id: number, request: TaskUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1557,7 +1557,7 @@ export class TasksServiceImpl extends TasksService { } } - override async deleteTask(id: number): Promise { + async deleteTask(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/tasks/${id}`, { method: "DELETE", headers: this.headers, @@ -1619,7 +1619,7 @@ export class UsersServiceImpl extends UsersService { super(); } - override async listUsers(params?: ListUsersParams): Promise { + async listUsers(params?: ListUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1639,7 +1639,7 @@ export class UsersServiceImpl extends UsersService { } } - override async listUsersAll(params?: Omit): Promise { + async listUsersAll(params?: Omit): Promise { const items: User[] = []; let cursor: string | undefined; do { @@ -1650,7 +1650,7 @@ export class UsersServiceImpl extends UsersService { return items; } - override async getUser(id: number): Promise { + async getUser(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { headers: this.headers, }); @@ -1665,7 +1665,7 @@ export class UsersServiceImpl extends UsersService { } } - override async getUserStatus(userId: number): Promise { + async getUserStatus(userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { headers: this.headers, }); @@ -1680,7 +1680,7 @@ export class UsersServiceImpl extends UsersService { } } - override async createUser(request: UserCreateRequest): Promise { + async createUser(request: UserCreateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1697,7 +1697,7 @@ export class UsersServiceImpl extends UsersService { } } - override async updateUser(id: number, request: UserUpdateRequest): Promise { + async updateUser(id: number, request: UserUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1714,7 +1714,7 @@ export class UsersServiceImpl extends UsersService { } } - override async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { + async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "PUT", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1731,7 +1731,7 @@ export class UsersServiceImpl extends UsersService { } } - override async deleteUser(id: number): Promise { + async deleteUser(id: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${id}`, { method: "DELETE", headers: this.headers, @@ -1746,7 +1746,7 @@ export class UsersServiceImpl extends UsersService { } } - override async deleteUserStatus(userId: number): Promise { + async deleteUserStatus(userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "DELETE", headers: this.headers, @@ -1776,7 +1776,7 @@ export class ViewsServiceImpl extends ViewsService { super(); } - override async openView(request: OpenViewRequest): Promise { + async openView(request: OpenViewRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/views/open`, { method: "POST", headers: { ...this.headers, "Content-Type": "application/json" }, @@ -1793,27 +1793,6 @@ export class ViewsServiceImpl extends ViewsService { } } -export interface PachcaClientOptions { - token: string; - baseUrl?: string; - bots?: BotsService; - chats?: ChatsService; - common?: CommonService; - groupTags?: GroupTagsService; - linkPreviews?: LinkPreviewsService; - members?: MembersService; - messages?: MessagesService; - profile?: ProfileService; - reactions?: ReactionsService; - readMembers?: ReadMembersService; - search?: SearchService; - security?: SecurityService; - tasks?: TasksService; - threads?: ThreadsService; - users?: UsersService; - views?: ViewsService; -} - export class PachcaClient { readonly bots: BotsService; readonly chats: ChatsService; @@ -1832,46 +1811,44 @@ export class PachcaClient { readonly users: UsersService; readonly views: ViewsService; - constructor(options: PachcaClientOptions) { - const { token } = options; - const baseUrl = options.baseUrl ?? "https://api.pachca.com/api/shared/v1"; + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { const headers = { Authorization: `Bearer ${token}` }; - this.bots = options.bots ?? new BotsServiceImpl(baseUrl, headers); - this.chats = options.chats ?? new ChatsServiceImpl(baseUrl, headers); - this.common = options.common ?? new CommonServiceImpl(baseUrl, headers); - this.groupTags = options.groupTags ?? new GroupTagsServiceImpl(baseUrl, headers); - this.linkPreviews = options.linkPreviews ?? new LinkPreviewsServiceImpl(baseUrl, headers); - this.members = options.members ?? new MembersServiceImpl(baseUrl, headers); - this.messages = options.messages ?? new MessagesServiceImpl(baseUrl, headers); - this.profile = options.profile ?? new ProfileServiceImpl(baseUrl, headers); - this.reactions = options.reactions ?? new ReactionsServiceImpl(baseUrl, headers); - this.readMembers = options.readMembers ?? new ReadMembersServiceImpl(baseUrl, headers); - this.search = options.search ?? new SearchServiceImpl(baseUrl, headers); - this.security = options.security ?? new SecurityServiceImpl(baseUrl, headers); - this.tasks = options.tasks ?? new TasksServiceImpl(baseUrl, headers); - this.threads = options.threads ?? new ThreadsServiceImpl(baseUrl, headers); - this.users = options.users ?? new UsersServiceImpl(baseUrl, headers); - this.views = options.views ?? new ViewsServiceImpl(baseUrl, headers); - } - - static stub(options: Partial = {}): PachcaClient { - return new PachcaClient({ token: options.token ?? "", baseUrl: options.baseUrl ?? "https://api.pachca.com/api/shared/v1", - bots: options.bots ?? new BotsService(), - chats: options.chats ?? new ChatsService(), - common: options.common ?? new CommonService(), - groupTags: options.groupTags ?? new GroupTagsService(), - linkPreviews: options.linkPreviews ?? new LinkPreviewsService(), - members: options.members ?? new MembersService(), - messages: options.messages ?? new MessagesService(), - profile: options.profile ?? new ProfileService(), - reactions: options.reactions ?? new ReactionsService(), - readMembers: options.readMembers ?? new ReadMembersService(), - search: options.search ?? new SearchService(), - security: options.security ?? new SecurityService(), - tasks: options.tasks ?? new TasksService(), - threads: options.threads ?? new ThreadsService(), - users: options.users ?? new UsersService(), - views: options.views ?? new ViewsService(), - }); + this.bots = new BotsServiceImpl(baseUrl, headers); + this.chats = new ChatsServiceImpl(baseUrl, headers); + this.common = new CommonServiceImpl(baseUrl, headers); + this.groupTags = new GroupTagsServiceImpl(baseUrl, headers); + this.linkPreviews = new LinkPreviewsServiceImpl(baseUrl, headers); + this.members = new MembersServiceImpl(baseUrl, headers); + this.messages = new MessagesServiceImpl(baseUrl, headers); + this.profile = new ProfileServiceImpl(baseUrl, headers); + this.reactions = new ReactionsServiceImpl(baseUrl, headers); + this.readMembers = new ReadMembersServiceImpl(baseUrl, headers); + this.search = new SearchServiceImpl(baseUrl, headers); + this.security = new SecurityServiceImpl(baseUrl, headers); + this.tasks = new TasksServiceImpl(baseUrl, headers); + this.threads = new ThreadsServiceImpl(baseUrl, headers); + this.users = new UsersServiceImpl(baseUrl, headers); + this.views = new ViewsServiceImpl(baseUrl, headers); + } + + static stub(bots: BotsService = new BotsService(), chats: ChatsService = new ChatsService(), common: CommonService = new CommonService(), groupTags: GroupTagsService = new GroupTagsService(), linkPreviews: LinkPreviewsService = new LinkPreviewsService(), members: MembersService = new MembersService(), messages: MessagesService = new MessagesService(), profile: ProfileService = new ProfileService(), reactions: ReactionsService = new ReactionsService(), readMembers: ReadMembersService = new ReadMembersService(), search: SearchService = new SearchService(), security: SecurityService = new SecurityService(), tasks: TasksService = new TasksService(), threads: ThreadsService = new ThreadsService(), users: UsersService = new UsersService(), views: ViewsService = new ViewsService()): PachcaClient { + const client = Object.create(PachcaClient.prototype); + client.bots = bots; + client.chats = chats; + client.common = common; + client.groupTags = groupTags; + client.linkPreviews = linkPreviews; + client.members = members; + client.messages = messages; + client.profile = profile; + client.reactions = reactions; + client.readMembers = readMembers; + client.search = search; + client.security = security; + client.tasks = tasks; + client.threads = threads; + client.users = users; + client.views = views; + return client; } } diff --git a/sdk/typescript/src/generated/examples.json b/sdk/typescript/src/generated/examples.json index d20380f0..d0aef1ba 100644 --- a/sdk/typescript/src/generated/examples.json +++ b/sdk/typescript/src/generated/examples.json @@ -1,6 +1,6 @@ { "Client_Init": { - "usage": "const client = new PachcaClient({ token: \"YOUR_TOKEN\" })", + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", "imports": [ "PachcaClient" ] diff --git a/sdk/typescript/src/generated/utils.ts b/sdk/typescript/src/generated/utils.ts index bdb8b1c3..05657ddc 100644 --- a/sdk/typescript/src/generated/utils.ts +++ b/sdk/typescript/src/generated/utils.ts @@ -60,6 +60,11 @@ export function serialize(obj: unknown): unknown { } const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function addJitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { for (let attempt = 0; ; attempt++) { @@ -67,7 +72,12 @@ export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestIni if (response.status === 429 && attempt < MAX_RETRIES) { const retryAfter = response.headers.get("retry-after"); const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); - await new Promise((r) => setTimeout(r, delay)); + await new Promise((r) => setTimeout(r, addJitter(delay))); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = 1000 * (attempt + 1); + await new Promise((r) => setTimeout(r, addJitter(delay))); continue; } return response;