diff --git a/examples/http_interactions/build/gleam-dev-erlang.lock b/examples/http_interactions/build/gleam-dev-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/gleam-dev-javascript.lock b/examples/http_interactions/build/gleam-dev-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/gleam-lsp-erlang.lock b/examples/http_interactions/build/gleam-lsp-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/gleam-lsp-javascript.lock b/examples/http_interactions/build/gleam-lsp-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/gleam-prod-erlang.lock b/examples/http_interactions/build/gleam-prod-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/gleam-prod-javascript.lock b/examples/http_interactions/build/gleam-prod-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/lsp/erlang/bigi/_gleam_artefacts/bigi.cache b/examples/http_interactions/build/lsp/erlang/bigi/_gleam_artefacts/bigi.cache new file mode 100644 index 0000000000000000000000000000000000000000..6bf997cb850d9bb89fed514e8be22d4f163b6e67 GIT binary patch literal 39283 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 V28L2-F*b*ffngc6xPq3P3;@Dxh-d%+ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/bigi/_gleam_artefacts/bigi.cache_meta b/examples/http_interactions/build/lsp/erlang/bigi/_gleam_artefacts/bigi.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..17929559b7d324dc3f9f2833b1c58663d6afd44b GIT binary patch literal 2452 zcmccpbU_9K46s2NTu?eaCp9rwKPf*y2P7cKz`!5^VlXf;Kx9%X^AdA2ll4w|@-i@-wBcDjx&G2R;Udk9-UaHv9|>75oeg3;7us zuJAK3@Cq<6@Ch(5SPL*P#0fAkOcG#V*dxHeuvdVA;jI7zgQy?_gS#LDL%tvbLxCUz z!(>4Qh8=q1- zz_3x6fnk#{1H)%w28Qp#3=D1}3=DZ93=9iI7#Qx0Ffcq2VPMb^WnhREWnf4YWnkDK z%D`|>l!4)qC<6nJ7z2Z%7z2Zy7z0D37z0Cz7z0DA7z4v>F$RWBVhjxX#TXdAh%qp5 zh%+!)i!(6Pi8C;46lY-AE6%|1LY#r&gE#}j7jXuLZ{iFL-^CdiEF>5hY$O;M(j*ud zrb#d`T##U3crL-f@K=I?K}eE;L0gi6K}V8-p<0rGp-Ym1p-+;5;jSbD1BVm?gRK+; zL%kFOLxU6p!+9wNhI>*B43DH37Q^FgPkOFytvPFsxQ!U|6fbz_3n%fq`9-fk9u9 zfgw?mfgxLwfnl{G1H&3c28Jt&3=C{a3=G~%3=BR>3=FkO3=FH37#JQZF))ZIGcbrM zGcdR*GceRBGcfE?W?=ZC%)szdnSnu5g@GYVg@K`0g@Iw03IoG#6$XaaDhv$bstgRi zstgSEstgPbstgS0R2dkU)EF2X)EF3A)EF3A)fgDAs4*~bsxvTns53D1sxvV3sWUK~ zR%c+4&|qLt)L>vR)nH)o)L>xn)?i@p(O_U`)L>v(sKLN+MuUOjoCX8Kc?|{zeoY1j zQ%wd2A58{^I86qIOic!cTulasd`$+10!;>njhYM$*EJa!-f1#0FlaF_aBDFz@M|$J z2x>7f2x&1e7;7;wm}@aG#Aq=vG-xp}EYf0NSfa(iuvCkI;fxjo!v!q{hOb%-46@n` z49?mN46fP?3~t&C4AZn37#?dgFnrf$V6f9+U%0|o{i0|tg#0|th(1`G@Wh71f|h71h3h71fd3>g?U8Zt0! zHDqAeX2`&B-H?IdzaayIj1dEaf)N9Qq7egwmk|R)x)B3IrV#@}mJtKP3?l}HIYtZ& zuZ$QNtc@8M8jKkjb{R7;+%;xk;4ooeP&8p+P&Hv-P%~j*a5rIK$TwkN=rLhnm|()d zFwum8VV4O5!*vq|hMOh~47W@e7sauz|d*Vz|d>Xz|d#Tz_8t%f#Hfd1H(0Q z28QeA3=GT`3=EbQ3=9Pp3=D-93=DHE7#J>FFfedgGB9vkGB8+JGBD&@GBB*LWMEin z$-r>al7Zo_B?E(|6$3-56$8T&D+Y$2RtyY(tr!^etQi;_tQi;rtr-}Str-}mS~D!Z)*kyb{hr;K^q1JJsSoF7aInK8XE?N88!?ITWlB@SZx^? zjBFVg+-(^cN^Kb!cG)s8Jg{Y8cxB7Lz-`CCAZ*9LAZ5qEAZ^FMpkc?r;A+RfkYvZe zkY>lgkYUHbkZH%jFx!rSVZI#$!)rSR21R=Y25)-?hI)GjhGq5)49o2q7;f4#Fx<6g zV32fRU~qO|U?_KBV3_B?z%bu|f#I411H&x`1_o_M1_ozG28Iep28KnB3=9_>85k}( zGB8|rWMKH`$iSfK#K55C#K4g5#K4g4#K5q{iGhL9nSmkDnSr6xnSr6#nSo)SGXujp zX9kA*&I}Ciof#MuT^JZ_To@SAT^JZPxG*qmbYWn)gDV3Aqbmc0 zuqy+Ds4D}5m@5N=sw)G7i7Nwxt1AOTs4D|QqbmbLt1AP;Vpj%+t*#6VEN%=8+HMRC zE^Z7ANp1`b&29_~-EIsF6WtgX=D0C19Cl-1IO4{@AnMM*pzO}T;N#A~;Oox7kmb(6 uQ0vaXFw32R;kr8m1CIv-gO~>cgSiI-gS`g>gR2JvgQo`rgEthzS}*|U`h!XU literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/directories/_gleam_artefacts/directories.cache b/examples/http_interactions/build/lsp/erlang/directories/_gleam_artefacts/directories.cache new file mode 100644 index 0000000000000000000000000000000000000000..41d2df6ec40b1b4a724241834bbf48ccadd25e96 GIT binary patch literal 14351 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/envoy/_gleam_artefacts/envoy.cache b/examples/http_interactions/build/lsp/erlang/envoy/_gleam_artefacts/envoy.cache new file mode 100644 index 0000000000000000000000000000000000000000..93054babb033a75b950cdab45325285b4664c5b8 GIT binary patch literal 3080 zcmZQ&fPmDzviwRA6GH$b$O0l57#P@~bWmz>X-)|fl$Xo{VWdE5Mg|53m>i7u&jyKt z)PY1m7^aJXfdOWxYf({t5xP2J? zi8;jxQD(3l1A|{Cib){TKr&dQ8KAyn2rWr-&QD1#$j{6x0jULHm_862<_ee@>8WsK zAcrCg!`ucAeV7^Aj0_CUj0_BnlR&01Ffb^%rD0%q%x+-aMDd_7f=-MgxXKN_wXXNLm z>gSXuSAxV75)v>qh5HBjxWoszIyxii1;>kqUuKRb*b*)-21XFzgt`yx`uMc8OeC){ z@Cz4k7{Lfx`$E5Z{>@7z9}u7#NR%+y_bkp&(OmB>~i^F9t=u zn`4-NXpm=!YrM0+kAG;8l8%B>KxSUHk|q~4abT!GNhol&U{`7=VNp%c9AXm z;Yv0rifEp2u*E1$EHMXOV1V3#EDUobI5MG8$1t0Pfnhl)GF71>kldV;qfnBOiZx3Y z>k-Ugi8(pALJG-2Aos!26x4Ds1uMwF87scHBqb*^i3=>lzyKE0PsvO!ffZvenaS{y z9;ygV!F&wXMM)M#&u=hapk*FpVVK?E&<1%GXG28Ja}3=E9RK|D~_3C3M^qGlX$ znTaK#L(5O(aQ38DBdP6fvjD86BV0Sy8;1q(5%#}?cM2m{3)Sd^iZ zju3SWR!|y5$uTf6$TKi7crY+9_%JXqL^3ciWHK-?v@kF*^e`|mOkiMOn9IPxu!Vtv z;UohC!$k%LhN}z=48Iu|7(g{g2SH{Is;(#JoKH^rHM+ z{nU!Y+=86cV*QMgl7jfmypq(S#N?99{JdiQq|(fs6#at41$)C{QS7yy}Ftlv+tz%ZMIf#CoYL%quY0DToDJOBUy literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/exception/_gleam_artefacts/exception.cache b/examples/http_interactions/build/lsp/erlang/exception/_gleam_artefacts/exception.cache new file mode 100644 index 0000000000000000000000000000000000000000..946d604a843d28df2ae98446a87ba341a932cb69 GIT binary patch literal 5901 zcmd;NfPmDB{?JCJA$}b05mYAoI z01lJ{h5V$f)Z`L9E;gt~LO^QrXc1J)pf)HtDwHMWl%|413hbmjh!4mJDX@0rkb;;* zT3A#nBHbi*1R%DjYCh4>y!lR%lKRLCySRoZ`8N?4{ga$}E;lL;dQQ2-@GJuX<5@ypDCI*S3620$3Z$0E(Z0+L{0U~tOMN72Is6^F@(6qTkz zg%}uM`PVHmrx+m$OMF48#icnVFaxCdAlhW0G{`Y9IT-Dq4H5;Z1BrkzOcw*fGH_By zR|hkdfq?;55QLVbIp?RO7UXB6bujGBRw5xt~`7|Fl@iV|4i z4Ne5$oGi%5z@UgdP8b-5Pl*nSDiDSR28bRM(Z#?3%O*vs#mR`m0hD{uWMFX)PXsUz z6|*ofOk`nTU`&OID1?A2SXhOrP@b8SqmZ0flANKCm{+NgT$ET0Vx=e~=jWBB7L_Pu zmMCQAmEzoIWacTjgPI;X2uGAu z7No)~NJ<5nnxc@IS6q^sn4*W~!^~oZ{Jfk>h0HXC%KTC;g}l_%6ory}g@V+gwEUu6 zNNXWECp9szv_PReBQ;MU5#lX{{N&`)qGBC|#Jm)R5|BhC!dGdDW%;E=TnY&(scET2 z2_Vx8@{5ZzlX6n^xVRV?K>*f9f=5bxT3RM*#4!qkQX2yUgPuN$D!tsSVpNq(3=AkW z4KzBz+Cg$4tOTt9z=B8wtTF)?J}_fZ>kyE35JoowBVpkbfJY<3Vwk<)FoFq#Y(bU4 zFcPE$q+E=hfx(@ffr0Td)VF9+3r@U6rQjHYMj$L%DI}(qq!xjqO(8wAEHzIdDLv6A;iAYvB0sfwm?dM7ca#-eD-v@Ha#D-A6u1-;5)!}(f=i*GG)W;X z57bzWFV8H=h|kGS&elk?Qb^0w&{WX1Q%Ho63e{W+3JN)?B?=$~3bqP~$%UnvMX4H^ zAo0@TR0SJd1#nzzfOS?Y6s6{*CKjh^fYfU$)PmKeft1&Rt>;p}n^jO!3BH_yq7amL zK*X2ISr^BNF4~n_^|R9(qRIb2Ew2g7^(~-14BI%14ADZ)eVO09NfF^TDlXFarkxier!z)x&A<8)y&mHx#Qd zFff1y!mJn=7(y8s7?v1{)Rz z22k^N6$=ByY8D2Dzbp(4@~jLD%B&0w#jFerM_3scez7tz{9|Qc;ALZA5MpCskY{6H z@MU9Q$Y5h&n9Rn&Fqe&iVKEy6!)i7Lh7D{C3|rV37#^@OFo0SnzU&MPe(Vekv)LIK zjRSjfS^u#|&=;R*)>1E|P%=VV|2VQ3IA#6TJP8Tq-X`e_;Y zX=%BMd3pNjMfthx+EElA5A=A@}C3=FeDJwR;Z&aha57iysV1d10u=twol6Z2UZ M7*;?{0Lg(c034d%KL7v# literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/exception/_gleam_artefacts/exception.cache_meta b/examples/http_interactions/build/lsp/erlang/exception/_gleam_artefacts/exception.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..c164a9dbab8b7aa2b69db16c3b4d29e0164f162c GIT binary patch literal 330 zcmex3AuEFc1{k3XUMQWOlbV>TpHi8Zn46gl5>sYiVCc}fUZmb!tZNSvfMOK}1_o6I z1_mnz28K`u28JaJ3=GE^7#R2&85oop85nFB85p`585piJGBCVkWME)qVqoxRVqmCc zVqoZJVqloa#K16#iGkq}69dB^CI$uuW(I};W(J0Z%nS^lm>C$jSr{1jSr`~>SQr?J zSr`~ru`n>KW?^9X%fi4Q&&t4{%*wz}%*w!Ugq4Be7b^q9KUM|?UN!~>AvOjEc{T1gBUvlgD*P+gC9Es!)$g2 shGXmu45!!`7^FBD7_vDS7#4CcFf8R@V7S7;z`)DNz~BzW&>&y{0Dv|r$N&HU literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/filepath/_gleam_artefacts/filepath.cache b/examples/http_interactions/build/lsp/erlang/filepath/_gleam_artefacts/filepath.cache new file mode 100644 index 0000000000000000000000000000000000000000..3b24bf5db634697433af0c8045a75251ebbf3914 GIT binary patch literal 15477 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 F7y#NsUJL*L literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_crypto/_gleam_artefacts/gleam@crypto.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_crypto/_gleam_artefacts/gleam@crypto.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..80ce8fc9c0bbd4b80794c3515be2819265bf050f GIT binary patch literal 953 zcmaEGCpm)w23VjBekh%ulbV>TpOje=pIB6sSgFRqz+l6`z`%(jotal+%fP_k%fP_E zgCbFsT3nh_;>WA3=B`07#Q9%F)(~%Vqo~s#K7Ri%)l_4nStRLGXujJW(Ecc z76yh)76yhXEDQ{@Sr{1RvM?~rV_{%e$HKs{o`r$o9t#7*BNhgRPb>@!-&hzJm{}PZ z^jR4g%vl*2T3HzwRz_5{nfngH|1H)qu28QPx3=Hy|3=GPg3=Dpp3=F-T3=F3@85mA; zGBAASWMKHs$-rRD#lT?B#lVor#lSF!i-F-77X!m_E(Qi6ZUzQ9ZUzP=ZU%;GZU%<0 z+zbqYJPZt?JPZuZJPZsqJPZtHc^DYZ@h~um@iH(-@iH)^@G>xD@G>y0Xm@2@)@JxV#K}L{)K|zp#!Bmie!Ap>V zAx@Bip+=B_p-Yf~VUHjK!w*3QhM$5A3~oXU3?4!Z44y&^3{!;|7fx%3efx$|cfx$f;a=idvOK^HVFm>4G9JY8wmymM+pXo8VLr5RT2ygha?ynZb>jO+>>BncmTzq HyaB=hDe8=- literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache new file mode 100644 index 0000000000000000000000000000000000000000..b8d7a386b1699bfea67e9662b0fc53e81c844955 GIT binary patch literal 4934 zcmb1OfPnOz)Wlr<)S{fkymbA4~Sr3U;wL$hp1tMig7~e;F83m zl90-RR1kkNBLl-xMg|6A3xiycBQ{ z<)tf>WTYx2=jWBAR+K1Y<|&kCWF}{T1(7YuELKQL&CE-O7@C@*$HfYD8qC+BC27w2 zDX9hdnRz8pDF#?DgZOM9f`Ne{D7CmWr-TX0%ix1BGNCj`2TTq|`)7khLFzyvAPm#R zz`y`=hHFt#ei6Dln5hg53^)TCq=y+~I0FL%%#D8eMY)MNP(cPpStv`vQ6Ud3gA)GW zVAkW}fGP?}%udZOONF}yD$D>2C*))(2$ckj!IE!Yeo86}RKhPm1)LyJ0s?tKA+0DsR{`V(9fj1)l8n?MxI#!;R4-OYK(f^el=46t^^Fp^ z6w*rbl0kV;AtMo#cvADgDKs@jM$kn5RL#010zYu%P)BmL#0=^ARbNfq?;( zA3!dG$%hn`B9aU&ceo|y6eC1o$=xp##UxnhVUcElnT|b&fOKHUykTTu_(Vi83erqm z76W+`gt0{{*##{l0|T_|VJIldEQ?RcEJ{r-$uFt|*#yF%0LGz$kfpE?jb~+GXkukx zU<`nYCEWq(nsLP=#oCa6lNRLD*(DA7}JO-#;E0IOFhNKDR7OixwF z1l0=AYK05gZapq8xC;~W6jDKDt3qa)LS8%mk7#LX?7(7`R7@}Ah z7~)tM7?!XwFsxu*M z2<{OxFfiz6fa=t=jQq5;+{C;*{q&;zT>Vr?WnHYFQBqP6pP5&ZT9gQG!WZi&m1gFo z=tCP0#rohX5!#p5FD^=k2wkrGmRfsh|c6xDN;KC*>CufDB0k8BwW_l9~oq>kKmttXLs8KczGWVnlIi zL26N1W^rbIowq?%I?p3w>B3_=;318O|1?uX?aP$ka6%mAVY4sO8A zKotEDLqX;H93suY-fD)qCXJPWp@fw<*T7m|kd8Ub1XvFPltE#wD1w0p>dt~RfV?Bj z$iN`Z2O z@@HURn90Dv@Q{Il;WGmR14ykTBLjmJBLhP+BLl-`Mh1oxj0_B~7#SEoGcqtZGchns zU}9i+z{J4tkBNanl9_=albL~G5;FtCT4n}@^~?+m8<-gw7+Dw?JXshRqF5Lh;#e3M zmas4|tYBea*v7)Zu#1I(;W!He!v_`y25D9X24z+T26a{jhIm#6hFMSyavKN(0QEQ_ A+W-In literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache new file mode 100644 index 0000000000000000000000000000000000000000..eef0c75b6250108f16f5ffd51d3a5aece5037fd4 GIT binary patch literal 6305 zcmWe+fPnOz)Wlr<)S{fkymbAcvC@v|=%uCnJ$;?hw zNJ%V7)GetjNL47wNGt)V%grw?QAp0u&CSotsZ=N}PEAor%P-CNmVE>$}A~K%~QzCQ^+sLNG(!ua&`1o0EJd*VtQ(^LUCzwhC*Vof-5)# z6%zAOxD;G-GAlBR^b|b7_7xPRrll4oCgr3ml;kUAbl#5FtJXIk%F;5{G zB%TVh3gq>Y3{ZHbDnuthW-0>%1GanvQw}m3 zChwG=p9A89Fi0L`9f%E+4=E~51@XZcX1QBpPBB8187#-Z;FpPF638@=3>Ikyc8~-E z0|P9OAW;q#WB|DVO$KNA0JCKiD+9wRRt5&fZm0+(v8NU*B<6wRS|P0{KNnO+fXfCQ zP##Myf@B7Tywq}qvc#NHP|hpK2d5QiO5lPe1@&TxDJ9^1oS6nUHYGo`7@P$YbBa92qKqU&;ob=4H)I2U|i3G~G;W+Y2X)!3Tlw@QU!}Av?+klFkR8Xmv2u->W z_i-uYmF6a;7Qs?7q;Lb}<+Myt9st`7^#fKf=qdO?vSBhL4`I(jAQz-%=Hx(}1}PyF zAVE-`nUh0I)=}i*VqgS8Sd2ny6KMKpNXpDhEUJtz$&UvW5a6f>MIZ>nk^?dgOM&2u z31(t2I7*R~f!VN}3RVlP#Bin~Sb7IlCSctl0+viOi@~mgISQ0M(WG$KelSh#Tnr2= zxIm_22@Q19KsJChf-p!645NCfxFjVfGl>@}3HGLbN@ZSRZe}vfKo+d!CKD8r z^NWg7iwpAeQotz>k|j%0D@qiKQVWVwi&OJT5WKE4V`H ziefH>goFfe;BYDAq?Rb;XM-bC!4_KWX(;7qD`|2mz&)q|RiVkHps%l>Yo`EG05%1G zQU)bpP(p?zX>j;}JPpE7uYxJmIE3T|oS_Db5|GEhy0H-`k%%Kxz)IotRCsL#N@U2w zI2{4=!6s$~hU3f(42)Btu268x%u4~6u;4-u6jz`?KrQ@`%0ACDg*-^*3@Xvm@=Nnl zKOye5L-T%oT3RMd9OTS+ zP>ldf)S$os`4fbpdQm81!WcyZQe6#p1T0=q+XFByG)@$-Xhy1{85qEY5d#B5Nq#)E z)PT7L6c|`!2_}$b>uE7nec^sA>c?=aN!El@+Lw32G9Sq!xo~@sw0hbG@jTU=rYf zx-By$HLoPIq>>I96*YFrD7IlCpPX1+0!g;eFogOA><8S6U^3u@h}_qMrFM1>1_onr zz)QXbSqP)bMoJweKF$K}*D%Mj7$w;kKfRuO&*?FKoB)Idaqfnd=>Q*X1 z&4+e0L4_;KKqO-zHi0C;b!~D9IMWsDKx_o%#`2=Xf`Zf{cx$v6R8&KIxtVEP;EW10 z7@`@}mjrc2lTsCOL3&dZQZkEDlS^_c74q{+@}d1vuw$XQ6dH_B3Kq;@8a3u2Nk{}N z109xtHFfkMArFg87f3mPZX~L40|Of~0|OT`149%u149Wj149Kf0|Tg{zstXp^$}v zp_YY#p^=4w;S37{gE%V#LlP?kLjx-VLklYd1E}+LnU#Uz3M&JHA{zsPE*k@bAsYjO zBO3#Q9~%QhE*k?w3mXH&WHttd*=!69^Vt{}K<&VfYzz#a*cce>*%=s|*%=rL*cli~ z*cli=^~e==28OHb3=E*6$%cc0!Ip!8A&!HAA&G;5VLb-}!%q$d26s*dhAvJ922j4- z$;rU*nv;Qng^PhfkBfmJh>L+Cf{TG6mWzQQo{NDYfs26w)CS+k#lQfL)4aNEyB_##%-~pmU@X$xGeo|>>PKtg(VsbWU7)T$v zsMIemN`?p^m$iCeB~aTC9eD#iLp=kyD2xFrop`mck@3+8{9d`8XIDHmG|DbvDC!=y*KH z%uCD+4A-EBoMUBRxCC{`LpBD6r%;FNW@lj74{EeCFff>NFfdqiKnjJ8oD2+Gp+S%0 oXPBj+VFH+I*Kjc~Y=Fi$zPt%@Bw8)Kl9Pd912i{+vMvY%0GE&qL;wH) literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..12c902a6f64008259c2be1c5ae9ce56356f97ce9 GIT binary patch literal 442 zcmca8o0`D@158i`FO*KtNlnbvPpQmH%*{*&iHR^UFn}V{gCrvZgE1omgE=DuLlz?gLmndo zLklAVLkA-R!$C#{1}-KBhDas`h9yi442PH)7>+S9FkE3`V7SV}z`(}Lz`(`Kz!1gE zz)-@>z)-==z_5v#f#EJQ1H(OL1_lil28Kcw28LP|28Ko!28J^%3=HC|3=B!E3=9pd z3=A!-3=ErC85l0JGB8|WWnfTbV_?u_V_-03V_*%=s|*%=rL*cli~*clj>u`@7SVP{~t%Fe*R&cVQ7 z!@dRz<) iL0k+B5nK!mv0Mxc@mvfH30w>e?OY5D8=)8)oD2X!%{^BD literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache new file mode 100644 index 0000000000000000000000000000000000000000..1c0ae22ac46e136abc49768dc2fe446ecca5b5d6 GIT binary patch literal 2507 zcmWe-fPnOz)Wlr<)S{fkymbBKjKrdx%;FM|6c31CU|;|%h=(X(go<%MX=fz$hZq?c zE;BMP5HuL77ffNZ1g`;gNDb`bP&d<%w&&#P)C@oG+Q7F$W$xv_w2L_iyT4`Q#NoIatu|i2k zVu?aZzCvDpi9%v>a%w?|LQ-a4Vo{|+aY<2TUV5=YacOb}$WV7saH;)5~FaJR&qVuUC&SdM|gFB8QikZB+pEYb|PA{wR#qz~p2P7sHIfuSTn z9ugl=J_ASrrX=131~Wf}iGiV!iGhJJ5Neo$bADb~YEg+oA~;Qf(^pBpLLxMl6-tXU z^U|SdN4=O!A)z!cGdVvc)e4aU<4f}6A?YSTkBf_e5d_(x#zR$$K)Kj7fxQEA9|%L$ zfhkymLe8pqJ&7xD!KNV-u;>Jv3`>u};4}|c#tmhq73Jq*34d(TcpV4xodh!jgDEou z1EW9G0;Cv#M6&`o{6RqljtwGX0HhXY_zOYp0vQS^Hb9XE(tC%JB$9v+0|P@O0|P@G0|UcK1_p*73=9nNj0_A4j0_AJj0_B07#SG$ zF)}cKDvT?P3=CHp85r1^7#Osf7#Q@K7#Kh~uZM|&p_hq)VLuZC!$T$phNnyn450Mj z#LU0|!Z7Qyp$z?u{M=Oiw2b_;wA{qJJpJ^d{9OIiip1Q4oYZ3djFOUq_{_YL)S^U4 zfv%ranwgWLUyzubotU0ltPhSdXdS0tT$BtEK(7_`z{;RDBFa$%JwrVMm?$idxI+aL z91*2OX0bvdyj}u1CmmEPm1m?Dr79%mQgNQD!R0CUA|Z1Tzu6<^-upELKP?N-xb#1y#gQg$&?q2IAl?Bd`=I zu#6zj%)p?<%)kJuRPdUCQ8BsRBz@W#> uz+eI@yBG*Gh+q*5Ye|9Z1le~Q+L{9KE0`D<8lVPSGBYqZLJbC~17QHvXYFMG literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ed515bfd065908dfb164dc33ffb3d1ec7c49386f GIT binary patch literal 149 zcmY#bz?H#(4tA`ZdcE-4)47sJ5w$3@aHJ7=AD?Fvv49FeorGFk~f#C`x1H)BD1_pK}1_o^=1_pg528I+S28JFc28LcH28R7i3=9vM T7#N;1F)&ClGcY(oG0ZvuQr{CS literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache new file mode 100644 index 0000000000000000000000000000000000000000..afb4f5c9e0984507e88d5bade96df4322a88fbcd GIT binary patch literal 5429 zcmWe+fPnOz)Wlr<)S{fkymbA%{FGFXFb{}eU|;}C#zQ2TpkgpN=ls09)Z`M^qN4mF zkdy)o1A{3G0|Q=zK?-3QW)QL^I5oo*quUBIjDdj#%JhT!A%ls5p_GYM?m@Q=YB>V~ z0~?eHb8%=%nsa_iYC(QxUI|o+ff2?)QXiCBT$)n?3*`(xh>}bw4blOVgVFxkAW@Jy zkO&CFbTKe6u!6)H7#P6ugRTx{Dg$P)fwaMtgV-={U<-JVI*_#>b^IWLfq}s-F*7GM zB_toJ9xBSfr~+jvgeMj&l;$NS<)kW<)P$w*bm0VxD)R>&+?$jdKLNCYX?Q7Fz=$Si@0733Ee zXJWTEza%5Ihzo3j9v3Wbo$~W@ptgWx4CYmsd`M9#+&%E{c1z4DMu;*)_4{R_m;?$q zkPH@Sc=}-nDPUk=fQAs1#{hB!x)jb_1G8r#8w0~`HU1YjH_pQch-ZhC(7N z4l?ueK+%v04h3+~!D6IXAvrNmp*S@!MIkq}xHvIAwHTx_H8DAZO932o3W<3s3W<4@ zP#)CHsVQKyiuJg-6oOJqN{jM}6%xP+QUfiSXeKCRrYXSegGE>o$OceK!D0ZGw1F@L zXL`s@P0Y*8OXosL6-A|aAR&d!JcX3Z;*z4wq|%bq6ouUUlvF(~E(S&r6o7_4a#0nZ zmX-<298mwpgOV>ehCsm#!ccu+3Z@v@5UeqmSdyO$OJj~D`MF@-*a%o22b%~hcTmd_ zm^Pv<2YCTzIVkJF41m)lnFLb|Ee*hN4>yT{hb0>vN=iwl^4 zV15P12DH+|8o!_*hAGAtFpP}w$}BH27hafvay~3(z~VR!ho!uk>}`B{4@nfjtAv2Wn~4|ip1Q4oYZ12E`@}I1h8MY6cUSzQ;SN#8Z>e% z!O2`x!PZs*WQ<;NQEFmIs)kZxL4lzIIOk>L7ndk$f(;;)gE*j}mzk29SCUy$NqO!8 zg)S_ZC`-T~-@!~m$%in(;?$fpsQbb0MiIvuJ1`x4m>3vNGBGePN<&4UWh6L)l1qz< zQu9i{PK-G@3Q()So-YO!%%F-PIj6L^B(+EfT)k)J zCFhi;WagzK2NtaU<^s0~kV6n$eqX~hzu((FamJAFGFw2N?87@P> zHlPy@NCMsr3=A_F7#N;0Fff=gGBC6;GBB)WWMJ69$iVQOk%7UHiGjh1iGd-ViGcys z=PF}jU?^u|V3@+hz%YY}fdN#^U1DNjxXi@Bpu)_+pvTO>V8qP8P{+)`(8bKa(8tWc zFq@fy;XE?~!*yl`hTEXT#lpZK&%(d}>Qh>3$p z$j?pHPs_+pOUq5n%hOLU%FoqLh1Qz-86_nJ@tJugsYQv&C7Jno#rjF5nK>!?1&PVo zpmwu9a%AZj7bQakkUQXdU?ou75FI%KJwrVMm?$Td;RdA@d`ojmG7EB26IX|aZAty6CRly@9BtTEWGY{2CF#kZD0!oV@BNf25mt_`}lqTjVGE-8E6+jtDp(r&cu_QGGZe&SiL25C$ z5=bk}gTx)kpGm0-rNtm4ixmAVXj#C+1|9RO&&MF*rdP3U2vD3Sd2%d7$WmSd@?2FF}c#Vuj+=RB#mt5q5={ zl#-uZnwy$e0ye@*!3LBpi>>ta%gf94Agu?z{GxRIl>B6UNXY6V^?md*N^*1T^uT>y zkbzjMbx`97l#6gG099(RLMJaVHx*V(EnsJ0Si;W00LtcAO$KSi)8vPl18eu=H3#MZ zQ0)xUc$kTS;W!fk2jHmPU}nIoIJ}mlHIHC=*;yDE_*e+o57P_nS;F+LU}Iod&j!&8 zYdyedaIpdNFmk&9GBGfuGC``!ZEOq-yFfz- z3=9lg*cljhfch0k<4j=NVF?1#2ZcF$KN|zXQK(j&F%GjIls92MDufQTpIVfYn3t}fSdyRX#Kgdm#>Bv|cZyNt&f^n!9H0t8 zls5wd!%PMShGz^63}%cB3~h`I467L#7&b66FnniZU~pt&U;yb)XJTN;U}9h>V`5+^ zXJTNO!oaD;_{;Q|W-!!s5Jh8HXh z3@=$27+6>t7_?X!7|K~07#6THFf3tZV0g~T!0?)tfx(82fgzoZfgziXfngyV1H&yg z28P>g3=DtR7#JAX85jiE85l&^85k7V85pA385ok;85q*p85m}=GcfFgVo(@>FaVth BFW&$F literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache new file mode 100644 index 0000000000000000000000000000000000000000..8578d354a79e7755d47cd96132cdbbe562dcfd41 GIT binary patch literal 1311 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 J5eqG47yx|ySTFzp literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..7fd09b65434a3a9c97d3cb9a3357aaa0229d287d GIT binary patch literal 3754 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache new file mode 100644 index 0000000000000000000000000000000000000000..218d2683b5a1352b100c2f5d1e524e10c9649f85 GIT binary patch literal 1716 zcmWe*fPnOz)Wlr<)S{fkymbAd)U?#1)V$IPHTtif*rR?ER=fe6L`s3R2|6-x6m3rkZK;NDUwOUx-v)#Kt)@GMct z$j?bBR>;d&C`c?S$xJTINi0&xO-;QOHP5EX&NPR4C6Z$;ix8NXjqCP;dtYwnAcF zih?URz}1Tt{6hj1(uxvuQ_J&a1mTHdkbNZ? zsS42$Lljc-lS^|`^GXs+GV}9dqYYz0rbHXYS}A0dloS+O>Fbx5m+L_yP%pnIT|Xs1 zSwA%`Ei*YYH7~g`KD{(EB~?E$r7SToIW9E|n{r6jO2kO&CF zbTKe6z?|V)RFq!?(hX7wQUk&;QyCZ-SU}pBU^xZ`zf2U9V3uQ%X26+7VVXcj!q_ka@>0t|!XONCC9*L790Ae+!si$n z7;Z5#Ffb~Dcnk~-3eH8Ti6yBDi3)kC<*;-Nk5oM_E(S&rghdr3jc`CU=O$*Sf>Hnj z$T2XtLgl~|EF~b95cnO6Gg!b;12Po13Z$e*4GTe*VZ#DQf^rND3`z_P3`GnK42KvP z7>+S8Fz_)lFbFX+Fr+gwFo4o&9U}unJtG6dB1Q&=WsD3Apg4HJ$iM)?Fr#3}Mn5Az zH&s6^BR?%IH!&|yKfNeFS3k8PF}ENmwOAjNTjMkHN>YmwlR;UzSU;&WGbcsAATc>R zF+H_dAKBab#YM>w0gS?153CMmH#|)n=o#u6z(vs+u=vFve@ICZrr|0h1H%nQ1_qE6 gObovv7*PyLUm*9CKr0ZCQTG@b7@mWYGDrf70oM9Vo&W#< literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..a0c82fae07c21b7e070f05a5ef8c0b26a42be261 GIT binary patch literal 113 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..91c93f198be4e115f1bfc254496ce0fd59ced5c2 GIT binary patch literal 2816 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 F5&)ee!}kCH literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@cookie.cache b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@cookie.cache new file mode 100644 index 0000000000000000000000000000000000000000..5f9049e73d02cec49c913467cac07836bf9bacc1 GIT binary patch literal 11750 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 H0tAEsaa#0Y literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@cookie.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@cookie.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ced0534531e176efac5d9d53cca2e4ab1f3bbfec GIT binary patch literal 734 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 D{0rIP literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@request.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@request.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..3cad834a5c8d0841e0a9786f14251f58d3f370ae GIT binary patch literal 1395 zcmeZxe>{T$2H2qtE-0OzlbV>TpHWg$01~icU|}FwL*w4biaFd0B;Ta190}Cqy0|zSug9s}FgCZ*fg9$4GgF7n&LpUn~ zLmDdsLoq7@LnkW(!+cf-hV85j40~A_7>=_tFq~v%U^vCf!0>^Uf#C})1A_w_1A_}2 z14BO>1H&>l28LB^3=F^67#NJ$85m6185m6285ka2;9_8K=VD-p;9_9N13=E(77#LXj85pGb85nH&85n~285k1y85rvM z85rjAGcY{hXJB~F&%nScz`!6Oz`!6Wz`$T6z`$T4z`$TCz`zh8z`zhAz`(FnfPrDZ z00YB80S1Oc0t^iNf(#77f(#7df(#6mf(#5Tf(#5(1sNEY2{JHj5oBOEBgnw;Sdf9? zmmmWJhY$mUf)E3PiVy>Xnh*nnx)1|Hpb!H?s1O4~pAZAX6d?wN#X<}WdxaPn_6spE z91vn)cqhcb@JWb)!CIJsAyk-wAzYY&Awrmep;DNEp;nlI;gB!`!!2P3hC9Lx40nYY z82$+}FffTQFt~^?Fyx6aFcgR|FcgX~Fia9*V3;Ptz;IiHf#I_V1H)Gl28M4U3=Gbq z3=Hm~3=G|(3=B6#85mT=7#NJi7#PgN7#KXo7#QNk7#Q-!7#JGF7#OCCF)*wWV_-NY z#=syf&cGld&cF~Y&cKi@&cM(r&cLutoPptvI0M61aR!EO;tUMN5)2Hc5)2Gx5)2IS z5)2H<5)2H}B^VfNkz`;vB+0;VLXv^utRw@& zc}WI_3z7^BpCuU>zDqJNxJfZEv`H~AT##a5_$$T0peoJ4;3CbykSWc;Fj1O;VWl(! z!y9P^24xur27MU@1}hl`hHx1Mh6ou3h9xo#469@q81~69FdUL$U^oKBu#yM>1Cq~4 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@response.cache b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@response.cache new file mode 100644 index 0000000000000000000000000000000000000000..704b2afce9e31e01693ebd55b9aa2c6527868213 GIT binary patch literal 11586 zcmWe;fPnOz)WlrdVxfY)%wWl$>=92Go5LIM1xGSSi~(k!TVf6*>cOIHV3L6$D7CmWrvzp|0Y5~z5K4m_1CxW%{@EZ= zkUEeE2*Y$SFu*NyEh@?{LRSYfm4Sf)R|LaS4M-~pgRDVU3kw;L95RM^6r7}Ciahg5 z;A$9Qo-Qs)EGaF9i^CWw2EfA7C$qQ&<`R%+L2QuWAT~@RGL2#g%);Q3qRhN>WMwF9 zqVzDbL-k~&CZ?no6*F2w`QVfbOV?0INOI0jtt?i^EiEolNX#iutSnYYN>#|oFHbE> zPApE<;{tgBgyDe>^D`)=GBEH!%?~X}bIwmmEy&MAge52xpwe)4+)#n^)ROq*{QT_9 zRCo}BY{4dtrwD-A;LXFp(8t5Tz!(HIUcoK3Bsl||qoB4Z6oYbUW}ZSKO4fHuOv$MP z<-Xk16a|=4aOO=)RY=J!PEIUJNlnq?;$mQAWMqVtS|D3N7-|=oqH4)?53N`P+W;a^ z(f}+XkQ3j4Bs%oG2XZM0gB*h{$IQS85(Z(A5?IJLK3D3xg0=N3}T~bfH@L5=s}K!*$85TG@)Y@LtuWVM!pAG2*R*j z3bUB1$r5HbBLf4>+~U-dct~jivk~M9EV6hqF)Z}!*cccdurV+&UWb~c5S&^9&cYBI z6v{J8AXz#+vn(}Fp)4_{G*zKAFC`UHbwWg;YO_--K{dN4tU`x0Y7`Q4ic%9(Dityk zixo;T5=#`aQ!8~q6>(;4N*-%TXcyEkOY=inwMOXnV+Y}#RaYr5Oo5~ ze5k4LOhT0~xCx6%P!xkO$QF=|APkcPu|fDYOfg6iwPOQrI4q@t3IRyf3AGL!DOhCj z#0{)OP2*)?c*V=Wz^DW@3tR{!LaSzIo(DCS6QK=njG7r{5>!8RW8ooMz``tnw`sv~ zO_%%xa|zf+NZ$<-39x{7EGa3-rxuhbWJ2l>h$y`FNUg{$E&=!7pk6KJQb^3pFUd$PQh+LjJEb@=Hx*QmfNG=R zQmb5or9Ehb!zr4zZ=g0%HQmB}!3#A#wW1)iC>7c*fr^2PGVBV7NVPZk7#RHc85kHV zp~fk=g6&YiTc9cU<(H&dfqQI;@H`AH`ie91OLI~{eKgQO1vqgf7Aurzq~?J$0&G+R zR$i7T7K6qd^w7I6uxtf&7Ih2BC)7wDm{AQg6;11vN_RWODe zp5y{j57Ik@lY!wKCj$ecJ%|Ttn7~@VAk#su5>TYW;#fx^zbv(=yeP8-G~fhkc!J9% z5C^0j(LMng1}ZgS)`0jRj2Xmu+z9gzY{U;_AjlAqp&)z}7Cj&i2w#T@fK*Yt4FWeD z);%mKs*Fc#6{1Vw@f^s#AZPAmVqkD(W?*1E0^)%pfRvVD5qOwPp(wSav?vc04M$&^ zhSWgXAdUccX6V^UJqU|*Q1Sp_P&k4@7lh%GAVDOIwbno#a|CGz;loh#L2M8{0_7u_ z24NCmCPX2XyDBgTg6jViGCyqYYcaoCS3Zb?g1pBxOcOYJ)ih zgT@+TsD%Q$9;{kOu?Chbk!D*Evk*n8DVas7$?%y1P*B1uMYtRhIoOn&fgzQffq~H! z>V5_1qEyi61JZ~CIEG<{7K5@Js7ipOlG36~GIJfSbPsh0Jo$qr1Ig_aLd}9xM8yfL z%zyL5xF#?8Yt%k^x~9ufYsLDbZjp5J+AvhItcB7-SYQ zMllyQnlWfbnkk=Af_Vv4fq*g*$nWT4AaQsk!NLs|#@M3;yC}$T5Jm}9m_aC!h3*89 zE_5*zXQ4$S%#~=uAY+g*ibG)L52M(H1p~~_b6^Zm-N(Sd0Gg2WW?*25XJB9eP5req zFfep5FfhzzU|?9tz`*bkq=u1!0koPRoRNVcnUR43G`G5ik%8enBLl;IMh1q*j0_A< z85tO!F)}b1GBGfiGBGf;FflM}Wny3eHQ`S(F)+MjVqlPGW?=AOW?-mhW?-1b%)l^* znSo(0GXujuW(I~s%nS^IEDQ{qEDQ{wMpZNm14A1N149Q314AbZ1H*b228PX`_+e#W z09E2)tPBkOtPBhjSs556u`)1RV`X5t#mc}S!p6WL!N$Ol%*Mb_#m2w@n#GvN#=x+a zje%hg8w0~xHU@^rYzzzr>nF)#>mF);XWF))Azykoc+7;3l}7#4FeFzn@G zU^vUgz;K6)f#EwB1H(@)28Lf;3=D?c3=E*^DTAAVp@f@(p_Q9~VFfn>!!vFMhF9DS z3~#s@7~XO-Fo^RoFi7(-Fu3zDFo33BC-N{bY~^8K*v7-aaEFJ1;T;bHgAy+Tg8?rC zgBdRagEcP$gDo!ugB>peLlQ3o189)9l9z#DDlY@WQeFmzZM+N&*LfKj-tsaq{O4t0 z;N@drkmqAy@aJP-2 z&qU7vE(&A73M|+ZI#{oMF?baKN?i=L1J<^Jr~<9U(T5E8qH51C0L@LHYR}0mE8XlCU4p040;`c>BOI`jb2cXf!(vVbaQ)f9%)rnA>NGPjFg)a9V0a0dT0yU|^+Aq+ znU zPC*SONMM2r0Z>Q<=jW!v^z`#HFieK7e<33HU=12r-2j@wPGp1x-)Bw+hTot;9|i`7 zSQZ9`R8a4Zfq}t@oq@rModLYwN0*afR1rPhx0*IAV_O72Lr=I4hC=vc+bng z@E0^`!+_U7sFe($9uy?)!ka`vJPZu6JP^}xMjt!N9-))rH47Fk|vk z%VAD2VPRk}XMs2c)Az7Eo0gMU0vch1>DtS~z;G1mawr8^CkQGGOLIzK7L^oL!nDLP zGcY7T#ql^1<_1tkha?kNn#<*7U?}H>xO5gj1H%%~&^rD!2Xj8GGX-;t4L1XW3pd0l zBHRK>vZ#d)H!A~!C@Vn&px$N3O)P*}9nQtT5XA-235`fd$icjcSUgwD%)n3&En10i zB`CyDUD?XUz|hYIu?rRyu<(VXa8OMGO~nk5`6-w?B^VeORQQQghlV4^IOszjF z149sKm;-5fAJ}}DTF*TA`auU41_oznA(zj{zyMkT2r>tc2VqW!m7_4FZj1~Je$aCC zDiZ_4T_#9*sm;v5UHmlY!w0l%2uFz)%3K@>Xy$Fl>ObL%A6kVxi@9 z3J(KA4z!$Zw;0E=8xJxvFdSusge8hbnEjAZ6<7@s z$il!74h>>cRt5%ZsGaVt3=Fv$L#TAxb eEP-eoz(VCTF9X9>Xyu{L&%j^_t@NSAGXns#AbBhR literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@response.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_http/_gleam_artefacts/gleam@http@response.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f68d652c1091b3a995d2288178e79e7abd46c5d3 GIT binary patch literal 842 zcmeZxe>{T$2H2nsE-0OzlbV>TpHWg$01}XAU|FLw z45(UjGK)(r85kJ67#J9MP_*V3lw{`Tc{4CD#9_!4r52awl*BVIFce|P6_*rc=A{=i zFfg<+FfbfBUiV{S(SbE{pk{+8kV`;zg3Jb)0aDw}z`)SKz`!t5JeV06s+k!WW-&7`%wc9=n9Iz-u#cI6;Se(e zgCGk7gC+|DgDwjLLo^EmLmLYNLk9~3LnjLZ!+I75hRrMt3=FIc3@oe+3}LJc4E?MO z3=>%y7$&hYFkEA0V7SG~z#ziLz#zfKz>v(wz);1;z);7=z%Y@GfnhBh1H&FR28Oe2 z3=EIi7#IxL85jcD85n}u85ly?85riUGcYV*XJB~4&cGnV!N4HK!N8En!N5?#!N5?% z!N4$?gMnc^2Lr=C4hDwv91ILkIT#qUIT;vyI2joHI2joHIT;uxa56AV;bdS~%E`cR zl#_wsIwu3eM@|L?el7+EK`sUcAua|6A1(%l04@fG7%m2e8ZHKg#as*wd$|}G&T=s@ z+~Hzi_|C<^@RN&y;TIPJgCREqgDE!yLk2ekLkTwnLn}7}!wPN&hG*Oi46nEu7~XI* zFudhvU=ZhFV36ivU~uPQVDRQ)V3^3mz_68vfnggD1H&C228MS$3=B%V3=9Um3=C$x z3=G!13=Fos3=DR>3=B!U3=CT3vdrXEkT@5JU|?VX%f^FbnV=%nn5%OOF)%^*dQzkCw2KE(22@jTln4u2# z%glkYz-Gfj42v`aOsi9VK8hYtx&xU3lMg8>1(!G=F$PvJ$-v;2m{W`pWrKw8rj}&nr^M%%r4|)srhvTzvW1ce0r>!z@mR7Pv_J>h z3UZ_m8w101a9}brGC~DG6bOUlK{N=1*f30`tPOIw8O((s4hUO71weL!Fw9O6O%Hno zK^8GEFytl{#1}ye^Z2CvluD2|$U&4w6;2BnLEZ=10I~^|6UsA7GC*R`P(dy`p%Dqv z1=5kh#K3SJ#DPa6q}l`tf#gA^fG~&+5(8lsn6V&11_tVtry#>sVTOZj1!0gSAPEqL zNrKoQtPWKSQU}7wYH8|9n8gg@AS)Rd7z&C~3sUn^5RspenwXMW1d;_gjMC^QWDPVN zutYShKuuw0V7Le7pypVx5VWlXaw*8gpr8d|O_;GDL25^|7MkHuI}n8_xOxVcF$@e0 z!6ikRdFgO9=nPnaiBAv6cOVSX3-TQZ)5nM0AS)Rd7*g|!ON&zD3-XIgkR5_W8czcl zrqhgtfuR7D;n8v~Ode_{mfQ&ocyM6B8l;38y`a$2h3SNu4WqFJKWZHdQ%c3sm^kOa z5;#(WgMk6w;7cum*Q)W5S{4?5AP?hFMnnv4V_{(U3yL8INR|Vc2r?anVZi~SsTd?i zG>IV;2Z9p}EL-}58zvx6V8tlu0&7ITw7J3<3=9mq3=9lm3=9m73=9mD85kIrGcYi$ zU|?VXbq+Zg85pz~85ndJ85lslmTim-3?CR77?hb97*v=T7(n$|6B7f&VkQQLeM}4t zx0o0ho-r{nyklZu_{+q=Aj!C$BGBYr2VP;@B%FMv< zkePwuEi(hdS7rtV78V8ueHI1=V-^MmQx*mWP_8RxVPI%yVPKfe!oaYeg@Iu+3j@Pe z76t}Te1n>TwyX>c4Xg|d+gKSGjT*Kl8u4k0~-T_5<3Hf13LqQKRW|MFgpW7C=|njCk@Kb&&bbB)lbXFPfN>9 z%*)eHFUrr=PpwGIEyzhN21ikRW?o5ZQDSmQW`16=eo|>>PKrLPkEjpM`ryvEesNJU zgbUUM>$&TJRX{C6l*tBqhI$5|bPSS#B@5KPBrFAj4L~sylBoKzh5{M1Lu)Z{i z4Z^U#E{F}nFqgr`Xpjbwu%sAhix-sIKp2+VKr{_f9GwS*K*0g>3CL#@#SN%%1&zGH z3PCI*HECWxo8B{ZlF z3?)UCFu%yMGB7AWeH_Bdzz_~)PiJLdm<45DXJcTv1#RVEh6+@I!6h{(wImgmAVB_? zgoeuvRtAQ9pn4x^)CUq5kn|687E*xx1En8m8o;y?=C^{xqT*Dv)U$wtS`7jp*l)|&G1_J|wHZ=wC?aW28FfhbJJ@J%kjk(8fFHDP0Wx2V=prU!(k{J zOA>;Xn$R@H0G)|}73{tY3=APqZ}2iQFo-Zh4Bf}bz;G1G{?5q2@DIxNVqstigt{|< zg@GX*%D%?Jz;GAJ&Szs_D1#=$b~XlvJ}4W-U$7uWiqsqy28Mj789!MV82&)ncp?>6 z9K)u5VEl3h28Md5w`3U^7*wIozsAVGa1YAnV`5+sV}f{h8WRJDo m1H%Gn*j{F4V7LusV{T$23VmCE-0OzlbV>TpHWg$020t)U|X->&x1_p*@3=9l+ z4da;?`L1(yg{lEjAWK1}g7kybEoWe0Si!)+@P>hbfrF8OL7S0*L5Gonp@5NrVH+a@ z!v{tN24yA&1{Ed-h72YKh9)KkhQ&+_4EvZE7;Z5!Fg#;oV0g#G!0?xefkBd)fkBp; zfkB>`fkA zSQ!|Ou`)2cWo2MsWMg1pWn*AqXJcUCU}IqLVPjyJ#m2y}ij9F`8yf?|Nj3(C4{Qt! bO6&{_4(tpJ{_G44!R!nSp->D87Z3&j>i$Pz literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_httpc/_gleam_artefacts/gleam@httpc.cache b/examples/http_interactions/build/lsp/erlang/gleam_httpc/_gleam_artefacts/gleam@httpc.cache new file mode 100644 index 0000000000000000000000000000000000000000..5b7172e73cabb1e742c38349e018c14ae3ef9a4d GIT binary patch literal 29872 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 a_CMb+L#{{!C3$5Q1_oVd83xLPAPfNNE;~Q~ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_httpc/_gleam_artefacts/gleam@httpc.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_httpc/_gleam_artefacts/gleam@httpc.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..2eab0296b4d17c5d5ec1176eaa342ef103ff2cb7 GIT binary patch literal 1352 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache new file mode 100644 index 0000000000000000000000000000000000000000..cccf7e6eb705bb274bcb69a99d531fbdc3360118 GIT binary patch literal 55164 zcmd;QfPnOz)Wlr<{E`Cw#N?9vB9I^_h+tq~087Q^mlVJRxS^aO}G2@qF<1QgOr^O8$4 z^Ye;9#wF+H6_*s1CYNw2lw@QUgT1K7#l;Twv{PwjPD&~`Jv`H9U=Y^RzY%;}S zGcIEp7`Q-E3=9mxC5c5Pu0=(V47wqZf#Gl2I8mBWr0R>V9qCr>+8ekwc2unlxAT=ONtTU0F0Wt@q7es?FUON~aK}s1I7!*P> zQo-q6p|m(P1(X>w^HMUC6H8JR$}4_@9X6cptrrxq70WEO*p)uNKr6g_yU1@#xm@C7Oi4BJ$Qjtn`p$e==q zf&vf4SVkX^!yvvcOUx-vMN}yz`CxxT%6A2@hf6Y2Gm8|GGct2hV6{kTGRWs?r8zm3 z5Wg4eaS1@p1bZD+Gb0sgtU(M63PD7NfhtlMfMT84Fu>;okn2DJ0m8`UGr)_J;MANn zkZns885njd;&d#?0A#~tppFHJgRm@=4^jg%j#$UTiW87CK<0q-gJ=-OX9oiV4@fBk z0|O!N!@Lc0706VOHdyWDn4Ap~1__~K5{dzksUQrq3B<-`ELJ0fONuh{(ot-Oxz0B~ z58W7;ry;Ge)S{fkymS$$X0Vt(L`)x=O<}?7l3$(&RR*SDR`_M+fcXpz3?QpO7{tdS z&49fq6oAINAe07~0P;V`6d3If4`h%C2*Y$SFfhOZ1zf#?bi;Il)WJ+;K%`et^#_sy zDTCEF$aNV^jsv8Cfq}s}Kd&S+uQU}V4x&LArk4@s0B|D_stJ+0z=0ZHT#}NLnS>Jf z`uPQ*#vCkE{J}hA_rusABT;n5L!t;@w80!73~jE$%mO(a#0JqIjPN)kObet1nqS1g z_z`3?sJMd#0irgAHF-fzS;ssDh*uJG5Y6M{#5{$hR8V1GmYD*nq?0oe^FXa+P(v7| zU!eqCVP~XrLCeU@Vz?!G3L&6|ZBc4LPGWLuu|i^ACBl5LzVgIkg@U5gvdsL_;+#r_ z^vtr z2xHWjuxbrl0D(sciYtptQgdO3_~xgif;56LBTO_mKP45lp$!s23XjZUh2+vAP&YQG zQXw->MyAEtN1A1XdxcGvlE0n|r^D(56#b^jM z9NZfy$V`DHFj&$9mk_Yj2JRw*BNUM)U?GiKfHRsQn+q<}Atq-QgZ!9Zq=P8bpb4d* zC_lX@F*i50NDr3V;f)J$=u@G<2003p3^1Y@$7l*H_an6?K}LX58ORtA4Z^Tg#=wA4 z{xePnX#f@fpzao|R)8dIa7cn$@Q9=U9{T`wy^2!HP)0={d7KNJJ(E%uAhke>0yON9 zOI}2C8Y&64M!i@eAtyh-08xY^LK!18a3y+Jytt$$m8J)SMsuK^V_;x}^#)Twa`E68 zXEcFID}XDv#KO{4NaX#1g6b=fIUwC28fGVmjcgaPINVM~Sek*>!tvlva%vHyImkFzj6)?6l~Qtk z3Q~m*4Fi<44+~{*?F4R*K@9}A4PluY7T%!FPDo~MYJMrW_YGDGGXW&$mYA7?sN%rV zNCd3a1!7KnOBmUmzbBF$^{F`(7e=& zg4E;^LXiV$MZ??#j(Po*%Dlwf%w$+#xD0uAIOWaq&G5nUYSY;=d|L;$MH z;Npp(9vFOFJuxStq4jt7|NGdH+ zNJ>r1FMB#%N24#owwY~9MBY2a(-S)F}QsS?dZXS zE+Y{0_jG>g9X%p z2Pa`rv_firh1A643|N5(Y2ks=Bsd4aX1R)yVimP8gOtiBVl*AwfCpG&ZYn4+K;8re z28a!!K^Pt|4Dg;!9!NR7CIuA(5dVR?jY+8rAn6ov<_Gm96H6dj8RTg^`SgfW_mI6MyvCs4~BORE6cXb>BO?a-_OSqH+fcm)S9tg=8FXl7u5 zja9-1CSjfcDMpuLVqgSW2ExdOgTodi08LI&?##>gEV1qBc+G(j{7 z!v@1a0SdyfObCv`L0e=YM!!LB24P}TJFGlHue6cF0>lPkm@SNmvIdk%(7L{$q>G$~ z5Yi_C85l0(UM>LA2y!|Y6YG9h3mI!q9VQ8KC8(E+C=fw*VahTxFuV?AU|_^OQ^^df z0YK>nguxDl5~%5f@W3F1qCih6o!fQ2&}6J?S;aO0T~OzQ=p|Rhz-J1(fHG#e3-Q) z4_(46L>{b#8AfXS!u*2P?S&Z!qfu%ISX%`F$N+C!<>aTQrxw9N6lxVH zBZBLFm}ZaxAdD@hL_rd;l`OD{;pB|OqMXd)5?HUZNSkXobpWuVz2$Yd>eHXtuG2R`lrnq*NZO4UuRD9A4= z0kw$}pd*@kAj=aV1B#%|X)$O*Fh31>`4OnI2Fd}DwW=V0!W;#43duzqEc2ihM<8QC zconSZ1aUwZE&#I-N~2@}Seimke((@3$uEHF0q0Z{aU3BJQ=cZsz|bwnz`$4x6;ben zEsFyM7`XmYD9*?)%}G%xF3B$d4{;P{l$Pj%CYN;(1O0jV3TdU_Nj>OL0;ul~o@Ofo zPhf+4$)Jf?(2#Csu|k4heo<~>P6B4<0_qSrMQoJ@^ES5Tl^!(ELE#O;`cOVRazQSO zho%}(A^_PAQV7e1$nv;~R+zqv)(i|xHXsvF6BbM!Bm=@Qv%%#H5z~Em>IaZc*jjQ24C0VPJSnAbdb!MQX%@X1GCd267>Y2H`qrM1$BMTo2`g z)R0s%!g4i^0ugR5EZ3$MB^INMG@(ck3L+mb28KjL?JNX#F4F4!_|%G$)S|q^99V@4 zSu;^m2`&IYfd|4MT_8_`@ML6i!7yr}NJdfz83Zzv*w7|WR&>Au7^V)~+CuFxqe-ESN5NVH z1(0=Bu$m9NK8b;WVYdnc0}p{U=^+1r0u1Cg7)A{!!X0avGmtxRATvQ2ws;C=HmP9; z4HJ+vK}LX#1Ywx5jEH>_m=nPucfl%46j^a;1_mwMyB9zs4uocYK@I|8kfUH2HQWY1 zfx%V*FfzfH7C`&?FqeZu2$sa4QY?^J4_I1)(V!^~kZM?XA_gu%zK7*VxF~pV8a@HX zfQTi;z%wWsrF0k=PU+(uD+N0fM8I4DVuLWqP8dcFb;79?=3gxBa*)v=XMr$c76@h$ zl*U>vV~!ty%!2A*AU4s%`tP6~7nX5wkdYv#fv^&^@Bp!q#bM4y7pKnbILt#Z8YL`1 z$pa*TGBpcrB7($0P6A!|PB>$iQ$dkb&U^z7c3f)Ybnm17S2u5~ETx2SU2D)X4y3L% zyeWwqiXg{=uokqKfLc#7;MMIlJTBcRpk#n2uG zD8i7U2|NOlmRST^_EVHznwy$e0-9P)RLCpMO@b_NM_ZEu+KZTxitJ)8gtIb>6=190 zKr1mIGvQ#rX6Atx?qrsL*Q6AK*Q2C@Jd0)&XeCEJ#yS?rq7`tL1>rAPcP2X%w0H^P zdDuc8F32MFq*UC?dB9#z1nqlPFIGrNO-n4zDSRcTC zc%sW^0#!QEj)m2Ph-Nv=?I1s4ktI+(J6kX?+_hj}V0;QSMypjPU} zWe>=oAPf%(L6FZF7#K>5D&vb%(=&@pQj6k2B_XWn0;$HSh|qY|s{jTDJ#fPgG+u?6 zID#1twH&Q$LAZko4`Q^=0LZa(VQ~yI97ba;Wv~~DShc_kdE|*Dm_ith)eiKM4CZ=J zhJ|Saxg1#>y!Q_z2vPt`_0VQ6DD+@LplQ3zymXLC9OVwo*%na@47;Nk7#P(-`a#V? z=*(epWnOYdQGQ;2X)$c97u>u7*#<6y5*3P4iwpAeic=wHyMW3S$XrKeUOFh}gHId) zl_=mQ0cb~QC8!~gmtO)ac~YP)FK}H9X&``B(dCzdOFmFbAXNe6mDD2Gs4|yAa#3P& zMryG_Q6i{40$Mklr;wAHSOzM9Ku&X-f)f-kAWY>deGO5m7v=~$w>Q?o90y8nAPjRYhz-Ip1+XRuWM2z3y@5Nx z7&4Ef7#P0eK39erwB&-KT`(ZqK^PQSFpN=@!)EirV;kVP7RV4jY-R@H5$KE-%oH#U zG8)4;99a>TX`$wTw1Uh4g&YXO2O}97VD^Ihh_F^FNG%A1)M8hF!(NaEkjC>03=B4k z3=E9tKs-^Ylu{ubK3I1cGT;D7pU}+)uq{2H8Ufc;1kywXZD#>DSs}Gk zB6!CFxYh#KSqY%g{gebmv%3Uz>JHdE*q(H7MFUm_>uo|U0quAw&n(UWxi}d*6azgT z4PpVJ*Nqs=08bAnaByf3_)A(pzH_BsX7Y9si_JH*hZMJEFpo$ z5;&Sc=`9>BK@*$YU_~2_0aTc=AS*yP0?isK*P;mHVc7t*;sNGYP;y2VXM-&2gH=ti zHXL{fJIJvxwVFNK(>N( zgVGW#Q-G#Mpwi3?;BF8|7NiQ6?u)=DiGXZ|r4v}b1DOZPU?A1t>HL?AW@!z>4}K^SH=BjN}KNK_#msRdF8a}q)lM|uY-207uiCIdr_76Sw0 zZV(UDl66c0t%8LZ0Gb8@wPfK3jAA=Y6x6haR?t}Ya>0(!02d(OEytk#EW`rPDW~~G zkjfj<27nG$gW3v^-Yi5LcyKNyvp6}iCJYqg3&MF`0M zAT9V+5*qySab#dv20m1ix`SW1N&=9pL9Qn@Oz~8TAkR0W#}&z=baZzp%*D`=A#ls8 zv^YL9FD)MwFrWYe`3#RTLUCH_#K5oAXkE1 zW~R-+uuGePff05pg950^0(C$^ixEM6Lx@oz7DyD*yMPUcg8IUl#R|EZ=^3E2FOa*z z8HwOSs-Zn)$ZRuYcou0=2Q$-u>1&1!& z7x0a3;9(t@FvvET{~!`L5(G#gNRxs!1H)Wv1_s9W&}dRXjJtvi0MD|3@;oGUl;nfX z_JqyGr9$RTAg3OK2YpggbQDta5|cm&Z*zfKmDrlMWtoWzkU^{z_;3__LZq}fHLWxU zY;A60Hu5YEco9Zo3HUfU(2*?)pn?N3?UR_Nke>rOU27s0q=>63aXkG-RIuI^JM&VdN41~D+$;oD(~cFr#Ny;K8*JSdlgXb^_&{{*o?;>gB<*jSB3pD_k01ZgHVP+<*0aFGTYP{A_r z1#%F~ag2x~Vn8S5f~*2r2?{(|q(jB!WEmLrWN{9rfcEH;eCjU9IuHg~3B#nOO;{mL zg_8?lK1Lr?0|glDAPx{4gyDxzFv9l_panH33}I;;AvHycfnf>0Sx@qgB?N^c2!lcq zvos0{(j^`q@d(BuJhtvhs@0yGZ+D>p}a8w-~D zL1`LP^W!24C8*Z^U8j4%x5YZhUx9kgvgARl3o z0ttgKMo{6J7=x9V*tVolaseZ(xP)zo0yzqs7|{-eX-3(WM7>*5VMFp*He!LCjzb!h zDnUsJqy$8RFwTq*QbfX%MVN;{6&A=`kaiFavlGNdwhLJtp0p{wP7+pWgUfM{jUZQG z2!bSFPNDGbR*)}1cEHL(903n=0uE`ADIiCIOajp$409le4Z`p+0x245@q#7m!_o^D zDX0%I*L%{tRDi|mXq`7&=RudEW4l@ymb>Xz>cH~OP^qmzDMkj2Qru!)g@;X5M(gA@}3gFF)hLp~D&!$Bqn1{P)p25Dvn z1_fpYhG=F6h6H8?h5}{=hEiq*hGWbO3}P${49P4E3>hp83}q|~4BadY43}6K7z9`u z7#vv{7<^b67#6ZJFnnfZU~pq&V2Ea8V5nqcU^vXi!0?!jf#C%k1A`em149Bk14B1E z1H%q>28O-t3=BdX3=E+h3=Huc3=Hcz7#LVM85pcM85mqR85m}8GBCX4WMJUsVqoy) zVqoazVqiGI#lUcui-AFzn}H#bn}H#Xn}K07Hv_{hZU%;j+zbq_xEUDUb2Bh#^DriFfdq&FfjCpFfiN~VPN1DWnfSjWnc&vWnicfWngF) zWnkzOWnj21%D|v1#=u}N#=sCR#=wvx#=x*wjDg{{7y|>hI0J*OI0J)?I0J)|I0Hk9 zI0M60aR!D*;tULe5)2Hc5)2G35)2H15)2G65)2Hf5)2GE5)2Hb5)2Gw5)2HpBp4Vr zOE54zkYHf=C&9qrCCR|xBgw!}CCR`rMUsKxp(F!?yc7e2q7(yzsT2c)rxXK&mlOj- zk`x2OBq;`lrBVzG%cK|>9!N1T{E%W`_$kG}AS%tkpe@b7pexP5peN11P$`Dt|9}&LPZ9K#fl6JOB5Lx_9!wi z98qLo;89{=kWpe_2vK5SNL6BBSfs?jutkZ1fl--(Ayk=xp<0=NVVg1o!)0X#20;}D z1}_x`20s-Bh5!`?hCmeth6O4N3_DdA7>=kgFz~7}Fhr^{Fr=w6FqEh=FdSB8VEC!Z zz`&`-z`zSSgE8xOtlyoLbMnd>a`dcwrMdi?9gIh*r~<9z@g2+V5H5! z0NS?Xq0PY1pv}NANt=OTuQmh2V{Hb8r`ik*&$JmBBy<=Ulyw*wigg$m4(c#4+|XfQ zc%Z|;psmZmP^HVjuuGSL;gBu^!z*0|230))+q7%u8DFevIXFlg#C zFr?};Fig{DV3?=Rz;Iljf#I}11H)N;28MI`3=9$m3=F9T3=9PZ3=Aa(3=I1W7#Qvu zFfcqaU|`TUWMHT;WMBXtJaWX4f#Ink1A~MS1A~ka1B1E|1A~PT14EP%14EAy1H)n? z28QKE3=Auc7#LO=F)(~LVqkDIW?+agW?+ahW?)!i%)s!;n1R90gn=Q+gn=Q=gn?nJ z2?GP@8g6A%1_pIg28JS228M&C3=HgM3=E)?RyE8R7%a>f7~;$r7^a&sFzhp9U^r>U zz;Mfqf#JUy1B0PC1A~V-1A~t_1B0JA1B1Ug1H(*n28J`{3=9{{85k5S7#KiD0)|;I zFf>^(FzmEoV0d7`z`$k6z`$e4z`$$Czz}W8z_8Skf#IVi1B0y<1A~th14Don1H)V^ z28In*3=CVW7#RLrF)+wjGcbVmn&??GFhp51FjQJIFf6cUU^r*Zz;MBufdO>g3abqR zgQyJyLxK$hL$?hB!wee+hB-D240CN57?#;EFs!g)U;te^^2~;Tfy0)8!O@n1A;^}2 zA>WpPq2HE)VS+6K!+u)^hI6(I47_#>3}SW+46$|$3{&hF7@pWMFmTy3FhtrjFf6fW zV7O$@!0^qUf#JVB1A~_X14E+&1H(KA28I(33=H2L7#PGI85pD;85m?785ler85q1B z85lsjX44%R7`h!97*;wmFl=#TU|@7&U|@D)U|?}#U;tf#P~*hF0NRPU$BBXAloJEP zHzx)LerE;-6=wzpcV`BMRA&Z;OlJm$Y-a`r(B{NB&I}B@of#OoTo@QsTo@RvTo@Sq zTo@SgTo@RpxG*rRbYWoF?83ls(S?EGnF|AhkSha&j4K0!wJQTdfGYz-oht)Fhbsfa zN>>Jk>#ht8Z(JD|zPU0m{Bvbs`0vWV;N!-?Q0&IQFu{$1VX+$n!y7jS232as z20M2K1{Ze*23L0mh6(Nr4ENj_7(Tf(FsOPkFa&upFa&!rF!XycFzoSQV7TYOz#!(y zz@X&Gz>w(4z>w|9z)!$cnjhJ`*13?F?M7>s-w7#e&T7|!}KFnsc5U=a0V zU3=EJPF@%BPatH&%>ktM8;ZOz! zkx&K(`%ngk@=ykbickiIMWGA~*FqT>u7@%(aD*{1$b>O4WQQ>@Yzt#xxD>{~@Ft9b zfi;|gfjgXmfj^vqK_HxgAt9WBVQx4B!=Z2nhQr|u441g$7?dLz7}O&e7&Ido z7_=f77=j}h7{VhM80JSXFn})qx*5U1@GXLY;YS1m!_No?2IoiyhJZ*0hLA`Gh9!{< z3@;)X7~VuOFqlL!Fo2HybB$tPNQz=$sET4>*cipYurG>%;cXNH!+R)(HZ2*FpbY(t z{M=Oiw2b_;wA{qJJpJ^d{9OIiibT-Kzs33)B_##%nRz9tMTy{bE5-Urpd+RA3lfvF z6Vp?R^}$mBpiO-G#YM>w9(b)bcxRg)SQcs#1AKFxp`MAJ0j%8%>*InaKOpPxK^pi$ z1pKZ%_?^H|Q8)$bUVtrvbx9$nz}nFu8fFd6$hOo z2f2hf9(=HDyh379aS1e?=zx!f%})c*s^{hxgU^!A&CgSZ83s;9pdErqIr+)i#h|O{ z+;dVBa~1qU0zmuu(u)#vi}j$Y!5twGTfs3;0c;t_Cy5|+;05bBnc0x*iy+$pom?G# z;c}o8IM9v>%gE13@dOzM3QTavR7WATA~m_RBvm0fKP44%o_BJ7Zf^9hjgioQgp88rWEvtsrlMcVcFifP^4Xkf>0cnU|geI?cQ!wJ0qyITf5HK<>}a z%c)cV9gUru;s7>?fdPJ^Sz=CRNu`cLW?phmX$r`>#ia${TLRNS8#yyl6{10r36cN> zA$ZI>Ju@#|4sK3rVo7Nc zR3L|zl>(@kEw<9vFE20Gg9M{qeo?x9N`A6_ZepH(ab>YyMoBLGDtB0!si3T^;0h_K zq2@C9KpBXff|mGU2hu4d=VYell|Wqx37|x98LyC(T2c-=%sCO7+2E0=0V-!8UeW}| z4-8DP5$@(UDFb5cR~1|SLwSfQ(skdOeb6JW!l z3I(M}3Tb%?xrv#18k!2#;4?iy5w4)1uMgR;4=$@9*%6%Iq3el3_aBsiGcD+Z|J;I- zN(In;t&l>ZBqP-l<`u9>36P_*VON16T_vAZng%I%P!7Jf5>#&HR4RZE;|Cp4RFqgykP5m|1LD$Rcvb`30y;f6HANu-T!SR&C?tTQ zB0(XcAin^_16_Ryy7@8z6iZ+=;AjHpyu@VCeRCxlpdj``aV$7n=_!DGU6iT-I&mol ze7Gn$H)o_4LCP|4x`W}FN|ZD;p~@Lh(Z zbgKm@f0h=fD!{Mm1!X))t^!~E3!nT4I~Cf<0OvLE3Eri7C7C%2pfkJ+a=^!OLmDKh zDX1Ryhei_E_wbTBGq1QLF)uk4C-v$Sof}w!|>a8rGm@5EX9FPfa?7{0F zu;UUSPKOtLDfy|zdFmw!>7cBr06H?b5`3syCbXgeoe>T?-~xQsLP26)W-`cM@+eLP z9}^Ca5J;^6PwmzSWvE$Q36?MwY~hMQ4nt%OLjxU!fcyeYq_P~%2uMyKXn+MyH-PgU zLH$te4B#{L;nfo;=YZ1>sQs6hU#^e}s)oR6B{K~aRiL^sF+C9+jL^v#aOh@&@9Hc8 zRhSB?MMe2Vdf0sk?sjPSW#(uS?Lv4B4vuJ_#NrZAA}A+ZjwKd@4j=)=a!I}dsM1gX z)f#$AP_N@GzJfDKOF$|$L7fzkVuf1RTo0(W2Z?~NIfwv-oCm1#$b>ZEK&^UceF{72 zBVQpC)WrogA0UksaQe1_>W0s+f{X??B;YZkudm<+zO|^hB((sP<>BUlS}LIY4~Z&p z%?66Fl6=q|Kbd(b2W*4erC=w)`j4P?2 zIe?nAAj?7a!CFBI(D;Ygp9v~fOBB*FD^gPwit`l`AyyV77A5AUmZTQJBT!!-_0kYgL0knoc!85NUK~DzcJW#-c zT3TS|K^N_4K;5UQP;C!W15Pv`eB;iGdxc13oqq91EGn`d}wAFhCrM)C5k+hv?2PR{*tP zpmClAwkK5&-nLgzfCUX~6hHxd<{Bu75y6LApCSbv=pv*<1<-xI(BKDI3i1^=b?6~A zd-U}|*8#&r2&5*nBsCYLJ~1T)TpvT?K0gh5H4`*fB|uG1Kx$;^>np%hSY|QkNS(@p zR6Q|7Sb!X?0cyxt!CEq!aR0d=lr)!fmXqwfe_H}Oaf$R3YH^u6hK98f*w+h9*|$40j?Lp*Tq>WKwYYVO`|4KQ3wx1 zc)JoBf;oxBC7>vQWD?X;FI9nXcLli+a)ZSW*le@|lt48qs8dr3&ajZu4^*C{CMIXV z8+@7Y8$ycp;4!4H4{5{~D}b6D(7}w74A6;BsVNHJF$_qj0CEr&sF+PG0hMQ&xvAhv zup}e35_g9_k5B~a4^(U^zy7mL708G~AUpi&4vqNlH~P+U?}nhY9H0apqNnRzMTp{HVn z@(j?uclin_`3iZd6(yivaeiJpxY+~_X;2mi+ouPwNzz~!F~Pc_8t{<`1yFY~IonFX zC$qRjBUMvJ92Tbvut>5}fEV1FI`9EIO$A+8cL=nMO#`MMNwKCvHSFdf1qB6gE`UaX zLN&bl1a-4ORVZ|j2}}83o(dht1bZ(5T(atc%KZdLQyZ3PlM_peQ;Tg8?gy8Opb`MA z1(aoBC-H*rj4Vn8_5DGK2kI!$`Fx<;WAY31kn%OyknmK6+{8-6)p{xUkel+7b5axY zbW01sV-*UZ?kS|mFUbI11^^z)fHiSJqi*mBfqEL$5&&Jzmst!tQVHyEkPN(_M+7m* zyBdgs5uQ~*%8^44RGK1c&T`NwBjo-&P^y3y&!B(+g$lS_1SiTOa4JOVh=V;14QWtC z0J`Ke0hYu-4T;opNV?Hc0EIdz5J4uw!x=gP1iC^V*7*au6x3KLEKMyM7I!HAwIG91T1tOw%u7cLto#BENFN9iSSZO2ngPM#qEL-A zJOT;H=vYu3;O+<5mwMW$u>?{CO+b(l3y7a|6cD`;z3sq~0;i2DG37`4CIsz}787b=9I~ z3V7d2L7^5$j|*l1FVxN8ev&@)JSCK!1Nud&#icnVsA9z>MVWc&u$ylonjwSV`cN;! ziUt(91p!GLzqyFMf)U=yC1 zdBp{($t5sv3c52eNI+XE_#%rBnhJ7Li_%j;R|=+r>O+_*Hys!loDJFq{LQ^3K43&kzY{a6sZJzX)Uus8b)GmS2=wmI~ivs^-YRU_DMhpxKjUbM2GG}0L zgAR&L*L=m%lr2vS%90l6Fv7B8R+-CjsRLRG_sfkDp& zVhFy#fq67Hu>hV@VCMbxU|`_%ggCs$kAa~B+D^e|9?WHa;0y{gX|5at!(!-s<<$TN zhTG5)S$rnJTn34@c<=-lEb7-gFfi=KpN3(*chDJ}A(^?U`K9o?!cPP;FkB9Vgwu~O z1_tJENI1cUp~2}FmTLVnb710Jv&iS>!@{n?iGg7aVdWv%ui$kekYX5Cb0ZF{hq>dwBm)Dd6eOwdb7EjP;Y8RSuxtW5Q5ohE zhd>4f4`_*x&mXXYFgHIhvjnjl{7Wzc!|z~-M{ooztWGLHzg-pPoworD4F3WkiQu_6 z1H%t!?t?`(IACBLaQg?AqG|#e7#g8b+!4sY&1zT!SVsR=g zBl&wVFoZyB*dS*HhHz-D(dfd!&_+Z_1?wI|ycG|s88g!|ljG6SMpg&|LnHL~cUTbu zj!S6E2j-lTqDmOE)SH2!0y^#V*qedj8I&F1!@v*>ZI|QlDJ)HZf-EyJ2QIq44uKo-Rp8gOvzOo8d(iPC3^Ig4iAw z!oUz20ttdoAq)&(p=`HM1_n?3;h2_Cz z-V6*Ip(S@@Fatvh{ssapuRzZQg(Y+;9R>zP9f+gkj2IYHjUWy`V8+03+zi5gWWm7j z(gMO>WX-^^5_G`?0|NuzPBP5ppbJRiA>BNf%eS~OFzknp=>KzLVBjR8j|Z#TzySg7 z9px9nA}hg`fguMv8r$!{z%U!yQr7ijV6cQX0TeR9i1HlXXA&}F zU{EuI1Q(7Z1PfWv;yrlZP2HM-LB|@R4`vUfmPQ?SfO)AnH752v{=nbYftL zhjt`hIx;Z)hmJttFcg+Spi>aA&Y4p#;SeyE?B0vH%Lp$>(46q0u#BYZHk zg7b4zVb+vLGB8v?&8<*mV5ot%Mwe+ZFsy<$h7VaYFdTy}9eU-#!0-+lFuT1O81_SZ zXTN+I82&*U$*}APu^T$r32QJyN?2Grcqq@n@EqEX!k408P6wqZSbTl*V_^6OEj1Sg zFfc5Ij$J&CU|@J20m+y6%!I`Nr20h+Kf&C#HiUs;FaDAkR`WXN=YgglQemamS{Vk0 ztul~EKP=0@a2h)L6r{+&5Csk43`GWp0w_D$gMlF#8mc&w7(4_@^5IRfTsa1YLOF;v zFyBA|9Wi_hi)d&rf+b6CDFy~1XdY5=Wnj>TrXd`*!hq#k~+YswoVadYFf`K8#0^(sc0m+1?BcbD^1t!-s)^%Ln4FQXd9}awz+&4+Fz@DEp5e z1H(UP5&k}ef#D;xz4Usy`&GV^g(k5unY`Y9S`&D6-Ne!ThOuD+fED&5235ZVL<>f29zAZL(Z@O z11|-Jh3J+b28Nx`CN#bb3(Eq?-HH=Y3=C(Y2xoSf>%B8`kkXlxBm;v7wDe1{VPMF@ z-;IOSNRY|l%)E41DA;No~upUuF)kgv+XP^1bGheZZBj$i=< z>d@zuz)FouH3o)y=nkkPGX{nXXiNLQ7Xt$;G}TH6GB7AYQ}c@;28MS*5aU1SGBAA8 zg|Ib)7#Q@S;#jSPtd)aVJ71oGVJUQsq|c0jVG7h*K5qsF5vaB5feZ|~P-|uM7#Ki1 zNI-%4Fpz=aIaJ&%h=IWdo3*eY11-@2X#xkh7Xt$eG{}fahM?NEnV1p-gA_EHP{Ifn)t-4JFs88?1A`efP)eg17%HM5;e1Aq zf#JL!giVYeKu6BQ%*?W8V90}3^Spr!41&-m`*wW>hTYJmdN>j^ta<>CZ-B>SVTSGu zWMDW9Ey?@U7#LS{od2pn4G6s0SGfN=;lr3=C4x zq7iGF@XO4B1b0YSSUbU0*?qO z28J+b_u{$@1H(ONK-4%gFf>CuEHfP$7#2X;`A!TBWzd<39AyTEVr7WS@I?vCYUqkS zShcP%$-rO+jgJk63=G?$W0Rg{3=9F#Oz_={f#Dz2!#g4v81_Rw84$(55Dv{8Q==Fd z=0Mq3br~4$Kno{WB!J67eNbJ6I^}{mEe@7#XG=0LEQPx0xH1F7MX2X+1Su?rKm&I$ z_DV$thPBXvyF)e%49B41Im?NGVIDNxs+<`Z>Y$_XIE;q%JV0p&I?fFXw$_zZBS!iXonUu?}Hc^zTwY+FcYDpO|TmNn>GW(UucCs!;pbt zJ~YYAF=Jp@3=NA!3kHS^=xEbE3kHUxP`0Ht1A_y!$f&bsU}%N1k9aXKoQC@KSP%ol zS*XkK>P(YD^K?2&5aMWU8@Px)^u_FURH8eL)aAaVZ0cBs6 zVPLomEjW{v85pvmiT95>0|N^*jo=+IgE_((y37Pt)?brgV7Ld3~=zuf#E9Dw=bm_7`{p2O(n1_23mv(i_lhS28K>(H)WM9 z1H(FK8C@>Nz)&p*F>aMS1H(FKtK*#l1H&f;h`5F#1A`7UfEQaZFf4~gw1o--gN+JA rjlVtvLol?~!R1F-jRTpY0Nptb^Wb(#28M%B53Yyarw*$EK=}s%v#`<% literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..c01841329892b8f65523a03b0db72db6695e027e GIT binary patch literal 2944 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

BLFg8Jru$YGSLSg2BXtKP9W**EP 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 U@(1%^x?&_47?Pl&4K;`X0G~SRrT_o{ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@factory_supervisor.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@factory_supervisor.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e60ef0ecb9cac040d74a11bc2968fda28231b310 GIT binary patch literal 2045 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 C=?mll literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@port.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@port.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..79e2f281c05fd224d5cb2b0e4a40258d3969ed66 GIT binary patch literal 101 zcmb=n<;-9}2fki^m+n0>?Hn(XggyfUgE0dGLp=in!wCik1`$RE1_?$6hGa$thIB>- WhQ*8w49giA7_KlfFg$`{n05fbI}G0d literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache new file mode 100644 index 0000000000000000000000000000000000000000..e2f08cd89e5676189255cd684fcf0954f05a114f GIT binary patch literal 33385 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 Vfs#DZz|n0!28Mf3eIO|i1^^rDw;=!k literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..4352f07cde9d70c23bd3f7e66396ad90d7c4b928 GIT binary patch literal 1381 zcmY${c|U^z2H2nsUMQWOlbV>TpHi8Zn46jWh>3xLm6?G-5JfVzC?_#5T|coTKbMV} zfkBIzfk6bDf`X#_!hFyM%SVGi|YxWrV6`U|{fOU|^Wdz`(G8 zfq~%~0|SF7BLjmJBLjmbBLjmTBLjmCBLhPOBLl+%Mh1p`j0_C77#SEIFfuSGF)=Xs zF)=VSGcho1V`5;q$i%>KgNcFRArk`wC`drQ2Kfr)evl(UZrH@kz_6K_fq|KYfgyl} zfgy*5fuVqffnh%j0|OT;1A`SS1A`qa14A?`149uj149Wb14Ah*1H(R628K(l3=9fv z3=D~E3=Fw!3=DZ}3=DhN7#P^u85o4w85l&^85mO785lORGcf#TXJD}9U|{g#U|{g! zU|{g&U|^WT!NBmEgMopSlYv2&lYt?QlYt?HlYwC)Cj-L=P6mb_oD2+hTnr5DTnr4C zxfmFpaxpM`<6>ZN;bvf%#Ld8Pl$(L!8aD&O4Q>X8o7@ZxN<0h<`8*5^#XJlQjXVqt ztvn12lXw^yHt;YoeCJ_c5aDHD(C1}fFyUojDCK2fSisA`aD|tFfsK!WL4l8fL5+`r zL647t!HAE6!H185A)JqaA)k+dp_z|?VG17u!&W{9hDUr14A1x&7+&x(FuderV9@7h zU@+!qV94NSU?}EiVCd#&V3^C#z_5m&f#DcG1H%n|28KKQ3=H@985r*KGcc$NFfil_ zFfeQpU|?VrWMJ?WWMG&g$iQ$*kb&W)AOpi&K?Vk8AqEC@AqECVAqIv}AqIv*AqIwa zAqIwNLJSNSg%}tv3o$TU5n^DF6J}tD5oTaW5N2RlDa^odMVNu%nlJ;ybzueuIS~d1 z2N4Da7ZC=ADIyFES40>XZip~2D2OsJ=!r5g7>P14)QU1NoDpSUcqGce@Jy6};fp8( z1BVy`gR>X|L!cM~L%tXT!$dI#hOJ@@441_i82*VdFtCazzz`t8z%WOGf#Hn=1B00)1A~tw14Do$1H)`d28LIX z3=9lX3=Aw%3=BR}3=DNr3=9oY3=EA@3=GGm7#O&v85nq^85pdj85jbj85k0z85pKW zGcc@^W?)z^&A@O;nt?%9hJnFOhJhhZhJm3~hJm3=hJm3+hJm41hJoR(3UMU+T|G-=EyTJtd(bA*e%b%a6_Jf;g&oD!)pGd7}69N z7^)Q*7-|$47#1opFsxQ!VA!I-z`&x&z`&u%z@VuTJz>urRz%W~pf#I?u1H*4c28KV13=Dco3=9@Z3=E}83=C~b3=ADg3=Ex03=F%K s7#NN!F)%z*Vqj2HW?=AEW?+a>W?-mSW?)#L%)qcjnSo&$6vIj%0N}CF%>V!Z literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache new file mode 100644 index 0000000000000000000000000000000000000000..e6b4be61807592b4a46cb59548c76f8d661850da GIT binary patch literal 15062 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..1631ea1c6cfe2dbd997c0aa4bf6616b7b5f2d413 GIT binary patch literal 656 zcmY${c|U^z1{k3Xekh%ulbV>TpI=g-pO{>dUjz~rV_;wqzq&ENyyL>O2_OL|7H42! zkYHe7NMK-KC}v<_Si!)+u!Vtvftitk!IP1JA&HTJA&-%PVJ{;C11A#$gFO=igEtcc zgD(>UgC7$ELoyQs!wMz_hD}Ti48NEd7;Kpt7&@657_KriFvznoFvPGhFch&cFqE+{ zFf_9;FtoBTFto8SFkE3_VEDnpz`)1Kz~IWtz|hXhz;KO~f#C@&1H&6u1_mWI1_lc@ z28ISU28NYv3=A9D7#M!DF)%Q*GcW|RGcXjgGca_pGcfF8XJEL>&cJY!oq^#tI|IWV zb_Rx@>~^o0EZ|pOb-MA}0gGBu)l~!<-BZ$2l1oM7bCkB)J$EbhsE83b+^;CU7w@ zEaPHe*u=%aaEgn8;Sm=D!#^$t1}1I>1{Q7x23Bqc22XAVhCFTth9YhTh8^4t412g4 z7&v$s7?gMz7}R(e7$SKX7;8A;7>;D8RtbC&0k4OMrplvH$~viXa1ny&waFs~`h|mmmX! K4-`X_5d#2hFIbxZ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache b/examples/http_interactions/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache new file mode 100644 index 0000000000000000000000000000000000000000..0c0b4065b9f25f75731b325bc3f708491eb81638 GIT binary patch literal 12394 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 Y*7N{#CM+Go6b<(TjugPK$VE!?09(66Gynhq literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..85380d03b28af5b06dab1ad4cb52ac3fab8ee236 GIT binary patch literal 1245 zcmd;k)sxA94syDZpQTvLpQO!*kO^jBU?^o^V5nkXU^vdez#z)Vz>vkrz%ZYYfnfn7 z1H)rR28QR13=DEi3=DBh3=Bz33=Bm~3=CyV3=GXo3=H#_7#Nl@F)*xRVqnC$3F*7jyWoBToWno}&WMN zn8(S$aF~;U;WQ@$!+A~yhC7@L42)b144hmH47^+n3{qSS4E9_M4DMVE4BlJ}4Aoo= z3@uy?3~gKt4DDPD44b(a7`AgUFdXM%U^vaiz;J_$f#E+F0|PrZ0|PfV1A_!N1A{#` z1A{v^1A{j=149Hi14BJG14BDE14B1A1H%k%28O-d3=BuP85mA-Gcf$(W?*3EVPIh8 zVPIh6VPLT3VPJ6NVPFX4VPFX5VPHt&VPI(IVPNRzVPKfd!@#hBhk@ZZ4+Fz_9tMWX zJPZtfco-PCco`Vvc^Mdtco`VXco`Thco`Thc^Memco`UG@-i^Y-4VCWHKV3;Dv zz%WCQfnlj21H)cH28J(!3=CXC3=Aql3=BF#3=9TB3=DQc3=9!M3=D-r3=C6+7#QXW zF)(ZvVqiEe#K7=Oh=Jji5Cg*-AqIxGLJSO+!VC;C!VC-v!VC=Sgc%qP2s1Do5oTa` zE6l*~QJ8^&QG|hkRfK^-NQ8kwON4Rh6n@0IuQnj6Cw-@7ep8su81%&JQQJI z_$I=@@Job&fk%{q!Ag{Ypx!(f31HUyf^1i`wXX-~hnq$o2l9h@+^pc27g zAy~?T3c>@)H!-gg-5Qu06gR`%<&#+qcNZu`ku_s;A9=}C80rRC1e6yg7R2YES&boy zGe5x`*(AWguo~ndMn*D}akU1yGTv0J2pfQ2}DF9v7EFP-$MVLS~*qa(-TMNn&1!LP=(BszP~2 zYMw$-Xd1i5{9v2q_D0*lSJFr3z zk|&`4L{QX;8(2<3&RU?d0!>_um4QK<6?YxL42nrmx`P=`pX>>C1XV(d zk%<8mHXsA@Qp-UU2!qlvvM{c+3sMWhQ&<@o*0M4%Fvfs*h_tJan5U4MTToJ|kO0p^ z33>{l#i?nfISPrz3MCn-3dJRfMI{RPX$px71(^k@3dtFXncyUwo0wP01y1qB3MKjJ zsh||9$HfIooD2*o)ezKHFh!MMVSwe}#DapE0hBDTNi%{14TM4JV01}7 zhzCpd#ieO!nH8Y)geyQ{`gU)EEH$T6A*m9Sc(@eGGK)$| z6LS=D6O%JC^HLQO^HM;1GBVRMaw-+_3raF`GmBGG;57p*J*R>z6j%!g)I5lX1Qw)b zf~Osr=b>Jva@v8Jic%iqYzM#!>JX&%9jK^76K7^+U=YSzJ1}A|f?%lv+?JrMjR^J_ zHuG>LGMw!dMn-r88(a>JQ-e4yw6T-4GBpe{Q7#xcDJ2T)wp-MzD@PW;OHmtE0yEs%3io%!t3=Gl&3=E8J zP#Xv~udx=tC@pMQfruyxUy!Ji)*YwY{Jy71V}-^1#g(G$|sQE(JxYL^fTd z*ccd+*%%lY{h&4~1V9`BuH;}?x!VNlnA{EI4s5_vEwL`xH-0i+kw62+BEV5;7-F)*02GcYiEgY6N2kK?QC%LOVUU5-Y5Q3hr;945~syflAQ}YbU~@7gFrO>;OeDb`?~OVqGobs933=EQ@$n|VcY6*B458S-TtV#vdwIDXg!eVeKu3ik9oheDpD*;W^fW?s;%c#S8 zkO>%AlLRu%1{;yi%m-EG5MO|t4{{U;!@>|m(TT&->VZMSTe|UWilK^>>I#z*90%2I10kN@Rm^`uzkkz3i zKv;BPOyZ;K#^wiD8yg&TM9$iyn+tOpimR|C15gG)){MlbL~G zH!}mn0cHjUJ{ATBD;5R@dlm+US{4R|Iu-_osVoc(%UKv0F0n8$fELWWXJKGqWMyCw zVP#;@VP#;@V`X5_XJufhVP#;Lz{l9Pd1888nou7eW3O@tGG=2t#>HG`~$M_i-PVqA^ zJmqI#0F8Re2{16I3NSD@3NSDP2rw`N2{14O3otN%7GtavU|`rIz`(FifPrDZ00YAd z0S1OQ0t^hSf(#6tf(#6Sf(#6Gf(#5zf(#6xsjFv#3=AIy85o#^7#Ku^7#OsK7#MVf z7#KiJpn4$&hCU$%hB-nE40DAT7;XtMFx(bmVBi&IU~m;?U~m&=V5k&kVAw0n!0=U= zf#J6>1H&I-1_nJ528Lu228MJI28K)#28JvV28N{~3=Atp7#I$TFfbewVPF8&xH6&) z4CKmc47H*R4E0b9%LZvshJHqVZmND-Mt)jaZem`ZetJ=Uu6}AoB4`GzSU;nr zq#!;suOzi75j;6ote;exnUkVlkeHmEn4Vg!4=xg*%}V{^qGX5w(xiYMSOwHF#EL5e zJwrVMn5YPp0iV7_n*Rn(ttBdyR2HNvloqF^D5T{VDWs;QWhQ5)=9PeEFeuq&Q!pB;N(@XWC+6hjCnGIi$;(ek1+{!Yts=11puwU1(h`N_{DMl5Zxiz> zVXda*{5()YNlyVZy9qZD>fTg4Px&lw>4=*3dxQ0}3|CJZv#^h@>C^aK7FGUY(4g)-vJ<}A*Q{lmwmx`Fe1&1W0*A8|p(m+gZX>o}{QYt7QGE-B) z3SFTlgBSjQ!x215ol%{l(clAZbZ0W0b(R5B_%-Sw;_uG65uoJm?;6|FL0JAR!A&LRY=S!Ppm8k zck2}rb28KOQd4x`v6!fkSDKrYS_De|Ntq>}RWXU+Je!waq7HLSN@iJRab^;Ds7}E` zp*%Au2b5?E5>rxAQxrg%Nx`5P?hZ?sHhWbtRB$j}U18apW4O1^x zfb=ek_28?5U^9Qir zF3qqeV_s@GOxtW$28Ma8__e`|L!SwTnc>OLz!1rg-warv22yImbbeuDVEDy`UneZ6 zAiW`^VPKdc%)AT?BE0wwfsI~)y#S8|So`w>KLf)*s0T{e7#NyBGja?J48H6P4AGzo z9tH*me_jTLSkQ1W0|Nt&NQLFNl!X`=G{FNNhXBgDXP7;3AqFav{>FvJCig&7!5LD?oE z3=Gzw?7+alU?9Z6U@ipl$u~g;hQFZYL<|fJI-(2=#-LFN1_lPqTnn?UC>2~e!raXx z#K0g7jY>>Su-uiKSOC-D&C9^x2c5ve6-Ka#hNK#pA&EQ;4Cy=&cW+{4VAug#Fv!5b zkiyHrkOfVKxJ-nlCU9bhjGMxY)#qbiu;POl+r!1cFddpw_Hi>XoP?$n9H|@D5i3Yc z0WVwvC0>|6O#~Pi9H7pDQjq-xNG(BF+5)X5f+af8!Xb8O>Ep@Cz~Bo_TQPhL3<;p+ zy$lQto%{?8y`beJ3=9lD!VC-n&{)Q058R!QiF9ygg1PfJI|IXIc8K?3R~BpG*9GVV3-OT ztzuwckmqDzP=-di9w!5X5tJRz$-s~dWiyE|Ft9;0GcGT~q8z!s0rT-LK?a6nf)F2L z=2@6))ADmtU^<*c85rC|A@wkhFoH!8MlGBo%D|8d)r-ruFwO8)N1!1OkaevB3=Dk& z5U08cFfjN-OI=tn!g38b#li%G!8;~k`8%D9fgu-~rzdeUFwB7FX=gqL22W^)BIGV` zO#qr}k_I`7fq~&MbOR8`k|nUk%M1(*CTt80w$SP*3bv31w5^7nfq@TNHR-TJ*GMoh zFx+QnV0Zx){{f<*RTdiubZU=*f#Es_1H%KT_(u)~hF?(jf7lvd(DoWG28M8`CyQWn zMhpxL72FIAjZhEIh0W`!;N?ECT~Wf)E2k z2Gl{tLJSPmQ1*8g28REjfhkZVK_eHOm|)oeH0=zl`|^1h7>c2xCe6#hAP;qbix2~Y z2h>1&AqEC#C_7q+fgv8s&J$u_D1x%3Ss57Qv4sLCIe{GEnFrsaBPI%+D_~$?z*#=S paxo<3gAyvtq+=`$3>R1+i_UZS85qi-*_VTrfk6oB1dxp&3;?G34O;*J literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_tree.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_tree.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6b182d02688080bc0651290b9b797215fa93f71c GIT binary patch literal 903 zcmdlYyaL+igqwjOl$(JelAD2HB{u`ZWo`zBtK19>*SHxNIC&TtczGBYG`gs@_=JGHw%;#ZXSir-;aFd6D;VusY!w((?hCe(E3|71h434}E4Dq}S z47t1v4Eeka3lnP>_M4PLP42NsxhIvmgV*GeHK1kAe&gOhOC{B0>xdT0#sA zIzkK#xifFdPzLU^phi!0=Io cfk8%;fk9oAfx$wQfuUBEfuSCXLFpfa0ah`F-~a#s literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache new file mode 100644 index 0000000000000000000000000000000000000000..7ddf54bb007b91d42baa6cd518101b0d23de21a3 GIT binary patch literal 31192 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 F3;;p}t@;1} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6550d99bef65421a4c6cc6444c979a991aab8187 GIT binary patch literal 2409 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..3ebfeec536dbd72adbbd7fde51cf82a4c0629c64 GIT binary patch literal 4725 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache new file mode 100644 index 0000000000000000000000000000000000000000..9dfbac881585d68b2d7a60a0368ff160355cc691 GIT binary patch literal 25635 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

l7%s6gFkEA0V0gyL!0?Kdf#EwV1A`PB1A`J9 z1A`hH1A{Re14ASm14AMk14Aks1H&{n1_sayG*)&71~zsE23K|l22XYdhDde>hFEq6 zhAeglhF*3ChNtm9x{_{qV*z{1JE zz`@DDz{APFAj8SPpuow%pv%d?5XZ^DkjBZtkj2TsP|nG~u!56;VFM=v185Bi6Bh%6 zG8Y3wC>H}m2NwfF4;KSN9~T2dKNkbT0WJmx7H$Rx4sHep32p`k8Eys!O>PDTA8rPQ z5N-yB2yO<3G;RilOWX_$f;9tMU89tMV79tMUg9tMU!9tMWxJPZs6c^DW@ z@-Q%*A6CVQu8y^FMEgu6zG9Lp&Iv)c= zIUfT z`573_@-r~J=VxGG5ny1D7hqse7GPjd6<}ac6JTIS5@2Ac5@2Ac6JTK2F2KN`BFMm? zA;`cGFUY{qD#*alDagPuQ;>mSt{?-$DnSN@r-BR&Zv`0`J_<50{1aqguo7Zma1vr* z0G%8WE5yK%Da61~CB(omQ;30KsSpFhc_9XdS3(R7Z-f{a-U=}=XbCeg=m|40*aIj3>=~i3_PL?3^Jk&3<{zQ47#EW3_hX^3?ZTn3=yIX3{|2G44|^3=DGO3=B%* z3=Br%3=C%C3=GcV3=DDN3=C=F3=CP~3=HMs3=EUR85m}XGce2(XJ7!$S3DMHVE87^ zz`!TLz+f!Fz+f)Hz+frCz+ff8zyNA7_DV1?^hq!-b1}#Yj20cjzhG0ns22g8jtt11(I!OkG7m^GN%2EsrQBn*H{Zb4Jlcg9K7E3WO zESF+n*doQi@Jxz<;hhu%!zU>QhW}Cw3}(^{40h5C44`&EwKN06WN8M5h0+WRccd8@ zcw`tDgk%^P6lE9~qGcEux@8y`Cdx1{OqF3^m@dP>FhhocVXq7W1E}{dCdqfX2qf!vDR~A42YCht4|xU#A9)6bNO=Z^K6wU)Y4QvVv*Z~Vmdi6RT#;vBxFgTN z@Ian{;jKIagSY|%gS-L*18A@j5d{VY4n+nAA4LX+X^IRC4-^>~J}NRW zm?<$Z_$o0llq)eXR4Xws)G9GB)G0AA%vWMy0G(I!PlVwdV_@J> zXJFt{XJ7#BM+s18U`SMFV3@1Uz_4DOfnl>c1H)Ez28M0w3=IE3YBU%agf$o#qBIy7 zW@s=l%+X+ASfjzfut9@?VXp=Q!zm30hG!ZK4DU1;7(Qt*FouQNz>uNMz)-5qz_3J{fnkj{0|RKk;V*3l z25ub&21OkPh6Ei3h7=tJhBO@phIAbUhM77H40ClD7}n}AFl^LeVA!X_!0=Ruf#IzV z1H(rh28Mq+3=A5&3=9Uk3=E*t9}0CD7;1DG7$)g5Ff7w$U|6Ngz_3P_fnlvK1H)-u z28Q#x3=C{~3=D313=Cd+3=C0v3=DC43=G+N3=G|R3=EU?7#ODOF)%FAV_?{&$H1^p zkAdN<9s|Q)Jq89weFg?TeFg>teFg>#eFg^5I-E3p28Min28Kd?28JSi28L<+3=FgM z85l0=GcX7mFffQ3FfgbZFfeEuFff=IFfc?LFfb$=FfgPWFff!DFfeo(FfjBPFfg1p zU|_gxz`$_bfPvwO0RscKAp?W3Ap-+wC%c;=1B1UI14E!814ED@14D%&14E4=1H)!R z28Pdu3=H2585r1&7#O&X7#Jjs7#M7g7#LiQ7#KW_7#PBg7#MPl7#IqS7#P+XF)(a3 zVqn;5#K3UOh=JjQ5d*^yBL;>)Mhpy`#taN5#taNL#taOg9Vq$63=9><3=CDq3=GxA z3=9{I85np?7#IXi7#I{y7#LJd7#NI97#PA$7#QMB7#NaG7#Ipn7#J3tFfgn(VPIHq z!oaY{gn{9S2?N6$69$G4CJYRJO&A!|Oc@yTOc}r%rc+HB7@ABO7*3ipFz}l(Foc*f zFjSf`F!Y!)Ff2A>VAyBIz;MWnf#HZ51H(}>28L&53=E*DO>=Vw1`BfrhE{V1hAHL@ z3^U9b7*?1wFsw0WVAyHS!0^DFf#HQY1H&7028N&J3=Db}3=C!#3=CEl3=HlT3=H)a z3=HiS3=G{C3=C^57#Kjyq1h}M7(kc-3KF5Tenx(7s(xBVep*^?VqTtpdQpC^ek$|? zA$`!vF7cUpC8?kdKbiS?#rjF5nK>!?1&PVoiRr1uD2HO{7Z)W%1Rx!BJ#Z@&Y7gQ( zG6OwBJp-7i9F(C9r4`(uXEqgs2b9Y5i?TuebkN)ybnTZOR3!ssn1g`%77gI1nKa>_a1^eeZ>l9CJITJB@hch))jzHe}Z%<;87zeGQ%9D^g2p%9t}I_Uvq7^aVP z6jCdaQ&Uqwz6Gg(t}|B9$S(pd8wRa8)`Z6cJn$2fa$vpwwEQ9k=#FcU8ITBojNO1e z37w6~%u|57QzO3!)cF88PeB(Zs;Q?CoRMFelL8*5EGeo4jZ%U(Jg20B*AHewdbjI2NQx) zC^$ibA_Psa z9d!aWFFC&`6||lnI)9AV`46?*3T7y5K^5qzEggkKqzqY-n4OxZ0N%S8ke^qnqu`uh zr~~%CPikTwJmTOU22WCeX19w|Q*#wc^HM87xgS(0l_;cT78RH1C?tW7!~ma)k(aLk zJ{SYE%q2B9u_QA&F(;=IG;Efc0yh!7hX5QdpnQ~Hggn8b04iHD6LUaXN>lYP(hR6z zD$PqtP0P$nO%Z_kHZxBFVPml#{D3Uf!58oxwkA{$7Ae@!fjekW0IX~u4+Fyi=nx{R z25>?`8~K3g-zmz#a6puS0W{duq`<(?37WuWKpdq6u5%#u56n=cc|q9t#!ndr1{PU} zDTk#Q7|wx4B^ekP9!WDWyoIuzWf&OzWFW(4i82ffIZ!stSR|WZ#)9{DfyQS*9=yrM zz;KTZVh0{Q$)JrZFg?3D85j<5LiE541=|A~F9MyE2{UYo2m`|k(9(1U28Nx&3=Dfg zdjl947}U8K7__+{4v*nsU`PP1IALI5n8w4vFbm4olVe~o0(EZ~7#KeDGcbGyP2Vvv zF#Hl{VE6}G-p0VdFkPB~VKy`{tdtlS?35s8PB3F&m|_ND!`uRPBqZ2j?gkH`f}#l& z7^lP;7|x4BJoZkSf#EaM+*}z3h9ay13rPghEaflff<@ku{Z=) zmcxz;g@xe_B?g8EN)Wefk!E1n19gju33_9M(gewjHi^E{Lo z7`&lsWpo%AVX+% z3=DH|djJ;Ju>JI~V0x#@!0-*Um6m~lp+}X0VTvlmQTeJ24CPR9JidY~5`@LQtT_XN zvN^CJdNjaInD?`)9+_ zQm2)@5Lr3r$ZO%orFp;f_C8KtdLC!>m}O!oaXX1>znJ2?ho| z2}n-E;vZN@7MJG2Lcl|nfx#P^{_yBXOi6+1aMovFaMOp_hpGdmbn=1MX-jw*7?wd} z0aXKPod!OFHWlVA86E}(Wgdt%f&vT-5&}5Ag=;+rsElP`VDRK;V2I#{n1scCL@rGN zAK?J=r@Ju&gTFCOZ5U_2!1Rf7Ffhn*;M51pN~jAzVKMVrmVtp$4x)J*KLf)NXce?W zh=Ji4=xku@wK~iXu&HvGam5l04Al}4|?!N4F6^{$u<1A{cQLJL%4 zUnAY3~O-bJ!tx6fNlW*pN|3yFfbH|Ky4Sy5oFM~)njyqE zEZShLNvNM-x{j$aFq~DxsS6fs;MIPx+^VI(z@V=H(Zi*}z`ze06~*ptm}9}S{fQ;; z3SqJ&1H)`dh!HR~FuTBhg$0sdW)3W&F|ac*FtbBKK1qRrAr;#2WmI8cV1YW}vo-_6 zH_+i`3=9lex(p1t(84y(lz}196rvZ!9$1MDI;Nptp0hD9yagTkz`($u!@O!a1H%zf28NTMX=?@shI66}440wo z^I{APS3vvM7#J9CiZL+UgR(D+GceqMw%6+=7#LchS%1DH1H%%~q8J7ShDd1!hB#;& zw-~krgMooTK$d|)6jTUI@7<&_rve&cNUdWqUx6y#ndY)MQ}D10B81z`$@qn}OjhG&kMWW?*;#Wxvs8VE71S zyXrD9ctLYnkS+s5IFy~N%fOHUWqau}F!+PEvobI+gy=IcL_*ndux+jk3=CR^3=I09 z16>&y7)%Tq7%ZV|JJ><53=9mx#taM*po5bb7#L!W85okF>~z?HN(>APZl(+j-q4&L zZpy$A17)k5Gcf3wL&D$CoPog%%Km1-!0;P%QU<62VadS2Y6($;HB&>D{=%~OCpHF# zAJD8BqQJlq1x*g0l^Ga*LZkPoHUq! z4|Z7q0|P^~0s})m)FKp(uoBZVuLLH37q(T4fq|h@f`Oq2nm1NTGBB)x=8bG=28KLn y-r$gBVBm)42h=JA+}eW~3tpfPvo4F3fgzt2a=^O-YgFgcU!+Zt?hPMn140enR455q+3{i{>3?+;V43&%w4Eq@w82FhO7~+^1 z7_yld7z&se81^tRFnniXVEDttz#z)Zz#zrUz>vYrz_5dvfq|WcfgzHGfnf#<1H)Vv z28K5*3=Gz+3=9FR3=AMQ6|yog6tOZe9AIT&xWvl9aE+CL;TbCf!z)$>hVQHl3{q?i z3`%Sa3~Fo)4908>43TUM42f(E45@4k4Aa;c7%s6fFtD;SFtD*RFu1ZaFnF>vFhsI5 zFvPMmFl4baF!ZuBFid4Mfx(!Afx(1>fgz5Afgy>5fngm71H(@a z1_l;R1_lmJ1_mBZ1_l{U1_lLA1_oVD28K9J28J|F28Jw728MD@28I=!3=A7M85p*3 zGB7Z4F)%1|F))O3F)(y+F);LSF);LTF);LVF)$q9Vqjq5W?w&@G>yO@G>w=FjtU)VU-{Q!&5;9hPQ$Y3?Bs<82$+| zFjxsOFgOV@Ft`aZFvJQmFk}ibFjNUKFw7KUU|1@|z;IrOf#H=91H&6328Opn3=CSr z3=De03=DR{3=B@f3=ICl3=H|g3=HMM3=Gx63=B(!85p(-GcX($W?;A^%)oF>n1SJj zFayI)VFm_K5e5cH5e5cL5e5cb5e5b;5e9}R5e9}N5e9}d5e9|{A`A=*L>L&>i7+ti z6=7gFD8j&SScHM$hzJ7%hbRLBk0=9!j3@(xf+z!nt|$Y8k0=8}h$sU?geU_;l_&$l zCQ$~4E20bxS49~ZB*YjPWW*R4bi^1K48#~1Y{eKDqQn>&lEfGo(!>}TI>i_m_KPtv z+!teDcp%2WASceipd`+~U?k4KU?$GM;4IF-5GT&SkS5N+kR{H*P%h5EFiD(&VU{=p z!#r^YhQs0v43EVb7`}-!Fz`t*Fc?cPFqlg)Fjz`3Fjz@2Fce5IF!V|=F!V_CE7~X*N$T2Wz$uTfk$T2YZ$uTel$uTg5$T2X4$}up^lw)AnD#yUE zQ;vb*q#OgoSvdxVTXGBxjPeW&obn6|yz&eTQt}K84)P2P9`XzfKJp9kVY4~|!&Y?$hHdH$4F5oCG#D6!H5eG8G#D6WXfQC$(O_U$qrt$iL4$!|uLc9d zDGdgOXBrF)?=%<~K4~y8h-)%1glRG`V5G&sV5Y^u zP^ZPf(4@t{FiDGnVVV{L!(uH4hVxns4A-?77;bAZFuc%WU=Y=2V35^jU{KU%V9?WM zV2IIXU`WwsV93yBU?|mQU|6Efz_3P}fnkF-1H&(E1_o{&1_nhP28IM328I+J28J{p z28MJU28NkB3=DI17#P;-FfeS?VPM#&!@%%Vhk@a(4gWno7z7O%7(@*i7*q`y z7&HwS7|aY97@`dr7?KSb7}5D1H)%S28Qp33=Hf>3=G^x z3=9%R3=FnL3=FPD3=Ez|3=CmL3=BC&3=9QE3=C_H7#Ox1F)-{jVqiFC#K7>uh=JjU z5d*^?BL)UeV+IBjV+IBrV+IBXV+MwNV+Mu_V+MvQV+Mw5V+Mwc#taO+CJYRMCJYRU zCJYR!CJYQlCJYSWCJYSmCJYS8CJYP(CJYRVO&Azfn=mk}H(_AdW5U4j#Dsz2jR^z8 z2NMQ{za|U}YNiYfdZr8vMy3o5siq7JO{NSCCrud`_{|s?Ld+N#D$N)eddwIY7Mn3J z>@#CvIAq4aaKwy(;iwq{!!t7m1}Sp}26J-;1`BfrhE{V1hAHL@3^U9b7*?1wFsw0W zVAyHS!0^DFf#HQY1H&7028N&J3=Db}3=C!#3=CEl3=HlT3=H)a3=HiS3=G{C3=C^5 R7#JQ{Ffg!LLNEgq001zMe5?Qf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache new file mode 100644 index 0000000000000000000000000000000000000000..cebba82b10cd6d02da6807b9583995e5150ea9a4 GIT binary patch literal 861 zcmd;MfPnOz)WlrrflFa-(kQfh$U|?VX%fuI#q~v5KL4_DtpbR!B9h6#Jnp46A z3HjoETb?=A56BT9BWaR|1t{ zfN2BqnLz{t1A|{?4wMC!hAF`!4fml_em;sGkPAR&z(OFTs5BMC2V7~fKN>Ymw!RfJBKdCe`Cq=&?F*!Ri zJ+)XLIZ^5t7bQak;OSQnEDyB`k)90n4D}4)qUa2e*Fj>~gC0GGV5+?t7#M;X7#LuQ J2xI~b0{}`wbXNcX literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..078b07ab2da9713cbb890df83ce6ad86894980eb GIT binary patch literal 73 ucmd&QD1#$cH)|Bn{IBVl#sX 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..582a9147e0a0441330af3d3b18e1b30385050d02 GIT binary patch literal 3419 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 b3=A)!=IO97Fz7?gYh`6%=zy9B(hkA^9~Kqi literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e14fb7a3e91392f5c1eaf21a6baeed353c0c2e0d GIT binary patch literal 273 zcmd;k)sxA94puC^_WwcEt`inW5{3*645kbW40Q|)3{4CS3=FIX5D-mow*$g?sq#IiClbV4!As{qsiC6fRE literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache new file mode 100644 index 0000000000000000000000000000000000000000..8f3531a6d475ee19030edb0420b80c3cc8af6c5c GIT binary patch literal 96350 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 XY8?Z^J7{YpWjzB!2DH@y&FKsPM;2D^ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..fe8d921b8f3db76f908f73544a1c9dd1360defd3 GIT binary patch literal 9274 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 Dpnh8DhFZ0>q683Pq^}MXAN9c_qaPi3(+j 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 Dcl*J; literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..1aee6edb8814de20895f173adc54b230d0ce5253 GIT binary patch literal 1357 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..70bd1d3200f2301f2c20ed767c21411aab018cea GIT binary patch literal 629 zcmd;k)sxA94*n)}U-CJBwqYWYgckz?LmC4ELly%ALp1{fLpK8h!vqEf29TP$3=9m5 z7#J89GcYi0V_;y|&A`C0mw|y{9|HpeJ0k;w93umR5+ehH5hDYG86yLOGa~~-JR<`` zIwJ!^HX{Q=1tSB)6h;PyIgAVp3m6#~)-p0MoMdERxX8%BaFvmP;SVDN12+=`g9H-; zgDMjPgAo%0gBcS8g9Q@8 z28McO28Kpv28JeP28Pqj3=HR)85r&}GcY`6W?=Zh%)lVd!oVQU!oZ-+!oU#5!oZNi z!oX0=!oV<%g@Iuy3j@PS76yjZEDQ{5SQr=>Ss554SQ!{(SQ!{}SQ!`$SQ!{>Ss56D zSQ!{HSQ!{{SQ!{LvobL3W@TX5&&t5S$i~1R$Hu^*z{bF!$i~2s%*Mcw&c?t{&c?t{ z&Bnmc!N$NamyLm8AsYk3T{Z@Wr)&%iFWDFvzOgYdaI-To@Ut^8c(XGw1hX?RgtId+ zOlN0cSjNu4u$!HM;WRq~!+CZFhKuYB442p$7?d~|81y+97>qd>7#cYk7&db-Fl^^w zU^vdfz;K#_f#C)R1H)Sm28NFu3=Gzs3=Gbk3=Hm^3=AQh3=El^3=FxP3=G>j85s6+ kGB6zGWMB~GVqj3=Vqmc3Vqgg1Vqgg2Vqge^Vpwtl01)L+Hvj+t literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache new file mode 100644 index 0000000000000000000000000000000000000000..2db37badb931aacf395389be6032d0282ef73e43 GIT binary patch literal 3298 zcmd;LfPnOz)Wlr64oWR9%_(7m@))5s zG9Si<(f--Usz9PJJ_7>-E7SnjqN4mFm^erc3`6B%P7EzcbIwmmEy&NzD}hQez_fw* z%pih+fx#~`2g(9V!<1l=hWpSdKOaR8$ORxXU?C7vRGJFngE7qAZizX?2vJxd!K}$k zEeES+U|;~5hAfO36Cf2J)fKD^42xJ97#R0}cnk~-3PGtQrA2wg3W*ANspSfwSW_s^ zEXhzP$w*a5&n!#LQ%KE8%}vcKDb`a6$;d2LNKVXCNX#kDS4c`#NXgGjRVXda%u5HW zPf1P5OinCGP2o}~uFNY*tWe0zD=tY*OtDf($V)BPFw{{n(p0duRZxa76ZE*a6qJ<} zTq_cD3vyD6xwsS(5)!~Eg-ao^xHz?_1mr4+VkCWFWn5g0jNqUL5g-inBZvlJJx+)p zL2MA#hw?!o1>%EbK{N>CQVR?8;?(5)ycCcQ5C-W$lfevfkdr`)jTspjLKqns7?VLf z$fq^j}su^#>gC#iykZlF2Da3?Ed13)dSSV{4=_nX#k{%*z zXd$2uekNKp3VMmW9(Yi;Cf82}l8|1n%&vWME*J$-uzCn1~);pi~1kmaIer z)`=2MhNOp(5;R0W9s*%yG(Joo=Dpm+f_O-3g?a{@axi5XL56`aNEwXIPXqB_$qZCB zfs!*!Gq|t-S2i&9#w-jBh@=!n#nk#Fz_H3)R8^@((xxfh+)FkbxMI zD6xnhZy+5Yosd|?9g8f?3=Hnf3=E7*L8=MGBG^$hj6|^cIO5OonAyC+Y;sNGa z5Ff?{(IBh=4L1-Qgf*djkUkIwc^f1S!su$XplY$13zGxs17VoCAoD;t07O8sCIbV5 zE&~IDEdv9CBdB4+z`&5pz`#(-z`y`1faWkTFl=OCVA#sQz_6Wxfnf&&1H&T*28L%0 z3=H2G7#MyrFfgz)GBBt!GBD^fGBALux^PAYh8#u)h5|+g22i7`n2~{DDI){JN=62T zos0|&dl?xRPBAhtyklfw_{PY<0IC?2m>3w$nHU%xnHU(HnHU&cm>3xPnHU(RGchpC zW@2Di&BVa4o{52B4-*5!50Iyr85lqb#EY4MA)c9mA&;4Xp@5lzp^%w@VJ|ZS!%=1i zhLg+;3^$n>816DNFuY=BU{GgaV9;k_U;q^lRV)k){VWU&^H>-d7O*fdEM#F|_|3w= zpw7y`pv}s_V9mZd{*H~JYRB?a-Bc_pbuiOD6I`FX|qNu`-NDf$J8$=QkN zsm1!>vJBcp)-Ntfh6sQQH$AW%)EY#~)%ob4T#mWTH0rD#{1^_J@+nxXb literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..8a631a8d2da920ecd57badcdeb017e678d2f13be GIT binary patch literal 369 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ffdcf2a5ffefbe05b5459d5da1dc1c824bcbac71 GIT binary patch literal 4017 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache new file mode 100644 index 0000000000000000000000000000000000000000..016a824704e05b0174a13e07d22dad6ad3f42676 GIT binary patch literal 14968 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache new file mode 100644 index 0000000000000000000000000000000000000000..657d3749fe56ef37c8a04e4471d010dc25b84e1b GIT binary patch literal 37684 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 H;ueGf3$gqd literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..2ad6809253ad37b26f9e6517dd40499b96b90af0 GIT binary patch literal 3269 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@calendar.cache b/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@calendar.cache new file mode 100644 index 0000000000000000000000000000000000000000..841ca486d3b0468f688bf3d66d2a3e8d3dd6179c GIT binary patch literal 23065 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@duration.cache b/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@duration.cache new file mode 100644 index 0000000000000000000000000000000000000000..8670efad65b92b8df0a20bd85d2635594ee77795 GIT binary patch literal 17911 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@duration.cache_meta b/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@duration.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..8ac0783d2396d543988f561bc1a8bbae0b11b50e GIT binary patch literal 1391 zcmdlccQlg$23VjBE-0OzlbV>TpOl}U0}>EqU|`?`F&G#aATpVGB|;1g3`z_P4BRLZ z`9&$IMam2e45kbW3_K`O#U(|VdFf^h3=EzO3=9jc9-aO8=FV1AMyMhN1_qFkAiW?p zUJMKj-V6*3%?u0-?FB10OR3Lj*Ge!xCl&hL6k)4Bwa;7|dB17?M~R7*bdm7*bgn7;0G<7#dj^ z7|yaVF#KR)VEDtrz`(=Gz#z%Wz+lMAz+lhHz!1U8z>vnuz);W1z|g_Uz%YxIfnhN# z1H)EU28KPX3=C&j85l0HGB7+~Wng&D%D|w;#=xM*#=wxx#=ua-#=ua*#=ua@#=vld zje&uaoq>Uuoq-{Ooq-{foq-{joq?f>oq?g7oq=H2d819QQFsO+!FldP}FrDrp9W3DOJuv&(J;g$>o!!H>I202*<1`k;Vh74H-hE7=qh8eO9 z46|ez7-q{dFr1ZTV7MsD!0<_yf#I7h1B06!14F+Y1H&CT1_ljz28I-Q28IlI28L}bOQiZ@5%K5 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@timestamp.cache b/examples/http_interactions/build/lsp/erlang/gleam_time/_gleam_artefacts/gleam@time@timestamp.cache new file mode 100644 index 0000000000000000000000000000000000000000..cecb99a09bbfd3af4be2fb0149fa1307c7a8b19a GIT binary patch literal 50354 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 LDkuhpF9-twD!D&v literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache b/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache new file mode 100644 index 0000000000000000000000000000000000000000..df52b2807e17217fe951c2ff8635a19a4a305ce3 GIT binary patch literal 15781 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache_meta b/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..12d1ee0b12da437eec859c1ed1c68259b5a6d61d GIT binary patch literal 278 zcmX@S!yTpHi8Zn46gl5))xyV3?aF_MZ8T(|se502GTdFffQQ zFfiyaFfdp#FfjNtFfb%CFff!dFfep8FfhzwU|?9!z`(GXfq`Kw0|Uc01_p-H3=9mH z7#J9CF)%PZXJBCX&cMLH%E-VV%gDfB%*ept&&a?K%*ennn~{NGE+YfOJVpkFEsP8d z#~2wH?l3Yi{9@RW&x;W-ln!wV(` s1}0_(1{G!o22W-NhHz#EhDc@xhA3tRh8fHZ3=fzY7}QuG80sAc0J3f*ssI20 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache b/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache new file mode 100644 index 0000000000000000000000000000000000000000..9e89e298ed7808873524a1dc523a1e3ba103877d GIT binary patch literal 14880 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 DCQ<*b literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache_meta b/examples/http_interactions/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..366456422a3f68371186c6c36bf144c41eb380d6 GIT binary patch literal 397 zcmdnCiYJo+2AH4>9w?ojlbV>TpI=atnV)CQz`&5gz`%eiS6ot*nU|i*z`#(!z`#(r zF-_Q@5&WME*ZVqjpH!@$6>hJk@$4+8_kQ3eKvCkzY>Ul9!R!WkJDq8S+&Vi_42;uskiS{NA^rZ6%vEMa6|Sj))3aE6hA;SM7M z!%s#A25BY+1`8$z24^M)26rX~22Umi1}`QChI}RlhAJiohKWoJ4ELBA7#=Y(Fg#&m zV0g;Jz`)DQz#zfQz+lA8z|h3Zz|h9bz|g_Wz|hIez_6B?fnf(T1H(mT1_lKd1_l)t z1_m`21_pH&1_oai28IY028Ik428Qh{3=F$j7#Q}lFfi<6VPJT|!ocvBg@NH83j+fK tD+2=~D+7ZDD+7ZeD+7ZwD+7ZoD+7ZYD+5C;D+5CwD+5CT6obMEgaO=TGkO33 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten.cache new file mode 100644 index 0000000000000000000000000000000000000000..65bb7f7f0b6ae6b32c3e555fd5acc39834d5b7d7 GIT binary patch literal 48765 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten.cache_meta b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..44085f94be0495cc48c033b2e71166b2b4447420 GIT binary patch literal 2463 zcmb1oz9N$W285st0VtiGlbV>TpHx|rS{z?el$r_>)?i>@5Ct(97#JY(sYN-7dFlGe z8Hq(XnZ+fV3=9mR3=9k+*c25MO6z`($;fPsO53q=BC z>_P?xhD{6%3_K_@`2{7J`FWce7#JQfFfj0=%9Rx8CnlHV7d>QPVEE3!z#xMmod%Y# zj4v)NNG&SMEY2_b!N9l`RjiI)t4FB*M2nyuXywsxqj0_C&ObiTsXa*MN zCugUYC@?WFv@$U;h+&o0hr~y58xsS=VkQO#K{Qn*MTvRE1^GoKOPClK&N4ADSpNN0 zvh~x$qX!wGDj66UKnVzxKtO2$6ziba1w|n!YCvHQ3Q|zefr1F+dyvmSjs`i+f{B5l zg^7Uy*%=sSvNJH8V`pHv#LmEQg`I)nDmw$ieRc*0c@72! zZ4L&87!C%8r5p?lD>)b#R&y{gtl?l__`|`#;Kj+n(8kHYFrAZuVLc}U!y!%vhR2)? z41YNp7?imf82q^y7^=A#7*=pGFdXG#VEDzwz`(=Jz~IEqz~IHrz>vbtz>vkwz>vqy zz)--=z);A|z|halz;KkCf#Ene1H%b!1_mJ>28Ki)28JFU28Ibd3=Efe7#LJ|85m4> z85oLr85lZv85oxGGBE7rWnj3$%fRr1mw~~OkAWeLkAYzY9|OY%J_d$ud<+b?_!ttD(7;=Rf80v%> z7@C9`7+QoG7+QrH7`6*DFdPwPU^phsz;Ilcfk8lofk9n_fk9J*fk8`zfx$$Cfx%OR zfgwkPfnll$1H)nw28L%M3=G_&3=A%!3=AHk3=CeP3=H0)3=E;73=CPK3=F-Z3=F$O z85r(}GBCUnWnd5#V_*msV_@hIV_?`R#=vk*jDg{n7z4u{F$RXaVhjwL;tUK<;tUMw z;tUMS#TgjRi8C;q7iVDjC(gjYDZ#*?Ey2JLA;G}VA;G|~R)T@ysssbWO9=)BCP@Yc zDM&?e2muwI&h z;iWVKgRTq%gO>~gL$M44!z>vFh6OSV3`b=c7@o^8F#MNcV33w&V6c;AU}%tKU|1&0 zz_3A5*oD>5)tD>5+jDKapu zP-I{@sK~%@Ly>{uqap)?h!O*Xi4p^Yg%SgUl@bGkwGsnEml6ZRTqOpEV@eDRPm~xK zIFuO}%#|4!ER`7;tdtoTl9d@4s+1WR7ArF_Y*1!kxT4I!@JpG2;g2!{!(U|v1|Jm$ zhM6i13_Da97!Ij0FkDq(VECuPz@Vhcz~H0Gz~HCKz~HaSz_37-f#IDh1A~UFfhnzGBBiRGB8wVGB7OEWMDX|$-wYVlY!xbCIiDqO$G)%Ed~aA zEe3`REe3{0Ee3`eS_}+(v=|szwHX-LwHX*Vv>6xzwHX)|YBMnG)@ERMqRqg-sl&ja ztHZ$Hti!;Np~Jv1SBHV&tquc&o-PA}t1bgWuPy__L|q1k$+`>-Q*;>^=IJsp?9^po zc%;j~Agss0V6Ml&P@u=aP^8DeP^`zmP^-tlut<-A;j|tD!xudU1_ylx23LIshD?11 zhHd%`3_J807EVCXPlV3=pX!0^_9fq~nQfkDoYfx*>~ zfuYWjfuY5afnkav1H%qO28QQ`3=Awr3=CpM3=FbH3=G~z3=FkK3=G{y3=ETu7#OY_ zF)%0_GcaTsGceQ{GcdFoGcdFnGcas6W?;Br%)s#6n1SK7F$2RJV+MvV#taOyCJYRA zCJYRTCJYP{Oc)q8m@qILFkxVLVZy-h#)N@E&y<0|z?6Z((3F88*_44{fhhySLQ@8Y m_ofUCKTR1Jc+40WOwAY=oXi*)ip>}pR+}*}tc79*C;$Mb#Inu+ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@acceptor.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@acceptor.cache new file mode 100644 index 0000000000000000000000000000000000000000..7196d163210a3b2efdc75b53d5a22365dc896e8d GIT binary patch literal 23319 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@handler.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@handler.cache new file mode 100644 index 0000000000000000000000000000000000000000..5e3ff0bf1cb258fb4614c46653ee7f9471d12c78 GIT binary patch literal 33356 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 g2rHq)$ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@handler.cache_meta b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@handler.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..366f297df75ab12184334a44879d5b92d95ecfa8 GIT binary patch literal 1522 zcmdPjwKbCg2DqUNUMQWOlbV>TpHi8Zn46gl5>sYiU=Rc`7#J8JlBq>GiFxVzi6!~D zDhvz^HVh05BG?oZ6y+zU78lzxFff!cFfj0-sL3xV$;{6yXJBAh!oa}5k1AJEpr4pr zl3%oxfq`K+hSs9g;?kUwJq!#C=P~4pONuh{(l0PDFg#{pVBmurn^|0vnx|i!pPZdq z@`QnbL5h)qK@6*`KE!3k(u@oY(TvD`hnP`Pl$cjskY7|1!^ptU%E-XL4mBnxKRrD& zFTIVCfnf?G1H;1H?&Fuw>xjrOLgg747(l@V3J#FBL4E`I0pwPYi$L~)j0EYQ%E-Vl zjgf)jAV@D00|O%y1A`kA149}U14APd14Abh1H(in28K0E3=CVD7#Q|5F)-X_Vqmz- z#K3TmiGkri69a=ZGXsMGGXsMWGXsM$GXp~eGXq05GXp~tGXp~lGXp~_GXuj+W(I~` z%nS^>nHdT^0t0?<@=q%B&0w>Z}Y5nyd^ATC5BVA*>7xb66P|R z1B1N~14FV914E+_14E|}1H%j<28JU-3=B-d3=ERO3=FQq3=Gx63=ETm85kA_GcfEF zW?(of%)r1e!oVOY!oXl8!oW}>!oaXcgn{9@2m^z%CU-_#26T2#26S##TXcNi7_y6h%+#liZd`Yi8C-v5NBYRBhJ9EOq_vXw>SgCO>qVW zbqNLrD+vaM3<(B?IT8#Et0fp1_DC=={F7i{Fpy+maFk?V@Rwv@m?z1=a8#0k;j$zH z!+l8x1_3Dsh9D^hhH5DWhP_e@47a2h7(PoeFbGI9Fhof+Fl0zGFmy>XFiewXU|1l{ zz_3D^f#ISw1H*f11_oOh28I9`28KEr28K;C3=9Wl7#J?eFffS7GBCKwGBAY8GBBjd zGB9kBWnj21%fRqbmVx1`ECYj@90Nm!90S8dIR=KyatsU~= z{8wOLP*Y@Ja8_hs=u%{0n4-wQFiVkvVZI^*!zM)rhAoN=3|kc$7@jIJFbFC!FbF9z PFgPhOFcd>EtmFXzwb1fT literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@listener.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@internal@listener.cache new file mode 100644 index 0000000000000000000000000000000000000000..ea3fae72c787942eb2bdf266762f469208de2e4a GIT binary patch literal 7135 zcmb1SfPnOz%;J*NJpIhPlGLKS#2kGHKeY%X%?=_M7#N_6n4nx%C>>mqSdt1B)MI2| zuwi6iAj(vzZU(SUG%E-ifMPeyBnS;N$~U#RI58dKu%8SJ44iav7@F-6C&5TICZv3l#`g3E`ltk4-wNZD9TSxEiQ&x z7hIZ@m6}`vGY%B+$ZQ6fI%qtT8wrdsZzd<^4 zV~tdh3qcq|0W(Mh38T554=M%qjDB%`a&~G7%qc$L6XMtl%N>OTYF_h21 zz{mwZizX?2vL}a{4!BYf<*xq zX$Dw)2c;I5=9Iwf+$aIIg<%tv2AKepgVFxkAW@JykO&CFbTKe6z})9rRFq$Yt`24@ z0|NuhNig-rC5eb&2bqZ~K`>dv0;`jefx#4{pMjBqk&yvhP=TBQlLt8)8N;l>nJ!?8 z(Nd})R2-HPN{SNmia|*SmIy*%0#JpR6pGa__kq$l$i2wQ(X1ve5r!le_=A%rvPlp& zEI)z$Ohi!xbDLjcZYr|dz`=~>ID!!cQx7faAuhs3q6822)C$uE3v-Bv@=FT%p)z0r z{rr*w{lw&w{34j$!Jx#Jnu6>XkOor21#TQ_5e+hmMnOZYM`6Vc*rVXA2yzqz!(tOu zNkK#)xe-|$l6SzR5l9Iv^kH=a0|Or;1A`VZbu&1OK?Dec%z|Mm1vV(4VMc@4=p{D6 z^ax94;06)QaS)m;^I(YrEqya0ibfQhK;RXFQBSR{f< zB2ch6GBPlJXjT(f?qOEGAmc!R zMxCSqty4gT!rX@z)nwU)!z?^m5aa^Pj0qAQ>{$zxFhTBw(b#=~U394U4dg!%h9!2C zOixw{!DT3=Clm3=9nn3=I1j7#LnMFfjaJU|;|Z1@JI3FlaL}Fz7NeFo4D^ z>=_vt+87xaIv5!kK&}4ij0_A*7#SG$FfuUQU}RwU&d9)E!NkCj$;7}ggNcD*I}-!L zV8BUv=jx|c zB<2?6q!#OEl#~?2g9msLlfj)#{iM>&oD}_n#N_P6^weT~XeQGyE=q>8!RnC*g!I7K z93J|xh%(SK)H8rO13|%(55Ca}{AmhD^Bbk?)-M8gJhA5qm=kjH)6+Ba(s6dSV9vm) z09J`W1_xjPP|n1_Pz%jRINS=B2~*3b8OSNJDc04!yA8%}dNhbTZbn zGB9j`%8=G5g*uc0)RuE&gf!NC%OnIR)j_~HrbP=?~vJa~5}o|SzL3auW;UQ;`QstXLQroS_;CBrcQ)ImXJsa2o0qe8~ak-@MfF_~O)@ zR8XfF=I9g53=CJGp2uM~%-s;JNMX8`nSo&&R4+dJVc8L40LN-52Q4*GB9*P-4BZ%aA^%KAfcu*o!1gR>Nb*$o;S1rR(6n(Jl3mG$prJ_HeE-^G@abz7>p$Q(3LezTpIVfYn3t|!P?VpXT3ig0(qdp>;0G}n7#JXG@=FTz z6O&8wi?kUS7#tZG7mi&BeAb4r{T7#Kns7#R5Aa+$>?sd@Uv`N`R-C1DH<4D}2Q z3}RSi_45l#GV}9_8yFZE_AxLp2%@PfDN4*MF32w`+0Veh@PdJX0p^#S{Pgt9y!4k0 z3=H2H7#KFQ6t1ej!|=iisvksw+zfIX$ORztK}LYo|6pKX_zCh0BLjmrBLjmjBLjmT zBLjmyBLhPlBLhPRBLhPxBLl;9Mh1o@j0_BW7#SFDFfuTFXJlZoU}9j%WMW{L!NkC@ zor!_rF%ts=6Eg#YCo=;>5HkZqBr^j;IWq%8GcyCjJZ1)l9n1_2*O?g@els&LFtac) zsIo9H=&&#__^~iBq_cqB&A{-8g@NHK3j@O+76t}JRt5$QRt5%hRt5%tRtAP*RtAQN itPBh@Ss57SvobI&VP#<0&C0-VhLwTg92A563c>(~Hdk-} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket.cache new file mode 100644 index 0000000000000000000000000000000000000000..8814e60a59a07b50d0ab7b5201d518f451cf430f GIT binary patch literal 23812 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket.cache_meta b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f3015b6de80b68aa9e51f220921598fdf9666cb8 GIT binary patch literal 745 zcmb1mz9N$W9dO#lHl1k}wO@xMA;G}Fpvb_$pv}O*V8Xz_;K;zh;Kabd5W~R005YMH zfq|iefq`K%0|Ub%1_p+W3=9nW7#JANGB7aQWME);!N9=q4P+i80|PfB1A`bN1A_`9 z1A{Ij1A{dq1A`kQ1A{*!149%e14A++14Ahz149!d1H%+X28IQU3=HcS85njkGB6xv zWMDYP$iVP`k%8eQBLl-HMh1poj0_AcObiS>ObiTSObiT4ObiU#ObiSrObiURObiUJ zObiTuObiTRObiV1ObiUEObiTpObiTVObiV5ObiSiObiTDm>3uqGcho%VPas|#>Bv| zpNWCt1QP?pB_;-j+e{1$PnZ}O-ZC*Td}U%__{YS+z|PFTAjr(XAkECcpvug^pv%m_ zV8zV9;Kt0r;K$6s5XQ{F5YNoOkj~7&P{7Q0DFm$mnFsx-^U^v0T!0?)dfq{>efkBs* zfgymEfgzWbfnfqG1H(pE28L6t3=B_M85kJY7#I}U7#J+r7#Jei7#K>}7#O3DklSj z4JQLbFed{;0Ve~)WKIT#Rh$eAhdCJ-ZgDa&eB@+c;NoInP~>7@u;5}~@aJM+Na12& zsODl|n8d}vu$qg3;RqK4!(A>0hEH4!41(MY40_xQ49?sP3=!N64Efv)3@zLY470fz x7&dS-FdXM*V0ggI!0?ruf#D}N1H*4_28KV}3=Bd%3=G0N3=GOp3`%k!3;@1VT_*ql literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket@options.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@socket@options.cache new file mode 100644 index 0000000000000000000000000000000000000000..1bafc1e572d42d6c0ada0d65a569ec3a29a8a2b5 GIT binary patch literal 23734 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@ssl.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@ssl.cache new file mode 100644 index 0000000000000000000000000000000000000000..4f1ddbb808aea169a7ef49686f8c14abb2acc7f6 GIT binary patch literal 12616 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@tcp.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@tcp.cache new file mode 100644 index 0000000000000000000000000000000000000000..0e1dcfb62307ee2c61831024ae57d6276b980f58 GIT binary patch literal 12844 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 WF)%QwLk*h8!oaW`>KjmSgD?R3us^Q= literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@tcp.cache_meta b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@tcp.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..a0070bca76d7ad1acaf0f3175277bf9d156057ee GIT binary patch literal 665 zcmb1oz9N$W2H2qt0VtiGlbV>TpHx|rS{z?el$r_>)?i>@-~uri7#JY(DVfP7nhXpK zZVU_zyr?pjd5O81$?gmc3|R~e41y?++<>4xW&Z4z{t$N;L6Ov z;Kt0rkj>1%(9F!hFqN5sVF@z>!xm-+h8xTb3^$n>82&LcF!-=AF!-`CFjTQHFdSoH zU^vdg!0?)dfkB3qfkBp)fx(-VfuVtwfuWI=fngIX1H*q-1_lN;1_paJ28L`l28LWV z28KK~28Q`;3=Hep7#I$+F)+MfV_;xsXJFu9XJBw(XJ80qXJANXXJDvdXJDAi&cLvQ zoq^#HI|IW#b_RwA>fQ! o6uB4}EV&pMytx<{;<*?YCUP+_OyXi-*ullXz|IZ9ptt~G05Dc+Pyhe` literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@transport.cache b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@transport.cache new file mode 100644 index 0000000000000000000000000000000000000000..3aa7bf7e17e3dde34bb189f9e5921d734103630b GIT binary patch literal 19223 zcmWe+fPnOz%;J*NJpGcQ#Ju8y{Gt+&Fgu7~U|@hsGD5kWP&x!zZ7U-K!*oUl2D}DB zHG(NL!*S}vY8%)DFu@8Y85nq=bZAMMbAC!{L4IZ)*bg9Kn8QJAoW_9^F@aPtFfcGf z>5$|CC<`GLT$}?JV}Y`q^7G+>$mYQ0LyAgMp-LGTU@mq`%qd2Q!mRYmL@^0u8b~J= zX$Cfs1Oo#@P-<~$P6^EI8zmsZo1iqv1ehF*_Rj{1g4BUTKp3Wr0pUK^qN4mFbagOO z85kJELHZdO7+?`ul$xBHSqAqJ$W(MGoRJLk-W4_m1_!V%Mh1}8AVWYjOde__+)il9 z#Azyh7J@BFiTLQ z6I~CA77nPaQ)Y={QBh(gl#8IiL5ax0*nJ1fIuPFlr6v~V=OK)M=}AmZPAw>jFUibJ z%`b&#El>pEP=Pa2U?G#u%)sy%%o$XX4GRt*aES;B6hv^Ku~4D|>=yD|L(D)=0Q^udC+Fvtr52UMC#Iwnr4|>1vL#3clmc)l!Ce`^GzSYZFqDH6 z3MdIM!ph6cywnnC@x_o?P-YUJn^;hgni8K`P-X_Rp8;es$Q%%cIRqr;DGB013T&8x z5D^4PS+tQH&s5O>E6Xwx^HPd460=cCT1-iBs3NjHqGkt$5hx_YSs55ozySdYdj@!T z!sJ0}Ko~6v;WQWK1e9h0$N)G-GYM91P+r!;G6D9YlV;Y!q7|vmW?+Do$;GK9@%aTM z#W3H4f)zs!XJEpNzQD=A;KcOfCs#1%ZA1&)w55X=+?24N^OIX|zYC_g7BGcP^9peR2%wHT!Wz^4pn5{D(5m7qcZ z6u=CqSqCN$bppDdaGFb>tb-b1sYN-7dFiMT1`*ST1{cgX0huW<4`I+KDV~f>0%n}G(STdosT!33sQk0pO4l@;l#%fJ4njTo`3aN(T zGvOnNpg6*m#2GU%+oky#7`(yFt|3`Lf?dwQzyRtNC6%V7r542(XI7!~VR0$J88o1f z1O*SPFatvA|G7Kfdz#+ z^Q+kz7=*#e3biQ(lLx5*VKfKhG?zZL5-f>;=dhstYXOim!Ce$k_a~{cB(*rcq$o8N z<{~GMNJvpCyo7|RM^Gpk7S_tb-q?m|LeT;XGn6a~Rf!gUGDwLU@i2Li8W2WvI8Jk6!94VvBWOM*C#qnH2eMuP6pSDY z3R*N7oWTi8OMOfX3|wFiYBYg`K!!pwnxk==OE{XK2EtbWfb$s4I-;vDSSbW5#9>V| zn8|2vC9b{=q3Sp~niKHHF~|=%d+H>aguNsOH$Fib8$59zpP84I4+|<#lEEg8Gk#%V zy-kpT;RaY0DBUo^k{zgVkXizZ9I#CwD?qwH7-lVqCdy+lb0HoBX+y!-%4zUC5xksE z$xMbj$0aivzCZ+K45|r`K`pSi;3h!C^l=Y#L5)H+2{IaoCtF%;PSQAzG*s z1_tU7&0O@&Zf|h(?VBNYo+122^oF#0Ws0NCJ#~r!f2@rr@4gdBQ%q!mrlt@D_DaTCP{_!@(89>T09ti9i;;n00V4y$GDZf56^slFplPC`j0_BS z85tPfGcqvzW@KOxVParVXJTLoXJTMzXJTOJW@2FIWny3ewSnd`F)(apVqiGL#K7>F ziGhKanSnupnSsHHnSmjNnSmjLnSmjTnSlYcT&szhfngRi1H&d}28Kh-3=Geh85n*t zGcahdFffF(Ffc^3FfhcjFff272tbn~tt<=-b6FS|4ze&XTw!5g_{74%Ai&DNAi~PP zAjZnT09u2R&dR`0#mc}io0WlK4=V%10agZvL#zx8pqb*^tPBjFSs55u*%%l^*ccd8 z*cceR*ccdM*%%n6vN156Wn*Bt$i~2MnT>$~w07_b8w0~1HUDkhk>DshXFjJx`&5>;V};b0~ap?gCQ>i zLj*4aLp?78!!lk5hO@j33}1K|7)1CO7%cf180z>K7}oMJFl^>yV7Sc3z`)1Pz#ziU zz#zfTz#zrXz#z@fz~INvz|h3cz|g|azyKQ1n#Rw-u#TUB;Q&7a!#jQk21Wq}21Nk| z21fw~23G+Fh7U|1*2z_44Gf#HTQ1H&_61_llh1_oUb1_oyl1_n>@ah)*q|(fs6#at4hR%uhP~7Hw&N^A(rkM=o#u6z(rvUSZf!5uK;`Lifae}btV^e>K$eu+y})u zSX+gNau%(Xf@(Ol`2w9L#%$}tDgbbh=of((MWZ{RB)Px^8XO2)(duJZ6#-vo2dg{e zSQ!}9Ss@Dxu^0?%?G&fxrNFdUa4;~~fa`Py1}s`&ZCbR2HZa{eYzz$5(7F_-b796J z2JX#ZwND=p1H()nh=b$!85lA_Ig|m<8hrxpImODra1$B;1lxT_3=Ao35Ox$h14Ar3gsscLz+k`uVc+Fo zV0Zv!_i{2YOyGowr*JVaq;o;odfW^QhTIS~s{jK7hX91#Da63gBLrb5iZU>yh(g%d zQZ&f9pcGtER0&IPApaIa{k%w+fnh0lDvW_4N`!$S7Mh3FiZU>401rrGCdA_69F(Ri zEZA8&85p>rxdE2SV0pPDxd4_}(E4mJ-BW}Z7#4%OU07{|#WZ9b2c{{InSmi3>P2)b zi;F?|0pxcE$T|g>CPr2U26m_q&^47L7eHkg!0mIGo);Vp3~!+s6PmJM+gzY){$Z;P zVGeZyPg=tCPZnWdmz!Dd7X%@x7zz_#@3c8k()lli*sPM!Di=R+6)937%ZT<0o{?s z#W_#|88AxsRjdpQo1tk1N-=<|5@>Q{@XLg!zjy%#h9qd_6%b@#5Eg{Q++R?77lyDk zL>L&fMIdZD5e5cFDEqJo1H&;W8=8P1_P_>7pbOVv))tlK!I*0W7#KD{z4k+xf#Eka zqyG?PVEBzAfQyTBV95|iMm;FRz;FTNaC|ib%n8B8IZy{Mz}BULq@tM^7~-I%s~a-| zgD13f)nQ>^(1(_&-&hzJenQ#vSs54>K}*+}Yzz!@prvagI|D-tv~>07U|mZ-lt z7#RLS*=so&7&bsl*DfvwhF)mtn#9e(kP0nbRRtIrG@uc*O^AVEC$w~J6lGv&ftIc) zkqqO2H$A~Js3|uCgB8?73lRneTWGZ5%xSR11+R-iVFN0$l=vAKwD}>KZLa_W!!fAa z{)jR#u!=zv3BJ-kH?aWbs2c(d47Z_iATGwhAO$VHu%vZp@?gNSC=r%h(wG<+DxpT> zOO3FuadBxfXiX!`UuirH47pH;ck?kYOoCSH5BL}uoROfbw9&(P6meioDj`=(D=di=(Ug!u2 zFfd3!BL$0X(2#)ETd?)iusAShVPJ5D1_n+)!wPZKsxOs=fgzg(;$xgzVQ~j-Wx}kz z&Bnm+h>d_=SiV51JgYev7#cVrI-!vdEy5tJZdfb7D77FbF*!9p6||EOW>TO414A4% zsF(y982F*?elNtp@CRB9Nr^Bps6&&ZqZk8&A2f|amBMRkcs~e75eG{cKLi;V*o7bt zjuK{I$b^>B=uuUYTmTDa)XGwynSsF+n&@#l0)H$QF*7hUFhksc#a<%vDyUuW3H1^* zK;R((s&i9IU~Lv?Vq)+t2s43dU?}5ZV5ow|)@(iohWSwE;`9zIOhLH}X~h;S`S1uc zFh~nS`~!_DXd*!E1;PR}KQA>tKMkfkoS%Uq7HR`d7vRdIH$@m29*PjQ8kXeJGE;L> zpk8OV%+0`X0~!IpxEUD!L)nTv3=C>eMIJm148BnIIUWXv%TV@L9tMVAP__&&1A`(o zr91O7FnB=OS-cDk`B3&mUIvC~Q1%X928O**_I+Lkh9^)q)Otir7U$r|us!Sy43nX9 zIKvEP9J~rDgQtMW+zbr!xFNw-#LvJ`OQ<^tOOsd@oWkO97Z(G=SuTi~FsosG1f)h< zFK*$_!t)@-i@RKn>F2Wnj>UvV(aU7{Z|JDqaSLS}1!VF9X98 zDEkC21H)-3`#moM!zU=4S%iUs4eAG|v)~bqoIatO^O%ym9G8`-n3}Vn!2d&8&pkVcNrKM9zb^`d}Cx__{j)q977unP-E~cL4p}1$H2g#4mIc%I|IXKXs-h119%|? zUKs$3W6;DZY&`-ryE6zeGBC(7LgtO%GchpyhW2xoFf%Z0fcA6xSr`~*L;E(Yq6`dz z*!mc-n1ieohdGp$n}LBF+R5PMW?&G8vhinISOSD^MS{6@3pWG9UT8{R&%?m51L`$L zeg+0FXoY)?pMl{%l&vYsz+ePz@FEfpTAG8#DFbMm3(TUe3=9lAp#jXp#K0f`4VH(3 z3=B`9{b2_o1_l>s*DFehfgujcMp%TFLSf#5FLi>c?1LUJ0!lKM7#SFDL(|17CI*Jh zP@DUi85m|jBczIjfuR{1A;zo>3^vft<_lH^hL2G8M>YnAU(k-{QFaD~bI>Hx!@-X3=Hy6U#bf;Fc?5vnjb_M z7=A*#Bd`zzk1oK9eb|0ISh6r+V_>j=I!=?Ffx!^!IMn=v+SGzoo6x;(uvqJ5U|^U6 zjj1Kv3=FHGDQrFu1H&>NNd7Y9XJD{^`T@m#utW=)_J?Id&_pI1G}vD-F)+M?vX3(} zFr0-(!6Ft0h855#2xMho2!{r{CL05TKGbJQ>9IT;vIpq{YcVqma_+W(xNf#DT2 zwzGv881kU@`v@~I_(NkmP?&)s1j>fC1)!A&G|zxi8_0dowMa1Mf@bY9p_%2mAOpiY zXi=6R#K4dawFl-Oa0b@Lwa&_sfq}slYSJA+28IVvldy)8XI=>`Y3yZUU^oB`rM=7y z33bj0_BKp+)mKW(I~UP`9sS zVPIGfP0DVp3=BTdn2%s(V2FdV|AJPH5`*$i=|$9m?Lp&%m$`8Xgw~85piZ{i-d*z+ecCO)Fsr1_x+L0VN0! F1^^{_CRhLf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@transport.cache_meta b/examples/http_interactions/build/lsp/erlang/glisten/_gleam_artefacts/glisten@transport.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..9618c9ca4da65795b955c317731d5e84b6c4ea65 GIT binary patch literal 1351 zcmb1oz9N$W2DqUN0VtiGlbV>TpHx|rS{z?el$r_>)?i>@-~uri7#JY(DVfP7nhXpK zZVU_zyr?pjd5O81$?gmc3|R~e3?kSh^;1%l^HWl@85kI*GB7X*qNqtN%1O*i*H0|T z&z;7=z_1I$5U7HJqWt94;^N&53=9t$7#Mg^3@J)2F3l-<#K6Gtm4ShQ4=$HkT#}ln zU!0$uom%pZfq}uEk%2)BtE_&0K}lwQUaTFfhzzVPH7O!oYBag@NG{3j>1yD+7ZFD+7ZVD+7Z#D+5D1D+5CnD+9x9RtAPW ztPBhXSQ!`&u`)0mW@TWw&C0;=nU#Tom5qTxgpGkgg^hv1i;aOHmW_d7DjNgCSvCfS zi);)Gm)RH?uCOsMJYi#C_`}A)z{k$OAj{6c;LOgz5XR2HP|ME1u!Nm~VFfz_!zy+L zhSls03_I8v7%s3gFg#*sVED?;z#zuKz@W*&z~Ifnz>v$qz);A+z);M=z)-@$z;KL% zf#D_x1H(5C1_ohH1_p6X1_nt^1_mik1_o_T1_ozN28K{h28J?D28K>f28NZK3=AhZ z85quTGBBLyWMH_!$-wZClY!wUCj$dF7XyP17XyPY7Xw2A7Xw2Z7X!l@E(V4TTnr4G zxEL5Vb1^WS=VD-Z&c(p+lZ$~toST6`i<^NVj+=p@lbeB|mz#m1pPPYU0yhK0L2d?y z7u*aC{5%W{W;_fGu{;b69Xt#ST|5j7-8>8odw3Wa9`i6TaPcxQ81gbOMDQ{&)blbh zEaPQhILphx@P(IwL4=Qi!IF=Gp^lG%VJ#m6!)87PhRb{m41D|y3?lpt3=;ed3{w0I z4AT4z41W9!3{CtD3@!W&46XbO4Ab}-7}oJKFdX1#V0g#Rz`!WLz@R9=z~Csrz~Cyt zz>p%qz)&y1z_3Vwf#H+@1H%^q1_n_<1_miX1_l{H1_oI{28Lik28M}(3=ES585p(+ zGBA7)WMKFx$iToU#K52^#K2%I#K7P##K2G_#K6!h#K5pgh=Jk05Cg+wAqEB}VFm^k zVFm_PVFrc>VFre2!VC=Sgc%ri3o|g>5N2R_Cd|OVA;Q3*E5g9wEW*IxDZ;?uC&Ivx zCBne4ScHM$ln4XEc@YMNiy{mRmqZvC9*ZzA{1#zg5Ef-%@DgQUh!tgEs1;>km@CS_ suuznNVUH*S!(CAZhF78t44h&N48~#%3~^!%49Q{)45?5I%EKTG0P4-xxBvhE literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@debug.cache b/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@debug.cache new file mode 100644 index 0000000000000000000000000000000000000000..52bc9c952ee6a525a7d29513cb1a281c4a1e6fa6 GIT binary patch literal 1264 zcmd;KfPnO(#N2{n{gl+C(sYmr8;D?FV1S52ISedN1~ZiQ%glkY7#JBCz!=2GBFz9Z zFetUSG^d0Kq>O=qVUrw$u^CE(Oo7S4X#Z@GC`cVh1cYI_7#J8>LE;Py46a2*`9c=aS6yOOy!8E09yk~0-lJl!PG-T zE4Yws6JlUs0EJmJ0|P@g0|P@30|Ub~1_p+I3=9lRj0_AcPz=)&17+xE{vv<>%_BRwU*Y)<1Z3(a z1_p+G(3C30z`&r!z`y{CEKD2NLCP5z7>ZKMQj3aHVNR)GU|?v5N`W+jm?#m6p2A=n z^%)o#%%L`;Xn>jGnFsfk8v_G_Csb_+0|P@il#QYfIZ?vA=Ljkbpo;w&7#M<~Y>+EK F7yy&hjavW! literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@debug.cache_meta b/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@debug.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..67db2ab09f71542385615cdd2fabdb2848ce41db GIT binary patch literal 115 zcmaEAw=9zZ1{k3XE-0OzlbV>TpOaZ!0um5pU|_I*%ffb6K&T3)0wgZPz`!8Pz`zjA hz`&5rz`)SMz`$^gfq~&40|NsSBLf2q6ob@)FaT7t3_Sn< literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@http.cache b/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@http.cache new file mode 100644 index 0000000000000000000000000000000000000000..37e273b4c39a05289c8ae35bb38d5e99db15b7c2 GIT binary patch literal 17332 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@http.cache_meta b/examples/http_interactions/build/lsp/erlang/gramps/_gleam_artefacts/gramps@http.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..62ad0f98ea001b4e7b501d7824c1f923c515f4f2 GIT binary patch literal 1220 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 eu)u?~)Z*gI{5+5fHW0zUzyMLl4&`w`X_wTr zoWzpU6h;OH20=y!202Cs24YQy8o>b4i)sroCZM|jW*UU%hfoa8`FSO&6(u2w*{S(u zsYM`BZ$<`&WHOuy(o4EC@i+uzAOs6RCI0F=gzZe)8SjY%hkY3WAiDEs}DNqWQMqN_V5=(PRK<4W+GB8-v&24B7 zfI16GaYJdOxCV*LWn^I3NQMhRdPxsG9F71P2Ei~dASJ_93=9mr=#~tzH~``@5D9ZW zj7AGfkP=X=gD{AVMHv%F90`*igt#2Y2$RUn0~d{OOK4z43Z(R9V1U^PP4X~1K>+~5 zAU+Ny3@ji?1_lPF{QMj!6I>$0LI@@wQdF7>lV^bEzQmkjgea~e8RP^I25HBo24N(q zJOQZ$VUR8m2C-od39igb2Jyj|2TU?BgqEZ^=cl9=M4*#C>R#d={c#1xv&sGjw%L5SnAHpk1v5&bIedv z{4!A@73Nng(hM*U2Bj94=9Iv^zex^a(q7$l~dMmSo*1|t(Nr+@E8N2%2I;=VVmruTprVKi%wS+h$&ZKD3?M5&f}p_0lqD)+L0UmZ=(92~ zWP-yDBVu928q`vF)WWoaw1Y6|o~1NuVS(k8S>jk!lvoLJ1qy~0?%;$FUtE%slbIv{ zRRI>$PpT|QEsifKN=*f)YEU%>5(z0vMbt{LRGwdunui?Z$l^o=Hq>T@mn;knpTN2? z0vjyKz`y{q3m(!ieK49PAx)q_1Z52zN|+cJL1_VmK`w*^Bue3iBnT<2KmiGA_0J?F zUx75itN~etjKPT))Xl_M(IKk_v5D6~dVu2yDv-4x%!twA0(lK20}5wYjzcuY92glG z+(`*3kWnBbNq0G>_1rSUItqFweVyF~aihyZEYNKFjbAX(?pMinl8@CMj4WCG@PaES~`O(036b|(X@7>D*& z;xqFy!JTrDI*{kF$rDu|!~A5x!oaW>9EqSXKxx*%5-QYg&%DeMco4y~gWLhaFh21y zO=Q9Y#Rchx!@?g?jDQ>o!Q`0;t8iiMCWu*BNUTxniKU$j(@T^F@*IsX7(mv;QV%%! z#ithKB<7_Hf+S#-i9SS3Kd~eq-h*;1$mC=p@uAT=ONdT@dbWnh2}DuWEcic!K4mg#V|a$!agrwdk~VHxrQ zr7$egIBIifiUZjW^A|KCK>2hVBLl-zQd%q^O|%bT>c<)BCSnO`KOVF$e$OKT2odJ=YVe%j~AWXVP2sCm*hJrEaCgTYr z_)sNM`UBYl@*yl8!lZ}^CXham{t8wGhArSMG#qn1FUX4w3=Gg58ef!JoQl$h!m5C% zN(z>aintgU=7WbJFe)inQ4h5}2xK|D6$R4|bs-t;Wop$@$Zbw>sB?-_OX5NOmZJQe zoXouR_=2MR%QA!|Sh85EWv8s64i))zR*fs%H9JT!fQLIGq6 zC?qgtiAvNkC!bi-PQh&Y^&81KAAI3UUDmlOFz*rf0k@29Og;cM`Z@ zWMF_y)`EP6f>9%p+;KKo5djzjOc{<0c!YAD5GFa z0?>pO%!i=Vhb+#(z`)7Kz`#pNQw$VRAoHk{Oppeu!aoM+!`okLC(V>1rkQauv`Z&RAI4-I>-bwZA5w+7FMvJ#L}k$ zDIq;-@fnC7($E3}WDy9%k{6mV>281-gppTZ5@@v@nlQ*MAWTJzaiul7I|Sxcv@pgY zOoI>r1uO^?Qv@*3r$q~L4hX|4f7louEYd(U=^=q`8ps9|3^Ep30G$sj!r-gCK#D<% zNOud0Q6OVbFvv_~0dzhQomJAEM95HBSpja*GcYiq%!z?qi&Fw53BoW>=G+Bo3&f z=fJ?g0GfgBVPIeYE#F+pz`y`nKev~Gf#Ev?1H(@S1_sc2Np?mC20lgx1_4IM@>xYj z1_m8Q1_nJw1_scYMLR|Y1`kFC1}{bi2GF|IG)4x7S&R$}a~K&IKr05fFfuSaV`N}> z!N|Y>8Yog^VqmafVqgehVqoZJVqloS#K5qYiGkq=69dB?CI$v+W(J0EW(I}`W(I~? z%nS^3m>C%6GBYrMMyT0X7#P@D7#KjaDUK`*3<)d@3`Hyq3>_>C3{zPc7?!gzFzjJr zU^vFYz;K*}0Xz-P$;!aM#mc|{T7OW^%D?~`h1t%^z;KL}f#DV_1A{Od1A`G81A{Re z149HG14A<#1H%+H28J1I3=A{b7#J3@F)%D)V_;ay#=rpTtKDQ{VEDntz`(`Mz#zrW zz@W*_zyKQ0&17d_C}U?}=wfGJSi#P~aGafi;RHJa!)JB|hA-?44E!7n4E`Jp46z&x z42c{J3`rae4DB2Y44XI@7!GqVFdXM#U^v0Sz;Ka+f#EU-1H%;#1_n@H-G>u0gI3DP zz%Y@MfnhQy1H%+f1_sbVndh7g3@+cjE(Qh=hDAASft7wn zer~FMT1I|aT5e)qo_=~!ey)CMMPhD2PHM4!MoCFQd}dxrYEfcxNoIatv3^o%W=@KJ zL1J=tVtQ(^K4jKOzqlwF#>BcLTMt~4!0d*vt}@Uw)H8sK!WghNC#;+U>(x)nEQwD9 zEx5qf>BqIs0%zx50%|EPjj#?{Nq#)s0kBd@m5qVH5SnXuu`@6ngXTwKhF+jbVMAlE zoNCCrBll3bX_+SwQw zdY~zA8wUf!ZW2-;mQEDRSkUayL1;pZVrF1Sh58HT25>n8OTnOp!!V68YzzzuQ1^6m zFfjC!;2v0NhP8}gX2O>Dfs%&}GXsMQ)G@?F9&F4C7J1-tb(q1WTnr3NT#%ri%)!90 z0P0vAr75hr23nIAUz}M5U$%Lloq^#6G&+fK6sQ3KDoJpRUBgm>04D>3Iy4s(V=OFk zz-vrkhJBat~6TgeG*DsYa{}3^ve&{*aY{ z;WaDqc@i4fu!&lb!$1KFTAp-_0TQ7hObiUs(6Z?P69dC@D4Up&gryp&iygW<4cwzIb%4cM zJOcv*Xze`6t)R&XRu)K@9%Nx)I0|LEb22b^lTabxa5KmXkkdk-yRATR@E_FLg@!XM zHGiE$2|l6o0*TM@`f!b}Vda?s!>##r2=Bd}aM4Z1T8Wcqz(28NH&qSuFo zfguhWp5Is)7&xE-;l|3q5CLUBVPjzU2F)91I2jo3K?6dEi-Ez83li5oTnr5JpzI3_ z3=EH;=>aA2z#?sy31nwrh=j7^ zple7#YEUc#`4p5o{W5c4i8dd)0uQA2F((7VGpI+LxfmGSppIY6#lWx($|ff0VJQl& zC1e8K#s+dlCJO^Y0W^#^voJ91g0itX*E6pKqzB~kpUeylf1vR+frWu#GF0Cp76yi; zP&P46gxLcg8G_~Z|Ij^vAj^c=85ks>;fbqL0IM^dV5_!48bPi!Wnf^igRXYSWo2L} rgGQb_I|G9T)KGlg7nnH#nJF;0wKFgTpOje=pIB6sSP2ppV_;wq05KRC7@*RXC8@>nB}J*J z;tUK7_6!URf++H-MLCIi>H3K!`MC}Z3=Am@3=AUJ6ciNYC#Mz{r!p`wbTcq8RGe-q z+j=VJVHH#@hyqyvG6SThhk=2imw|y{B?AM)Dh39Iy$lQt-x(MfeljpH{9<5WU}t1t z;A3Q95MX3r5M*RvP-J9a&|zd?&|_p^&}U>|uw!Ik@L*(M@M2_O@MdISNMmGRn8nDz zFo%(WVJ;&B!xlychG&cn3@;cN7+!+(Gchn&FflL$FflOnGchnsU}9ic%f!HNgo%OS z4if`|G&2K3I5Pu71TzD}EM^9VIm`?UbD0?!jxjSZu(2>Ou(L2Q7_cxfII=J>B(N|r z6tOTcbg(cmOl4tUSkA)0u!n_#;TQ`8!*Lb{hLB;l z4BJ^57>=a1H&RV28JbU z3=B)z7#L2mF)-X@V_^8f#=yYE&cGnW&cL9_&cL9>&cKk#&cIN{&cM*c&cLvOoq^#v zI|IWBb_Rye>0gU|?WigVN49nW=dtAkIk!28J693=GJsLE^|5Sq_0oigkYE(cpzMc(2_Lg{FKy!{7i`dK*Ee50*aZTv|nZp zl*Is&24N5%i!?lFobvNg^uPiY79=4>rKun@z!>Iax5S)cgec6lL8-;1IVCUyzCum- z2Bkrcfyu#W|7?&b$Y_uV2*Y$SFfhO@0|z#`I+&?gqaS1-NCOCi*f0k|5(rEbB!i4$ z_NC{fCg#Gl1eX+L=B2}xF~Ss;rNj6af(o3=A-H{qjq~5_2+BLQB#t zu$c=|j$$^p$;o9DFP)^G%l=24F(5NP-EGkJ#O)PP)OwPf`&(LC(0aP<0vtg+hBf@y0c3>|qppp!rx&pfjMg|5oMg|5q zwCau=vM$+;I*aij$u(wIgR7V*Z z82(^{1{(t?K)@JgIEbcBrURKoVt}wQz^sPXL10s$p2m=5WMJTAWMELn@E8MjkHP8; zNHl|rd6FwIuzQfxFfto!dS+x`z+V}Gjf8p;ze+|122Vx?hC&SgGGX^GN+JeV*aJ|l zfxU+uO2};NA;!djyTk??2MsOUsu&p<{)18m6KZ>xn}HpBNWqd8BxR6W&w$;A>^)>Q zcCWHC5K*3j&4v1z2<40n44q624689b&xPId$gV_YV|6jiEhJ`bE(Tboh_j^tD$emK z+Q`Vja0nym!yF0f-9X(&ToG~*H+o%H@4lNUi)x*fZ5WvL1(1wv@IkEc~Esdga zX^><&;dv6HpMvaPSf0dC#K^!fm63sA9Y&P0VD})*P*`M8l2&16phhSQsKxJ^^!R zK)_nJuuy_o0TTxYF}M{-TB{e`Jk(Yt>76C~=7Cz}^lqi1y9gF?u=oUr9L|!SDBZA( zgQEgOH-if8Q92k2E5FDXzCd>!6^%m9k;rUVY@%joSltPVb8Q9&20I1@h5!Zz2GBri z83O}DIRgX32?hoR&^Y~V1_p-D3=9lk85kHq!|Cjd3=Ara3=Hm!3=Bn#3=ESQ85q_x zGB6xwWMKFUl4D|E@MB_NXlG(zSi{7?u#Sm=VLhnu1+_4k85rc485lH~85p#o7~}>J zRt6Ca3=H}i`MIh3X&L!xX}O7cdHSG+t$uM)GMJ$UPL)uVh~ZrWJwrVMxG0PP^E+BF z;}}iDlmAc(6Y#ibGD>*>9x8zi-ynxRtj@-0{=-rNta%Tr>yR~)I@AI7GHATx6(eM< z1lceG?Ln|!P@}K|i(Xhtf%OCk4Yz@sS_Mp)Zh#d9kVY-c`=B=VV`zI0*~J7$1i)?v z4GY9$aWl^0F|aPs7?}wcU9i#zJ_-YKBxtln2#W?-LkNOcbi#5GwA}^^9S&v&27YEpgu{Xmmhr%u z6&CZ5N&ptiqRNEyL1_lOA1_lNYKY@{fAp>d@EVw|0AS_hDML9?z$g}@JJyd8KTgt@1unIaV zhNF;!6NwPKr1A{FC0|OU|R910*o*e@NgFgcU13!vPeo29TVsc4-Q2+x2LmC4E13Of@ zxTGktq_jAlfq|ivfq`Mi&BI4+J};0~fJ%cXkX;~CL0UoT$`}|J${83KPB1VqoMd2N zxXr-8@R@;u;VT0J!#9x8j0_Aaj0_C!j0_A#j0_Bu85tPXGcqt7W@KRa3zB1EVDMvN sU}$GzU|7S%z_5;qfnhxp0|Nsy0|O&71A`nh1A`_r1A{gcgWLeZ0Q<``d;kCd literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@activity.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@activity.cache new file mode 100644 index 0000000000000000000000000000000000000000..e7002868fb11c2f5f13066b66832af074967635f GIT binary patch literal 50208 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@activity.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@activity.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..19ac382d3737a9f1cd6187b9b5ffcb09878ff986 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application.cache new file mode 100644 index 0000000000000000000000000000000000000000..708cb303da9fbbc47e92555745eeef97328a4f5a GIT binary patch literal 58344 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%0A4(z;F`Ee#yeX@E*$UW@TWQ2+gOvSQ!`& zK-nKz85q7p*^+Dw4D!(YxSEZDVI!2C&CbA32+fbeoD2+-(EKRD#lRp3EeUnG7#NJ9 zY)38z26resn2UiS63Q;(VqmC%vf&9DR9k@?_OJx)l30=mYg_yU#V)9_!_&5a)#{j6 z4Z}>F!@up8=CxG*cceXp#H^`6=01z=wdeTcoTR`wxR@< zxZOD!7^0v{PFxfi7{Z~GJ^6|Z3{B9*Y^iDt3>DBMFRaeMpa#vEBJ2zd>d;_D$vH58 zfVTf~gE9>R1H%Sx28J!rnreX-1H%$%zPVt`z;FecmTN2+7#g5yIhUD%p%9vmSXdbt zIG}Of#>&9Z1!Y&UF)-9Y!(bU31H&pPn~#%$K?oY>nw$&_x==Q*aDw)b8KB_=*@+MH z(K8MPhR@K*P?l$4Fn}&D4O3-cNQRbI7uXmW?m{iU!^yz#l7NM<00M1Mht=vW>SjT=5t%FbF{hb~TL{ z81$h7SEa@b4As!(T3!|m3<1zVER-AtZq>tr+%-2p3)Ff6C!Kso1_sb{DX8pTs>Q&t z3Yu`jbr=|8poJ{I9s`32w5G~1U|`6F7ND@cH^^i9pe2AXpMs`6VF91S#K4dStvSyy zGca6$raE&L1_oOifQ4@xCj-NLXt6R~ ziGg7Qbl&*BIs?NGXz`)R&cNUVEk0oSKoJECBk--oFpqB1Vqn+~O@PTd3=A321SqY? tz@Pvva7zpr7%HI&u-1ryp$VD*Ul=nmyoDyf9t#GBiO>WHPF@TQ3;-FIL=OM} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f0bda56c28dd5435bb8fb81ed7b09cc5df9a256a GIT binary patch literal 3105 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@current_application.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@current_application.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..a4c795185e89aff969ef248a567083ce60f81bce GIT binary patch literal 1237 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@role_connection_metadata.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@role_connection_metadata.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..502eef8f67912136c9dcc1e80077cbe1eb8d2043 GIT binary patch literal 1051 zcmZQd`;p1Oz`!v1L4YL#H;Bc+z`zBi({oZ2bM;d)lS@DXvJ4CiA|M6>0|Qj1GA}VV zGg&_+H90>eRgQsy!IFW2fe%GZT4`Q#NoIbY6$1lv?%z%YT8fngRa1H&y= z28MgA3=HgS3=G0-3=Fbt3=A4<3=EEJ3=F|+3=G9=3=9=)3=CCl3=Gw53=FMo3=CV@ z7#QxcF)+MmV_=YHXJ80oXJDvcXJA;!&cJYqoq>UkgMmSZgMlHGgMp!!gMncp2Lr=o z4hDuP91ILkIT#q8aWF8*a56ANaWXKJb22bAa56B=<78lXz{$Yyl9PeqJtqUhKTZY) zCN2gBW-bN>e=Y`w94-ciQZ5FDSzHVZceofBo^mlTyyjwH_{GJ*@Q;gu;Xg<%HvZwHv>aIHv_{oZU%;R+zbq7xfvKPax*YI=4N2{!p*?&jhlhtJ2wM^EDr;N z4i5u^4-W%FJ`V#!D-Q$1JRSyy{X7f|k9imve)2Fd2=Ov7i19KosPHl{sPQr|sPi%~ zRPZt|RPr(~wDK}A%;9BV*u~4h@QIg!L4c2eL7I<&!Gw>2!G({3p^uM&VG17u!%98| zhP8YQ4D0w9820foFudhsV36QvV36WxV36i#V9?@cU~uGTV2I~uVCdy%U^vRp!0?`* zf#Cx`0|TD`1B0Xh1A~zO1A~(Q14EAh1H&W%28Kfd3=G!=7#MB~FfiN^U|@J7z`!6a Q$iUzx$iUzM#jtb>0PRGmNdN!< literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@team.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@application@team.cache new file mode 100644 index 0000000000000000000000000000000000000000..0c2f26446449e1d7e590b166137d4a1950d51b91 GIT binary patch literal 13075 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@forum.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@forum.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..02431825d324d513af8d196c87c27249bd8b99bf GIT binary patch literal 692 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 H1V9)7f=7%Q literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@permission_overwrite.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@permission_overwrite.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f235435268c31794e332649856c29b5237366123 GIT binary patch literal 804 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 PLazb`H9;ZOAp-*ddk(av literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@thread.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@channel@thread.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ed5b1adab57aa5852bef069a8c4fa3097528cd71 GIT binary patch literal 3002 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@command.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@command.cache new file mode 100644 index 0000000000000000000000000000000000000000..2566b3fc53b18df66fccf0055c56ee83a4dc065a GIT binary patch literal 247025 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 bQ3Juu^kigU2!_Uu1TzDJ64YOylmWs38uZi3 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@action_row.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@action_row.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..952e9b29cbbf5d8eb77af3c34fc8610546751f5b GIT binary patch literal 937 zcmZQd`;p1Oz`*cmW~?OxH;Bc+z#syp({oZ2bM;dy^AdA2ll4M0}qOx{DP9q{5&5928K!o1_n`>Tv2|m zesX?pL4ICpUWtBEX-P?bUKIla!x9Dt23hQik~0$X@=|l+i&Jw_lS`H|FfiO>U|>+h zt~fU}4`gp*QcfyV!+iz@1_ee21}W?sit^zq6&V>ALKqnsKwbqYPDgcYaY<2TUOLp& zP(}uZ9!3TRNsvNNK%uHFNv$Y}&&(?*E$L-sVAzM-`qJXmBB+u385tNpF)}b5eWED8 zUAA&l8PsSH1#%K7oI&9U3NBD6fIJ3rGRP?)TR{3hGcquIVPs%%Wny4(V`5;4W@2EN z%EZ7hor!^A1``9rS|$dDeM}4tSD6?XJ~1&ca5FP7s4z1ycr!CFWHB=^|=DqIWerCbaQ2e=p*j&LzB z9OYtQxX;DF@R5swfr*=efrXoaft8zqfuEa!!GoKDA)1?kp@W-&;RrVa0}l@agAWe_ zLk|xF!%iLshIc#+3|71h40gN>3=X^u434}E43l^n7$);FFkIzjVEDny!0-!-L2(Ph E0NKH@)Bpeg literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@button.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@button.cache new file mode 100644 index 0000000000000000000000000000000000000000..82113ada7771f0e486369ae66f4ff6817abbb19c GIT binary patch literal 18986 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 C3zL2T literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@channel_select.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@channel_select.cache new file mode 100644 index 0000000000000000000000000000000000000000..ece8f6180fe2d583ae04a1be6f92708c1981ef46 GIT binary patch literal 15151 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@channel_select.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@channel_select.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d11127b65a683e2be0dcb4137a25615c3df8096c GIT binary patch literal 837 zcmZQd`;p1Oz`)RRG2N1Z1;k=tU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&_2KTn2%fx!|(CMUDF#EOA|!JC1Bfd@rTenClQex45l14AW9PQP<%_Z+6o z6;L%G3S=TkFGx)l0|P@f0|Ubu1_p++3=9m<7#JA7gY+>nFeo!JFa$C(FcdK|Ff3wZ zVA#&cz;K37#OUW7#M<>7#MPy7#Q-I7#Iqe7#I#RF)$orVqiGK#K6G7%)sEy%)rpj%)qdf znStRHGXujFW(Ed676t|(76t}876yhu76yiB76yiT76yi976yh*EDQ`MSQr>Cu`n?3 zvNA9TvNAA4ure^rXJue`#md0&nw5cpn~i}%gpGkgg^hv1f{lT}i;aOHo{fQ_fQ^Bn zk&S_2G8+TK3^oRaS!@gpv)LFJma;K0JOk-rXJD{kXJD{mXJD{qXJBw;XJBYyXJA;% z&cLvboq^#FI|IWHb_ND14h9AX4hDt<4hDt>4hDv`91IK_IT#o=b1*P$;b3531H(NY28KsG3=D617#P0tFfg$2 zGBAkqGB9ZHGBDWiGBEh?GBCvOGBD)wGB6bKGB7mrGB8ZxWnh@b%fK+5mw{m+F9X9P zUIvCAybKI%d<+aid<+aKd<+a$d<+a;d<+axd<+b6d<+aZd<+bEd<+cvd<+bgd<+bG z`4||^@-Z;{0;%8vk%Fn>?m7js(I~0S`C>p literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox.cache new file mode 100644 index 0000000000000000000000000000000000000000..74dca2997ba7f387bedddd9300c53aba24519503 GIT binary patch literal 4226 zcmWe^4ywtoB{p5_){>(2C7guo+;26-+WP;8F#0I|zf6p{rpA2_j)0C_6nTH8D57xFjVf zGYLgZKfjyq1_}od25G}0&4A4XAoZ|-fXRmx zm8OEs0ApBCfb9i~fCyLwg_fi_=cl9=HF!0;6+4iZ39Lnv>-Z2iW-z@Px;FfuYSGBALQ0&zhYYC4#L1uiISQ(uD$dV?D#x8)L8%2ZpE81kK^UYTBejD(2=XjQ5|Z}uq-Bu8 zQU(Tw7laEfkh$P24YL_!0SLp=9K_f3D=0vABV$X*~;ushhK&{+j1_p+D1_p*H z3=9mb85kIjF)%PZWnf@<$-uzynt_19%*)dUl^XiRMad8bX3Jg=TvI?bBN{J; zdWL!+ufiq33|MMK$u`g?FlH@}Dw2~~T*3`D1+9&s50Zg3aME%TOG;AnV6o81#K6!2 zO$691g~hL5eqJifaX}0W3}FnA#?CZG28LOTkTU-s69dB|CI}lPk-#|MRwT^k_e=~7 zU!bb-*&Ljon+kOxLj(f@Lk!erkQYF$KahC@Dl7EDA7&h=O;QVOC|+e^V7Lo)4r;i9 z+EcJ1uo&D609gqN=(CIr440uCOg*sR%FKf|eH|GY7+jzlIv5!kx}j`L%`gk|ld@8i zOQ6aaCNnWG%!In0fS+OU4Q=$oO!&*dz`z1+B4FAIi|wTR{2Z7Lc_s!1W#a6F`54r| z0EG_&1H%#~28QKKkR}PtPXt_!9$PR|GoamG5TBosfk7Oa>`p+NARq_BQW`kV!R&*y z&Y)gk*vrJga2OiViy0UgRzmxbC|W^wfPCPY2M_x`1_p+S*wn%T4OB0K+yK&dpMinl zF;p?O#0_&Aq&o|9nim5DLjW{+w=*y>^g>O*9Z{h6JxsR*0|SE$RJRNxq+bGa56Ddb D_t#4J literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6d6576c22dd78e5df1032fc59bc04d4aa9353771 GIT binary patch literal 277 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox_group.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@checkbox_group.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..35559d8d3067af91d23b0b01dd4ffee73979a6d2 GIT binary patch literal 497 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 UV0gyMz#z!Sz#t68&?LYB0LJK^Jpcdz literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@file.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@file.cache new file mode 100644 index 0000000000000000000000000000000000000000..d5b748ff97c0a42a90fc037387f1858aafd05a03 GIT binary patch literal 6131 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@media_gallery.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@media_gallery.cache new file mode 100644 index 0000000000000000000000000000000000000000..75bf44898d8ff82ef1b69bc5dec724dd267d3197 GIT binary patch literal 9379 zcmb1PfPnO({9OIy{M>^4ywtoB{oK@)%*6Qg#GIVeqDqho77)R}zyMOm1ZDF;XrLpy4@iAA(#z9 zF)%}Ezswvci-D1W0gOR>EYb|v3`N$1EDzHUG7E%Za-e8b0trB)6e;RU^U_L-a#B;` zA!#79BsEtMsxlNR1&(l7c!K2^S-}(o16UK3&A`WTqelp)9Py;*(ii0`nM1FN_V+0}DD(vLYte!qlUrAqIFN z0;RcP26#e8ju4QoAls2`4Jj&31@XZc=61KloMMD1ER;h_(wy^CQVa4k5n%x`60DVh zfdQ79f>Mi1b4p+qO)-MVPleJT9WXf$&gBVoR# zESO=IK&*s>kT4SigA6EW7#P4A7EKLFfg{Vrz~BouYfuLcEN-x7F?b%sCJTyqY`U2s zbraa@&~OCBC#0T&6+EE4SPY6-Ek*{0C?cD4Aobv44Q4jTTo8si137`Ah*9270r?hW z6-p%x57Ej1g%*vu!U zGKNJaN~;K)ASf*%i!(DYf`mXAWH+pwM>JVMAr4Dh@D@6j1~Mpw3K$s}_L7>RU{-^y z2Vt1&Kq-k9K?_TH;EY0>Rx`N1W?*1I?^uAmhm2w23U&(2-^fi?xMG+jC{V~v4~z^9 zu-t*%WI4jZz;G67J}9!$)DTtifFd8{)^jWj(55(~;sKclk^^CoJPbpv0}lxh;~iL> zVJj_QW-`FiE;uwvX|;e{2{HxdDwIq_t+o{`nZsO(n!`ZWW6FR;kueeJ1xL;VnSwCpTwiV5nwh zU|7z~z_5#%f#C=<1H%hu28K7x3=F0$3=H`!3=D-V3=Bmq3=D@^7#KjU4^XRtg_VIp zhn0armz9AbfR%xvf|Y@xot1&1o0Wl~hn0ch8Y=_Cbyfz3H>?Z{|5zCq_}Lg31lbrE zKm#tIF%ma628J#+28M1n28Qiy3=DtS7#Mii85jiE85jiF85sQ885jcC85oM$85mmG z85pLqGce3xXJDAg&cN`Boq+)~ilxTEz~IEez~I8czyRt9G;%O7%;8{QmYx&!^axT94l^7<1uHH=?J=kk44^IsXe=4TcVl5-@B+0s85kIvIT#q)IUt=ieD34} zS<1k`0CQ)2T4ria3e-^y)0r6xLEaWTwGb}S4Gu2AjQnHU)EK_!TZI+(XXg=Rd`Fc&EF z`Is3Pq?jQi)q9y37)~-lyhnVZg*h17je+^MgpGlr8rsgpOvO+MhGOt+4@}Q{BU21|>e5xtL@ zf#DD{#P^Ks3=FL75OxhK149Ee(oH!S7%ZWQsh@*^VKS7BFG68eCOF)=W7k`evLjWd|Bpiwt7Mo9GEXJTM@4UK*c4h9AjB0L8RR8Uz3 zi^UyG3=Dfna33rIK~pBo7!@W425lyY)9{55G|CykWe3b(zRU~^K~PRI69YpA)IE!s z7#NmA*{CTDag-2%r$JWoKY$fy#YjWnlOVwUYEag672nW(J0Gs4G!j z11jlZ2`4`(D>WIOTyb-Wc z09@`todb<~24yw|27NY&S;trz7%o9`I5AFxRj=?dPpJERI2ag0I3Q-i0+$%`U=anX zTA{JYkPcnl015_uW(EdhXvt*8#=u|&t&Vs(7#IYhnY0QzRtk!>5Jm=u2u4Whg)is8 zVhTFF0t*@oCI$w3C`X-{fk6-C1_s1zIdY*2b1r1UAF7Zcje&syH1!TL#F~+T!4c}1 zIgAVpi=b>`0tOa=$R!snuq2_2Q$Th)FflOrKufMSEDQ`kpmo1E2V^!B6e!RX%m8*h zNE0a1JQ1a;6C(qIE7WZ$YGGc4j30uG0_j}L$iT1?svH*AV59XxSr_I=(0m-sE)xa@ z1`B9i^B3emXo|CkPAY;-CcZ?$xs(HD-6{qKhTTx>QaBhGN}&m*hLM4x7wT{nf5O}h r8uA3W1?0;ej0_BW@F@m$oOkCB04A0q?90Y(Of zgNzIeLQD({!b}VdvP=vN0Za@GJxmM?=a?86elamH{9$5X_{+q=z|PFTV9w0IP|eK1 zu$-BJVHYz4!x3f%h8N5X3~!hj7))6h81h*d7z$Y!7>ZaJ7!I>AFdSiFU^vIZz`(-F zz@Wp*z@W>@z!1R7z)-=;z|hXhz|hUgz|h0Wz;KO~f#EtU1H&6u28MsE3=I5i3=D#7 z3=BeS3=A@C3=D2;3=Cat3=G|D3=G@Z7#RMtF);A3GcX9SGcX9UGcfqGGcW|OGcXjh zGcdHWGcZhJXJDAY&cHB}oq^#MI|IXOb_NDD4h9A%4h9An4h9BS4hDus4hDuf91INe KpcoX6APfLue^qz@ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@mentionable_select.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@mentionable_select.cache new file mode 100644 index 0000000000000000000000000000000000000000..100f6ad066629592d7596c0d35a1a2290a3d30cc GIT binary patch literal 11303 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 YVDM#QV3^9rz_62zfnhfkgTf4i0kNSu761SM literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@role_select.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@role_select.cache new file mode 100644 index 0000000000000000000000000000000000000000..70ce56c523ac3b809cd4c7f1506c86dae5c9d0d9 GIT binary patch literal 12367 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@section.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@section.cache new file mode 100644 index 0000000000000000000000000000000000000000..2d7df7d9d4175bc22aa915242293ca638e5e4efd GIT binary patch literal 13553 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 bU2xpa&%iJPn&Z5f7#O0V)`8u^z`y_iY;$ie literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@section.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@section.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..7a9c5ed74e89a36160430a62686715cf93d3fed5 GIT binary patch literal 899 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@string_select.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@string_select.cache new file mode 100644 index 0000000000000000000000000000000000000000..64795903af4bb226624badf43634a8215d5bba0e GIT binary patch literal 16023 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 CE`SOE literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_display.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_display.cache new file mode 100644 index 0000000000000000000000000000000000000000..bea8a1fd6dc54c3d4cf907c242d8c86f1b32b714 GIT binary patch literal 4594 zcmb1UfPnO({9OIy{M>^4ywtoB{gTv*lK7O&;)0ySN{|8;5W&E}08+*XWphL6kkpD2 z7lhKY3=9kp85kIdG#P3Lm_oLN2<2P(7>`-QMeqKo`$YfabIOXS~ zgd@x%n0!c4X)07N0|U$|x5S)cgeWWkLQB$|^HWj_@-smeKr|tH7v{F0)Z)^d5|~9( zj36qfLTQi=m>i7u&jyKt)PY1m7^aH>;V9RlqWmIsbud#I7#Ltaf(3A1YB|V65C)lu zB+Ljh1%yFzu#hXx$S;QmDG|vTrrVK)fuR^=Gb7kgkbx-46)b>6z(N?D6kq{{7MU>R zAY(ynP(CKYNm!CMNH-Y6JXw+-pH-Zn2MQ0yRZI*Fn?WoF1_ls?riPd@0Tv2dm>3wI zg3MrG1cw8N36cY0u>DYiiavsx$iM|Oh!1xsi$ zF@Uov$a5f5U^FDl!a@(40YN^0#=yWJ%t%z>3Q`VEH!wp%7Jx7;??JprzXAzlH!>cW zbj!%V01MZY)a3k>)FM#$FlI6`Fu+OzkN}z*Vp1;5Z^euZ42vM44^FutaWEIeK*HGK zK?EufjvM`y%Dlwf%w&CtLt##GfjAr8NLY%bLe>T+8<0z|q*#zJ3{%lcP?&>^h0%~4 zfL#Pzz@doIs78e+9Ha~hav>~Ypu!*_WK2{#hs7q$|7d2yq|tB8&_SVvGz7;*1Oo z8H@}JpgaxAj7u3A7(OsEFlaC_FoZHOFoZKPFhnphFsx)^U;x!vpsMBt69a<)GXsMl zGXsM$GXq03GXp~wGXp~&GXq0DGXuj$W(I~$%nS@CnHd;vGcz!}VP;@>$IQU+o|%Ec zfrWtqRB4y7FfdGJVPKdF#ZYI1-3(&uXXNLm>ZfJor={g4=H=;wa;koDQ8I*qz45IF zE(t)o!59|7hI%G?24E3n0+t`KWd?jD3ug6(Dw2~~Tp|Fq2%kBiN)DtfGp_^|kFOXR z7~VoTsD^@SQCMpwKPf9U8LsCkGXukGs98ibQlKgs@>0v87BP6RFfe$tKpOr;m;lRF z(54K`l-*1W42PH?reF(FSa1jD=cd9k6{rDT0d4)5Gczz)Gc$loQ+&b30SYq)1_nq! zATbAK|1CxahDT5i3BG{T7O={Up^A}#p^*{d3w*ACC9Sl~)SMKktqdGY3=F(bzu^l4 zzx=#ZSa5*aLq*UaILpYuaFG!b1o6xa49U>oXklSs=wN|}<4zXE;2sIgEt8lT7-m2t zn~31RQg=Z$Gk{uA9~mG)V#&n7;KBrPNd*f7Lo?J>_=1WLEyCha!fXd41H&<>qw$#n zOZ=$5RA6FYP=kgzX2`&NkeLVX46!jYFmOV}1DP2ZLZEDXR>Kl}acOdLYB8vc1pCO4 ziGjfatKFay0H!G|C$Xd?H4o}ThRe(h47Z?m!n}m(a9H91wU?nbFqkkfFjznn%0p;# zA7m;lC4!4SsL2eTh%)6b$g$8wg)LFSTmk7x!)!}qU|;}sTS5G93=9l^7$A{OOgN!6 zx%wCx7^Z?;O5899mXrt!j%(1t5|A&aF)}bLgnH{D69dB?XsXPB_6I>0q67`hpOC&D b$X}pW_)z7g% literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_display.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_display.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..137d6dd2d6509c3f05614d4e6e32b1463933d8a9 GIT binary patch literal 341 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_input.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@text_input.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f9b28c5674d836ba17c8b11ab1ed1c82a68fadeb GIT binary patch literal 681 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 Z3=Da!3=9id85l0HGB8|$Vvv797yxS)HlP3i literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@user_select.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@user_select.cache new file mode 100644 index 0000000000000000000000000000000000000000..b6d72af434fd9ee714bf8b5979e533f26318ccab GIT binary patch literal 12367 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@user_select.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@component@user_select.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..da9548ebba75e62ca4fb36d9abea612767530fe6 GIT binary patch literal 669 zcmZQd`;p1Oz`(${H`kJZ1;k=tU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&_2KTn2%fx!|(CMUDF#EOA|!JC1Bfd@rTenClQex45l14AVP1B36G)%@2u zgzwZr)qp6Fi6FfoHB}4@4Al$_3}+Y^7|t>vqt zz|hRdz%YZ6fnhx(1H)!U28OMS3=G>C85qtnGBCVkWMFvB$iVQ1k%57miGe|hiGe|% ziGjh5iGd-9iGiVjiGiVriGiV*iGkq=69dChCI*J{ObiSR%nS_P%nS_e%nS^hnHd<4 zF*7h+U}j+W!py+%gPDOrorQtHmW6@AorQs+iG_iojfH{XEDHmJ2rC0a5-S5kJu3r4 z2P*@^3|0n)RjdpQ`&k(nF0e8%JY;2H_{_?{@Pn0s;TJ0d!*5mw239r(1~)bah7vXg zh8b)O471o67-q9EFuY-7V0g>M!0?@ofkBgk28NH^3=A4P3=CRO42pXY1^^UvVM_o2 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@emoji.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@emoji.cache new file mode 100644 index 0000000000000000000000000000000000000000..cad031aeb6e518f3fbbbb9649efca3660f623511 GIT binary patch literal 8895 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 JHnfgp007Q&iy8m` literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@emoji.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@emoji.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..0ffa3b2a641e96e088a6e1ad5a3b3521f3a0ddfb GIT binary patch literal 702 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@entitlement.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@entitlement.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..4d1810bf8ddb032cfa15c2f4446d1d2dd0872522 GIT binary patch literal 1203 zcmZQd`;p1Oz`(${H`kJZ3&dhzU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%Cm9$Ro-i=L z)k4%{l#~=aWnf_V#=yWJgrXZHqhFL-SejZ~@|}T!L4XlmZ&q=Bo**LwgB~LT0}qPc z{DP9q{5*X|28J*UxuVqK(wvfTMh1o~MsydKWag&ogNWji#N2{xMh1rIj0_AcFtdyD zb7wFzFf3+7a%)k3u6|}-NorAEVvc@MYH`UDMh1qxj0_A?NV*UzK!(Q`r6n618(Z#U zWMH_@$iVQm$mw9>yA9JWLv?^Ce+CAI5C#T@bqov)Cm0wQPBJhsfWiqB5+FZ={08y^ z$c-R3fouht_kfXs;UOafgAo%0gE12WgBKG6LnIReLnadgLoE{n!%QXyhHXp?49A%m z816DLF#Km?V322KV6bIoU`S(TU}$A#VCZCKVCZIMVCZ3HV3@1~D+7ZMD+9wMRtAP$tPBh%Ss55EvobJzXJugc&C0-_&&I%z z!N$PQ$;QAii;aQdI2!}QX*LFiS8NOnKiL=<)YusqwAdLKg4r1ulGzy;a@iReCbBay zOl4y7?QXd7)rSr7%I6L7}~iR7&^Hb7`nI_7;bYjFx=s0V0gjJ!0?}&fkBps zfx(K0fgzEHfuWv`1_l;h1_mx(1_pUv1_otb1_l*g1_l#e28J+R z28Kpn28L!{28I@128JoT3=FGz85mCTGB7;jWnf_CV_;C>V_?wdV_@*$V_=BnV_+!Y zV_@jwV_;au$G~uakAdMj9|OZ(J_d$Qd<+cV_!tY;b&kt%+J7ZfuDil5kCV1mjDBUnE(Srt^fl=i2wsbsQ?4R zWB~?-c>)X!+XWaHP6#kCunICTa0@apSPL>R1PC%Pga|S)gbFe+qzW=Hv!0;bbhzK(Lm>X0LNSK#_fdQ)lAW0a8X#^RDp%^BK zY$`+}D}-XerUK+95C$nhmS+YDV8Jl6(sNQ1bJ?H@f=h}r^U|Rl1_nl$L3xR};6Q-O zfEYxXzyngrz`&54pI4HaR}x=RSwO%f4yX>N%o4|8KS$k8x4810`85(TLPiGVOn7Xt$W%u%jIMfpYO z>R_faFuZY$GN3sfl=wk<(S$)l$QZ?F+&;n<-q^%oMxz*u%f+xDfN6pm2;vJcFfjNrFfar_ zF-Q!AH9!Od1A~4>er~FMT1I|aT5e)qo<1l)=oc3yLl~gSQV*PQp~?}(l!2b1o&j7G zodI(a-pUZB2voL+L8BQ~is11$EJkD*7#NhH1)?zn1A`@$jiMDjDqwcHFfcIqKs6BM ONRT2>TL9)jkdFZjTG^uj literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@file.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@file.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..cb5147e8dff3ce434809e8e43f65a1928982c37e GIT binary patch literal 65 xcmZQd`;p1Oz`(GyyTlqqgYn6wZU2^c9r0oT^B{x(0|SE}0|P?<6hlNA7y!Nq3EThx literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway.cache new file mode 100644 index 0000000000000000000000000000000000000000..5612d311c577c207991e54c7f74628d05d6b93e0 GIT binary patch literal 419205 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

SzgZMBt7%c#( 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..573925c4177a1d067cf17ed893468d5c6b8ebaff GIT binary patch literal 16200 zcmcbj_cN1$fq~)s@m>W6BM^&$fk6aHr{|<5=IWW zB<7{-7Zl|urxq8>FfcHIw5@4lJHJSN&c8m-RQHObiV97~1o5a})DY z3YZuenwgN24@i4zZhlr~3ljswB6LTk=9OfYs^b66M{ z`dH9H5^fk+X;E6Tv9YmbKMMoH1{MYeP=N#TT{@zLAB2;R*{5Er@gTQ!>+_SwfPP zfx(*5a@)L_vd{`M6l339~xVR)SJvBZvuec;JFF7@tm4RUrE1DaNOEQzQ zQ;R0EGB9jKx4yVEsW`bP6I`HeV`X4?#EMjMf;>@LoLcmlm4Shk4J|*FoWQ6rhHI&21qP{%70Kv4=T$+#Wtvb1{KAiA{SJyf(lGfc?c@^K;;{#00R|JpaKb0 z?tsb`P#FR$BtV4$DBFXwHYo3caw#Ymg7OzA_kgkpDDi{RHz*B*5-BJdg3=c#<$zKI zDC$8`42nTeB!OZA6x5&~1%(yS@G&q1@-Z-U z@i8!*2E`jc1A{3)149-+1H)W?28JX23=FUN85o2F7#QpY7#MN{7#L;>Ffd#dU|L$vMHm?Ni7+rQi!w0yh%zvgi!v}w7iD0$BFezLnN$`Xm?_CP*+a zOq5_?STDiAa8!bU;hqEo!%qnY22n`{24_hI26ssY22V)_1}{klh9pS_h9XG@hJHx~ zhSib`3`Zmx7*0qsFr1QPU^p$wz#t>Vz#uEdz+fW9z~Cjtz|bJYz%WCKfnl{21H(Qk z28Jh63=Get7#N;QF);j=VqnmaW?;~gW?;~kW?-n1W?-n5W?+~m&A_lmnt@@PGy}s8 zX$A&K83qP#83u-083u;+G7Jo#Wf&NEWf>UMWf>S8Wf>UKWf>UiWf>Tj$}%uqlx1Mx zmt$aXlw)AXmSbR;FUP=eUXFp`qZ|W+usj2UsXPNiq&x#dzB~iNGgMqk> zV_*o>V_=x1$H1^tkAdNe9s>inJ_CcUJ_CcHJ_CcXJ_Cb^J_AFPJ_EyYeFlb=`V0)K z^cfh==rb@p(`R5{Heg^-G+r3+Xf5_ zuMHR&7!4U1Bn%lCj13tW3Jn<;DhwGImKicItTJR^SYyb*u-1@);gTT(!+S#p23{iu z22~>lh5#c5h7cnLhA<-rhHxVWhB6}thF&8EhWSPe3_Fb&7|t3oFg!70VBj=nV8}6M zVCXSsU|4C)z;MKvfuYcZfnlBr1H%au28J&t3=GPq3=Ha~3=9dT3=B!83=GMp3=CbS z3=Ee{85pjZGB8{F3%YuR7w*>=(j3onuoh1W9iX{U>y(I&~ zEK3H49hM9XpDh^}wvaJ{x`mGokR$DPJ9JFF!cxc7I@ZE}m zf!CUW!OWV0A={dPq0O3sq05?qp~sqmq1T#$VYM{_!#!&Th6mOR3=gdt82(!`Fc{e| zFqqgdFqqmfFa+B$Fm&56F!b6mF!b3lFl@GAV7Oqz!0^F_fq~VQfkE1qfkEDufkDxh zfkDZZfg!+_fnlaC1H)`v28KDd3=F4j85ll+5sk&542Gx)TG#4<`l&L1zXAeP;#+UuOn}JZA=m4rd029%lxIK4%7oerE=T z4bBVZaOnCd~{}D;CEqQ5O!f;5OrZ-5OZN*uyJ8vh;m_ID0E?9=yYLVSnk5W zu-b)zVXX@T!#WoRhU+d23_n~L77#Nt{85mgI85r2y85oS+85knm85j!O85la;85owiGcc@jXJA<4&cLwNoq^$w zI|Bo=2Lpq&2Lpq#2LnT(2LnU02LnU82LnT<2LnTv2LnTw2Lr<*4+e%^9t;dOJQx_> zdN44sdonO+crq~Pcrq~Pc``8QdonOYcrq|ldNMFf^kiUI?#aNg$CH8KnkNGTyB7n4 znim5@fENQpp%()~sTTu7xfcUNg%<z|il@z%bF5fnky_1H)Ed28Ij13=GeF85sEd7#Q^Y7#Q6B z7#NcM7#Pz17#K4B7#Onr7#Jq_F)(cOV_-P$$H4H=kAdNj9|Hq}KLdlZKLdlMKLbOs zKLbOaKLbOTKLbObKLf*Ne+Gt={tOI{{TUek`71~4#`2QV;H1~4#G1u!t24q#w76TramB!GcIIFNxsG?0No zERcc0G?0P8Cy;?5K9GT-Igo*2aUcW3$v_5%Z-ER9fbN%a6g!V;bAZX!=qpZ28Iv@2FVZx289p?2Kx{O2FDNv2B#1PhU5?i zhP)64hKV5z3=2aT7;c3yFmQx2FsOzyFgS-YFt~>@FnESCFnEPBFl2=?Fl-BDV0aqJ z!0Uhh-6^!j$~j+j$~kHj$~kHk7QuzjAUTwiez9|63M`DB$9#Q zW+Vf{zeolKsVD{p(worhQHAa47@Q6 z49YPK42dxe3=3iy7|z8oFkFgZV7L;)z;HE&f#Giq1A}EO1A|{I14CRa14C9U14Cad z1H*(^28M~T3=FGd85mB*GBEJRF);YVF);YWF);YYF)%EMV_;Yq$H1^Uj)CEJ90LP) zJOhJQJOe{nJOjhTcm{^8@eB+*;~5xs#WOH4ConLuCNMCtB``1uBrq_(E|Gy@Y9a%}(nJP^m5B@ts}dO)&L=W3JV|6= z_?^hWz?a0pV3EYYV4K9iV4uXm;E=??P?f~Muri5(VP_Hp!;vHgh95}`3_8gS3}wj- z46BkE7&as`Fx*OJU|>mMVBk+-VDL;~V5m!BU}#BUV3?4?z;G&sf#G!u1H;D@1_s4c z1_rHE28OCs28Oy+28M=I28PB|28IQx3=A7n85nM*GBCVMWnfTFV_@(}V_=w-#=vkY zje+4#8Uq7oIs=1wIs=1!Is-#WIs?OobOwg2=?n~t84L_=84L`QG8h>4WH2zC%V1#O z%w%BD&tzaQ&tzbz&17IWoXNnznZ>}Mkj21|l*PcXG>d^@PZk5izbpm@iEIXj#B2tJ z`fLVP|jswh|gtUsLo|z*q+P4a533=BVt7#N(385rV<85rgiGcar~W?=YQ%)lT}!oc8H!oW~c!oaY-gn{91 z2?K*dDFcIBDFZ`iDFegCQU->@r3?(8OBoo%$`}~r$`}|t%NQ7<%NQ8C%NQ8el`$}E zD`Q}|TgJekUe3VaSI)pNqnv?Ze>nrg#c~D)?g|D5g9-))iwXvYgbD_Rq6!9vwG|8u zmn#?;ZdWice5+t!u&ZQXh^k~@SXRlvaHx`jfv<{zL92>^p{$C5p|^^G;d~VX!?P*| z290V428U_}hL&muhB?&?3=gUq7=BbUFxb>EFhtfcFs!IyU^rI8z;L66fq|!%fkCI1 zfx)Dffg!Y(fg!z?fnjzn1H*k$H4Hjj)8%(o`FHUo`E544h9CEP6h^n zP6h_SP6h_|P6mdsP6meLP6md;P6md~P6meQoeT^sIvE&tb}}$r?_^+j+R4E18>GIA zfkCW`fkC2+fkCp1fkC;8fuXdEfnjkM1H+Xr28Nei3=Cho7#LW)85lUb85p>_85n%K z85sP#85jb(85p9v85q`dGcerhW?&HMVPFX8VPNR)VPNR#VPLr4!@zK}hk@Z%4+F!K z9tH;GUIqrcUIqrIUIvDmUIvC0y$lSydl?vx_A)Sh>t$g01(NGyV3^p)z%Z$gfniS{ z1HWl43_3=Dab7#IpBF)$QPVqmDA#K3T9 z5(C4{Nem2kCowRHPiA1Soy@@CHJO1SWHJLo#bgGCn#l|dwUZeb4o+rZI6Rqw;mBkL zhX0cp7&NCaFgQDGUrcQyCcI zr!p|qPGw-|p31;*cq#+K@u>_9tkW16f~PSsR8C`HXq(2sFku=4!?tM*47;W=FmOy~ zVBnn2z@Ramfx&7z1B2^y28N*N3=GNB85nA&Gca^aXJD8$oq=KgbOwfn(-{~RO=ns5zFeJ@nU`UzC zz>qqVfgyh;1H+n`3=GF+GBBK)$-wY;CIf@uECvSYSqu#Jvltj$W-&12&SGGwoyEY= zK8t~2^(+R4^|KfluFYa#cruHD;oU3-2BFyu3}Ukx7#wFaFa*zLV2GW~z%Y3>1H<&$ z3=BtSGceqm&A{+vHUopu90mrlISdT;a~K$W=P)qD%wb^2oWsCSHHU$rZw>>)28PbL3=C`M zGB9kM%fP@mkAZ<{9s`5bJO&1vc?=A;^B5T1=P@vJ&0}C#Igf#1?K}pC+w&M0MCUUw zgv@7PXqwN!FnvA)!@~It4BO^2Fr1ptz`(PBfx&121B1r`28Q+p3=9(&FfdG7z`$^H z0RzMN1q=+=7BDc}TEM`-yO4oFeIWyb;X($6q=gI&X$u(`(ibu?OkK#pFmE9P!`g)m z3h<1H-3<3=CfuGBA8y$iVP_Ap?WMA_j)oMGOpyix?QX7BMi)TExJx zWDx_yiA4+yXBIIqa4lwFuwBf+5Wbj!A#pJSL*HTshDnPV7$z@fVA#Kyf#KX@28R2K z85n*nW?=Zcn1SK{Vg?3=B@7H)OBfiUmM}2XEn#3dwS<8|U?~HG$WjId(WMLw=1UnE zQkOC?tXayy@OUW$gUT`n2Crod3<=8^7`m1*Ficp+!0=!h1B32z28QzG3=FfDGcc@O z&cN_wIRnG1nj)-?yg{9;8@APpuCcS zA!sE7L&Qo3hRBr+42>%p82VQ-Ff3fjz_4j01H{WS~>57sa+yjsJ+puLuX!DB50gU?z9hPJf~45!yJFmSJ9VBlTHz`(bTfk9#& z14HyW28OD23=DPa7#Mb}V_;xj&%ofmo`J!0Jp+T+dIpA&^$ZNN*E2BeThG97XgveN zpY;q3mKzuttT!+)*lb{6aNEGZ(7u6zVaWyth7}ta7#?k4V366!z>u_&fgxoh14HUY z28R5N3=A7KGBBLo$iQ%MBLf5XCI$w(O$-b*n;01CHZd^NZ(?BR+{D0eY7+y)^Gys4 zuQxF;sBUIp$lT1pFlRFZ!@>)RO^_;xTb=? zcQP>4?_^+@vy*{g;Z6pI#XA`omh5C;Shtgb;oVLK2CiKU41Bv77(8|{FihLUz;JmN z1B1YB1_ry`3=ICe85kmWGca`RW?<;s&A_l@Hv_}T-3$zucQY_Z?qOh%-NV3;w1aBdF+!=pV644?KeF#OxYz`(qhfq``|0|VP$1_pt>3=DC5 z85m~lWnehHmw|z49|Hr+J_ZKXeGClz`xqEv_c1U`-^ajkY99jw<9-GP=KTx|Ec+Q4 z`1Ug}#O!Ban6{sR;oN=(295&^3|t2o7`P8GFo+&tUz`$_h00V=-K?Vk|gA5F12N@Xp4>B;!J;=bY<{$&Z#)AwDn-4NDY&poluS_t zFuXp(!0`SE1B1j-1_r~U3=DBc85oj|GBC_J%E0jSCC*27&aVdV0d+$ zfkFQS1B2lS1_q-O3=B3W7#M0!FfhzK!N4&01OvnA6ATQjCm9$_PBJifon&ALImy6K zagu?d<|G3{?MViPQzsc1&YWamID3+T;rdAi2F+6p4C$vB7-pVgV0dtffr0Zh1B2vg z1_tHR3=Hn485q1zGcXjNW?*PK&A`xent@^CX$FR^rx_TooMvEne42ql@C*Zk=otnE zpEC>$6=xV2y3a5$Og+QEu%(747<-TFuXs*z`$^pfx+V}14G+c28N!q3=DJ6 zGB6xD%fRsHECU1MIR*yKa|{fD=NK5m&oMBhonv6AImf`zb&i2y^*IKH-RBq>PM>37 zxP6X+;q5sFhX3an7zEEVFesmAU@$$;z~FwKfg$od14H(C28P=63=I9}85kCxXJFWT zo`K=$c?O2-=NTBDo@ZeAaGrsI?E(V>-vtH+{R<2XCKnhO3NJ7)EV#hHaOeU9!<7pR z42%~U7+5bdFz8%lV6eT&z~FI_fg$oD14G6|28ObW3=Az785kyCWMEi&k%3{yMFxh$ z7a16?Tx4Lld69wP_C*GUI~N%ko?T>Mu(-s)Pi7lml+riTxMYSaG8NY@(Kfk<`o78 z<0}jd;a3dy}`h+?gj(H=Nk+RUvDrle7nKGz;Kg+!RaOg zL&8l4hLoEO3=3~EFxum;x=GzPm2X8Ylyt>W6@ar}M1Irx-28}xm3_5oh z7=rIGFeKh#V9371z%b(u1H+s<3=Eg_)<0ul*!hfs;qEgAhKJ7>7$l!FFjzfjVDNsfnnBL28LB{85p*_Wnj4RmVx2UTLuQTcMJ>~?-&>~-!U*4zhhu1e8<4h z{f>d5{~ZIv@plXitnV2ZoZmArl7z`&6Hfq^080|P_R2L^@>9~c-;ePCcX_kn?d z?IQz&%SQ%=rjHB^t3NU@?Ec8WaP%Vs!^@8h3~xU&FsOZEU{L?Wz!30>fg$b_14I5N z28M=D3=I9B7#QY%VqjSEiGg9+CkBS)pBNZ^d}3hu`H6u+_cH^7?Pmst@Xrhkai19& zGCwmg)P81QSo@iQ;rV9<2B|L$3@Tq37_`4IFc^GcU@-c^z+n7^fg$}114G#t28PvN z7#P-kVPH7?g@J+nD+2@XR|W>YuM7_hQ%-L*X|Dh8f=&7&d)lVEFuvf#LTz1_qw*3=Bfw85o4W zGcc%sXJE+t&cIOeoq?hBI|D=YcLs)z?+gsHzB4dv`_8~{@jC;mFp9~CBeljpD`N_br?4|RQ252b zVE2oGA@~;qL;NoWhP+=446}bRFzoroz`*sJfkEgu1B2CX1_p=U3=EFH85kmdGce5k z&A_nuHv_|x-wX^delsw<{LR21^M`>!_74Mt>mLS&$Uh7WIe!=!3jQ!K6#ijgX#2y! zaO4jI!?iyQ47dI;Fx>gWz;O2u1B29G1_rIa3=EEc85pAfGBD)*Wnk$3%fPVYF9XAh zzYGjJ|1vP_{>#9y=Pv`pv%d@sy#E*&B>yomX#8Vf$oj{?ko}K=VaGoPhMoTy81DUJ zU=aDwz@Yk{fkFE}1B1?g28Q_m3=F;h85p+wXJEMgpMl}=e+Guv{}~um7#JDS7#JDy z85kKBGB7e+W?*DsVq|3CV`O9yV`OBIVPs^GWn^TCW@Kc@XJlk(Vq|1!V`OA#XJlm9 z&B(~GmywZSA0s2fQ$|JxP9{bMQ6@$PRVGFTdnQH(Hzr1gKqf|pR3=7-*-VTKPnZ}P zPQkfYUa+nzz3Yi%hikTT1N|+fLjxsYc9Ajo=;ACNBP-kIeFk)e3NMT`Q zsAOSesAXYfsAFMd=wo4I*vi7l@PLJp;SmcX!($dk1|e2P1{GFD1~pbj26a|O1~*nl z247Z227gvYh5%MZhBj74hIy=v3=3Ep85XiKGW=j=WZ-9GWYAz^WN=|)WJqCSWLU?> z$RNzl$dJy?$WYAA$k5Er$gqT+kzo@%Bf|-HMuyAmj0}(185!QPGcxdSFfurBFfzn* zFfx>JFfvqeFf!C|Ff!C~Ffwf6U}SjA!N{P?$;dE?lab*WCnEzh7bAl`7b8Og7b8O+ z7b8O_7bC+IE=GpMT#O9sxEL9BaxpTzu#jUT#K)bKHy!FS!{RIC&Tu!!15W zh8KK{49xtD3@-eP48{D63_bje3_JK48TRlqGVJANWVp)D$RIDk$dD?)$dD$$$WS4` z$go#{k>R2MBf}*DMuvw1j12Du7#WlV85xuX85yhv85!IJ85!~g85w2?GBPX|WMnuh z$jHDT#K^!Y#K<5Z#K<5h#K_>Bv5F^87Ax4JxLW~UJ z!i)^M!i)@#!i)@2!i)?_!i)^b!i)^Fg&7&v2{SUB7G`94Bh1L~R+y22NraI>LWGe) zPlS;nRfLhDLWGfFvj`)@F%d?F>mrN{uS6IbzKSq1{1ahh_%Fi9pd`x3U@XeW5F*OR zP%p~J&??Hv&?d^ruvnCl;fN?B!xK?PhIgWj44*|A8Dzy68T7;$84Sc284Sf38Jfiy z8Rm#FGT4bTGOQ41WH6LqWbl<>WJr`?WN4INWayD#WH=RKWBg0DxMg~Sn zMh1RKMh0z3Mg|i}Mg~_&MuuETMuvV#Mushtj0_hg85!P6GBSLXWMq(%Vq~zFVq}Ps zVq~b0Vq|ELVq{n^#mKN56eGh&DMp5`Qj84Wr5G80NHH?-OEWU)OEWS!OEWU$ zN;5LlNi#Cck!EDLBF)I~P@0i}NrsVuONNm_L57hbQ-+ZtONNo5NrsVOrVJy)0U1Vy z7cz_tGO~;eHnNNiF|v#d8M2HF#j=bHC9;eRZL*9Et7I7&_R2Cc{E%g2V3cEIV3K2G zP>^F}P?KY1h?iqz=#yh)SS-iLa72!g;kq0ngOEHUgM&OHgO5BTLySBlL#;d`!%BHZ zhU4;#3@_yw8AKHr84MK|8Eh388C(?@8Qc^Y8M+l18D=XmGVD=cWVoZi$iSh<$RMJ~ z$dIJS$dIMT$dIkb$WW`u$S_Nhk>QjgBf}F#Mh0UgMh1H&Mg~tMMuuo5Muv7JMuvGx zj0`K37#U6}F*2|zGcxEXGcuSdGcwpIGcvd+Gcx2WGcv4KW@I?1%*b$0nUR4{g^|He zg^?jbg^?jkg^^*J3M0c>6-I{bDvS&_RTvpWRT&wqRT&vvRT&w4RT&wART&w|RT&w2 zRT&wss4_C#QDtO!pvuVbNR^S{u__}2gBl})f*K=(g&HG6s2U@~Of^P^=W2`$FVz?s zUa2uMFsL&!D5x_sSg12HgsL+#EK+A=xTwy^@I;-Ffl-5zK}my=L0N;5!B&HjAyk8r zp-6*~VUh+T!*UHqh7}r&3|ll989rz*GJMovWZ=Bg0NjMuuyej11p285#Jr7#Xy+7#Wha7#SvLF)}RHVr1B-#mMkii;+QC zn~}jvn~|Yfn~|Yan~|Ycn~`C%HY3AyZAONf+Kdddv>6%JYcn#O*JfmRuFc56qQl6b zti#A)uEWUSqr=G1qQl6rLx+)Jrw${-H62EVPdbbYCc2Ca7P^cKmb#1#iMoso6LlFG zPUtc++}34e;MHSf5Y%I25Yl60P}XB)u+U><2-0I@DA!|TSf42lMf4C)4q3>pTE4DJSu3{3`% z3@Z#68O|FpGCVP0WY9EZWY9HaWY9BYWT-Y|WH@ce$Z*Dxk>Q#lBf||tMuwY)j0~?0 z85y{Z7#XyT7#U2B7#Sjs7#S*!7#S8AF*4jRVq{P@W@Jz?W@NB1W@HF8W@LyqW@N}R zW@N}SW@N}WW@MOQ%*b%Wn33UwF(ZS52_r+e2_r+c2_r+k2_r+H2_r+12_r+B2_wTa z6Gn#fCX5WPOc)usOc@!JOc@!}Oc@z8Oc@z8O&J;DOc@y}Oc@#aOc@#Gm@+bKGi78r zW6H?DYsSc6W5&o(Z^p>5$&8Up$neX8kwMs!k-^Q9ks-&DkzukWBf|x8D2Xu zGQ4qMWUz8%WUz5$WUzH)WQccUWN30^WLWOV$gtXxkztP`Bf~vMMg~DAMh00YMg|8b zMur3@Muto$MusdWMur9_MuyW)j0~5Z7#VIlF*1C0Vq_3;W@ONEW@NB%W@LzUW@M;y zW@K34%*e3HnUP_aGb6(lXGR857e)pR7e7e`Js26JJQx|I zJs24*Js26nJQx|8Js27KJQx{nc`!2E@nB@Q>%qvN2f)^vhdM`$XU0#d~r@R;$u6r>uhN-HBg3-*Mh2-sMh2BYMh4?RMuvhwMux&bMuzEuj0`gZ85vdvGBT_VWMo(q$jERY zkdfg*AR_})5F>+P5F>+25FY6dur!R3;b<5m!;3IRhCgA93@qV{40_>= z3@+h}3?<==3`@cp8J31KGVBUxWOxO|rBg4%IMuvwGj0|j%j0`f7j0`@Jj0`oAj10Arj0}?^85s^mGBSLNWMudi z$;j|0l9AzWBqM`L6eEL66eB}g6eB}p6eGiuC`N{jQH%_SqZk<;L@_cLMl&*4L^Cp2 zMl&+_Ml&*mMKdzwL^CqXiDqOt6V1qQHky&)Q8XiiPz)o3TMQ$ER}3SAPYff2ZwwZ`O*c{8q zuqBp};Y2JW!;4r(2Cg_p2BkPghOjtBhV(c_hO#(DhWT-f413}j8NS3ZGW>~SWMGPC zWMGbGWDtpGWC)07WGIejWLOo?$nZIyk-;c|k)bw$kzr8+Bg35pMurCoj0}$w7#SWX zFf#l~U}TU=WMt4wWMl|TWMn8$WMr6}$jGoTk&$6{A|u1aL`DXoBt{0uBu0joBu0kq zNsJ7Kk{B6IB{4F5N@8UAmc+=wp3KO=nas$*mCVSXoXp7JoXp6On#{;hpUlW`Fqx6z zP%_=%z3-*rhNs#H27X)Tb~q%uHcqxRS!i@FIng z;cp5fgK#P%gL*0>gKH`yLuo1_Lw718!<OZ6BZF85BZG4WBST3BBg2*qMusmL zj0`53j0{%85!d97YDMTtM+*ip>L@UEDVL8XL|A*O_pVM+-j!ODVBZF8aBZG4#BST3gBg4WO1PBZGP)BSTpu zBg3jjMuy9cj0`s$85!<2GBVt6WMp{I$jI=yk&!{TiIG98iIKs#iIHJq6C=aJCPoIy zW=00%W=4jFW=4i>&5R6}ni(0cH8V0?Z)Rlp*38Jj)56GL+``BZ)56G*)WXP++``Dv z+``DPpoNiPUkf9{>lQ`^lU7CsuU1BegjPm|g{_PXS6UevSlbvG%-R?k^4l00dfONo zX16gi>}q3VxYNeS@T`rIfvKI5!Lyx_p|_op;c`18gFpu(gH8t{gG&b^Ls|zT!~70L zhIJi`47)lQ8IE)?GMwpPWMJrIWMJuJWMJ!LWMJ=PWU%jKWbp4~WXSDgWLVtE$gsSV zkzr*gBg3jrMur2Oj0_Jt85x+m7#UQ%7#S?P7#YI57#Z@r7#Ui-7#X&9F)}>uVq|#M z#mMlxi;+RQn~@>Dn~@>8n~@>4n~@={n~`B@HzUL6ZbpW$-HZ(1x)~XSdKejudKei( zdKekXdl(t!^e{54?qOs&*~7@d(aXqS)62+E-pk0axR;S(buS~s#$HB-O}&f^$9fqV zF84AryzXUWVCiFIkm_S(@akh^sOn>6sP1EAn9#?_Fu#wH;YuGP!{0te2IhW72J?PK z2J3!C2HSo{2D^SnhQ5AAhPnNW49EK!89wzhGJNT0Wcb?8$lx}Ck->ceBSYE*MuxHp zj0~+47#U_yU}U&7fsx_S1V)C>6Brr3PGDsCHi40WXCfnm+(bqOvx$rhsS_C)8YVI_ z?4HQTuzw;W!@-G+42LE%GTfNR$iO>^k%4~_BZI&sMh4YMj0}#G7#W%-F*2N;#K`by z5+lR=NsJ6!lNlLICNna)PiAC@pUlXRFqx5|e=;M(qREU5`zJFpJebVL@NhCC!`sP> z3>;G!8KkE$GFVMvWQdu<$WStck)d-6BSZfbMurJf7#X%rVPrTyg^}U<6h?+mQy3XU zr!q3gOl4%Soyy1%JC%_keJUeE&QwN*1ydOrHcn+^crcZb;n7q^hR0JG8Ca$5L4{(-|28rZX}`PiJIE zp3cZnKAn-FcRC}((&>y0yQVWT+@8+J@MAh7gUJj=2FDqU3_dd$8A4_-GGxzSWSBLB zkzw`>MuzJ%7#UvAU}WH*$;eGW?pw$e=o#k->R3BZKE`MuwEx zj0_uRGcsJ6&B$W|BZJReMuynAj0}^Y7`D3+0Njo37V3L7>8A|(Q=0I5tAg6*bh>t~@0lTpv9ikvD3=D7;?xmSI zDImW=#CaeT16bHMH8&}>2qDRjBAK6(T9jClnV$z0Wnd71vcQ^MD+-EIi;FY!^AH9I zA}as|Z#u*TgbH3{72&B#8Tt9y2w4=@dFGX6mZTyiQEU&(&rD7YE=fdIz=v#hKv8OO zYF;vueiW;HQ;Ule(~)Fh;Rf~_R5mCzF&Pv92sa2JTLD!SQdy9hmkyO+V1T*8C9^0s zxdf^PVS*S`A&NAnMX)4=q6~*kP&=IS;prJ{hX|NtfJAk0az<)OX-;a2YguYu3Bozj zP(_ZVCHcPa7dR!d6%? zqWBI~2`qm(<>#a13MQyEF!_+8(p0Dr0|P8axh3WlBSc}TDzqfcIX@+}AU_jQHh{E& zvJ8j@VOXXLN-ZwUDS=ru#Rw$Fz%Ug`gLJ^;V6=ZWNEDh`Kk%7UDPzZBF z-Ai!@bEAeZmWl)9V3ZI>Rr7*@f#CFT?;V zl9Dn@isK;-;9^is#3;eWz@P{%kwF636v7i7mhvB#vX$5v82rKJFfuYi>oF(=Q-VUn zD&zE=)Wlp^f$o!8TmsVs3P+HGP;@~#D4IbELE@f>>IcLHW0=7phr_Z2h)q-_#=rop zb&!iQjMf;)enJXm7#SE02}L&-*vVjmB6l-#!8;9!IVjB|hEyg7h9V}sjR25$KyFCP z$pN{dl!<{sodppTprEg0VqmBT#SJ*V8K8|wP)!cfh@<8Q83vLD1wYAA!N`Ep=>hu> z!*Y5zBS1mX%*4RZ4+;uczJVqcl&%v#Im&{C5i?kDSk}nEz|c#mo`U5)iVGY@aASKi z69dB{P~Z^gIuekkEQpveg9x{M6Brm6<`W7fkdHtqnc`4l0*BHvCI*IWATv=~8K~_z zuyuswDGMiN%y7bEA-x-xpuDz=iGkrXD4<}KIykR^Iv(f)2?P~T7E~;lL50u8tqcqd z#|R}Vn2RV*R4m{`b)JcV;SMOQP`ZQYLn?SBVJ;%Cz-Gk^BV4wfWME*pMksth2>_f6 zDD9-Mg2U$l69dB=Q23w}muQ1Z_~a-H9X8C+!C~1=1_p+wgaQXv6;hmd*ua7F0TeKx zb^uBNfZFc?#|0sI$^wZUGm!9DNbimqC_%C?GcX8&0t(hw0+&b_gNyiODGMYH%s|3v z-ERg422Mh)P!4d+f(c4p1_~lEW(EdjP!OTye(2Buyvc}Hk}_v-Vg?W{+vq(+1_~c_ zW(EcuQ1}q)LV#n0C`FWo7Z+xD5wexuy&F)tIWRLY1cAa0)=36uJ!rZ@9STf3lv;o%nS@Epx}ZP4k!ad@O+40ma<^t!3-vx*3o;I6%<4n%nS^5pdcb{ z5{5Wclm!_tW{?rIHj0sfp@2{!4=VvFZZPnI8w^d%3=C62K?m#Dqogj>+(?8H$^wlK zGtltcN$=56P-2_O%)qc56k@O}04=%Drs?p@QWj49nBj!ex=uz0hUtV76|7rLaiZb} zC#u!V3=I20K}5_{B{6C!3oZf7;38n@EJg-~b%X*9)}p02&;-DNc9@xg;T|Z^h#Nx( zH!4U_N?8yJVg?~`cGG*PACyBMF*7jy1cfBw!F+Hi5~+-`5EQ};LBjUZd#(W#dViT2 z82CVbMHv`@7IG+a`2-b!OaX;3dE*emm?4JG#+Qr?48I8FSy**Kah??h=UE{Z1_l+7 zFJY}ll-d&|un4OFnL~bPiC~5nUMv4HGBEJtpHC2FfEAMz2N#k-ATJ`Vp1?Va1RBXe z8r7z3RRaS91AJ}^>HshWO7I{CPFH{=L71Y*VSvxB<4zx-@@h~;7%a-by)zukj6jYi zppM>EDZU~C6p`QwD13_OU5?-wJ%qazhdjMU7hw%daB{#HCj%t}oGR!&ctx)?P>FJe6;I3X}VR_fwbLhq?X zSjQqUCnvr%ub?QiEHfuHJr%T=8SG$it*FVuz~Ib+cRc_ub3k>O8w&%&5mrRS1gZ$V zSQr=rK$5UY32D5z_!v6ZYKQ$Q-oOHxB% z{(Axo1H)X9FJU8{;6yb#|INS%ng3qM!oaW=6h0_zfzkPIa7Nq6!oYA46gVg)(&+p* zQi;S44ydCn3=Ee*0fmywprbtSDj%;TNH_T<(IB7Py~e`8@EYVpLNlnK5Fl=~DabUC zYVu0%Au_r9o`nHaP$3$Y;L;nKu283wV51$lRZtdULt%24m6d@(7}WNLb)HcMh~W7U zzbs|JG}tG1#aS5`bU{Hxu(J-zTnxmiqAbXU%H*ygD+7ZIDCl6r-YBUHH8&EWgt9;z z5|g_gtPBh>pb#T4stHbN#HgVx%!bCyWdbV$Lp3PSh?^1whaCw@DGS0OHFH_d%D^xU z6q1Ce2En07q%z7vaLCMD&SGU?SPcq2lwLNp?SV2GM^FJ}AvOeNF4wa%FdPJh6|5D8 zQZ=9i7GV{Xh1TfIC1h70YVxR74fM<pPh7~B~d z7$O)M7z!B~7}^;b7-ldsFsx@}U^vUj!0?=rf#EkJ1A_n)1A`D~%?1+#LkbfEcu#0K z69Ypd69dCUCI*HjObiS=m>3w&GBGgRV`5-<$Hc(E#LU1T!py*+%FMuE$IQSG!py*s z#>~Lbz|6ofotc4QB{KuVL1qSq2h0o%znK{r1Xvgt)L0l8G*}oIKyyqUEDQ`mEDQ{( zEDQ{lEDQ{NEDQ|uSr{1BvoJ6mW?^8s!otAtmW6?VnU#S-l$C)&pOt~Zjg^5Rj+KF- zmX(2F1}g)@T2=;z!>kMpM_CydjVAHjIHRh7F#B*m&%LRW;zDmtvR&uwA$y3#!-{ z7#i3h7Qi+QgA9eu5rWuwEI=8XhR$Pyj0FV&Y_b}}U(UwBuo-F%Y(p*qqfth7k>;9U zzJTp-1Q{>D&cL7m4JO!TO_1@hi7OBrk1ue{?!jz=%?pEUy2Zx8@Dl0~*ydS~>9C1J z5F3w8u&N0hA@J1|AcH|623tS@;>&U{Flci?A_lgJiGblKkpZ13fcXKoj09x#W_AXK z{m{^Y?Xm+I4V!TQvGMo;R(gZ|0Ih}LTkc`@!8V+L>}%&>V3-5-3T!hC$PU=pD~OH9 zKKvs+FbiQTI6xMfa56A>aYDilwr2oj32cZ7#KvPGO74Vu5T1EpJCH#31aUAh#6vv> z+eHO38#d?;Vq+8!pvnUj*-4ot<(b8)@x>XLX(jPFsc9v!fK6ayU?_rS-Ci~ZhWSwT z0X7DPOHlSVHU)b#c0<{ZIT#qeLfMj>3=G;(KZSELFl0j6lQV-wuD7#Jd<>^3e2h8a-yel7-vi%>S6XhA6ukfwTIDFC*;5EL`d*clkU zL6bOauOuioVDm;GHXf@$l{_epkdrvLuLQFTws;I=*Fr7^hD}_M)COBr3o;+pO$D*> z*aee9>6k&r>|nORwm*PuE8=8eXoaS2*rpDU4X`ml5F2JBmYRo%@fet`uqE9fTjjYJ z7_6Xvge_nP*#sLB0)caZt8g#aKn z9=kwBfWja#CkLbw#LS1D{{fQ1qZ3w&Ag55&L<8Hy1~TXf2Lr=3Xh8+rmItyBHj4vd z+ndYYNf?G6dH91o78$GBE6h zh7fF{5y&z;hQneUB?F)oO0cEgAPa7BF)+M=1_*5RI>>_c(D5J;8>9~8Fg%t*Ya0eo zKFEU~sh0~qZWCmHI~xOoFSJ1f8fO9-2yP0)q6zuLAZs=T277FZK_-E8dgj4T7jj}_ MU~q-11xbN00Q}ONsQ>@~ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway@intent.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@gateway@intent.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d36593ec06b9cd8e0bec56c5bc9c427552bd239c GIT binary patch literal 490 zcmZQd`;p1Oz`*eJY_l~3BZ$Soz`zNm({oZ2bM-UxN4 z3=A493=Enq3=AGD3=Bam3=F9(3=EYl3=DlN3=H#G7#P;GFfbfuVPLqz!ocvBg@J*Y zm4QK&m4QK@m4U&Hm4P9Sm4Ts_m4RUfD+9w?RtAQ{tPBiCSs56Pu`)1-voSD8urV+w zu`w_Nu`w`&u`w_-voSDiV`E@=!^Xg%%+A0N&CbBk&(6Sbh@FAqCp!a!9tQ(M5(fjr zd=3VNn;Z-b{G1F7zMKpU9h?jd`#2dGesMA|*m5y2)NnB{Y~^BLc+bVa@R^H&;VTqF Hqlp0kTM0n- literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild.cache new file mode 100644 index 0000000000000000000000000000000000000000..b012428841cc41f5236bce8b7ce9cf637df502f4 GIT binary patch literal 194798 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 HTZIAudYdwN literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@audit_log.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@audit_log.cache new file mode 100644 index 0000000000000000000000000000000000000000..154d7e58addcfd53030fded51af449956802fd8c GIT binary patch literal 67051 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 F9RT_%?qL7` literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@auto_moderation.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@auto_moderation.cache new file mode 100644 index 0000000000000000000000000000000000000000..2205e3f60d4ae4608d2659ac5bd39c98899d5b25 GIT binary patch literal 43874 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 S7#P@D7#KvL$p};;fG_}ZsuiLD literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@auto_moderation.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@auto_moderation.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..721ba1901ab9c645ccfc2034ccd542708da591bf GIT binary patch literal 2469 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@integration.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@integration.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d70079680c9d30d2feb93021b623683fa78d4c95 GIT binary patch literal 1124 zcmZQh`;p1Oz`$U?gNcEG6U1U*U=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&uHq(Fv&fkBsnfq@4_CcmI0Ge1v{fq@|uL#`;bxHP9EjDdjx7#J2YFfa%qiRowNm82HsCFbZCr52YgW?*30 z!@$5Gg;fQ}@c5#%WMgAv%e@Q?4EGoiA&_2_pQ~S5oLY3Bfq~&S0|UcsITglvv6J{t zK#c-XAP<8)2J!&NfglHgtOOYb()Wjff#EL$1A{Lk1A`wU14Ak!149WT149cV1H&{% z28QK~3=Df285nLeGBEsLWMEKaVqoxNVqhp?VqoZDVqjRr#K3TbiGkrc69WS)GXsM* zGXsM!GXp~)GXp~~GXp~iGXp~kGXp~|GXq08GXp~-GXuj^W(J1o%nS@Om>C#WGcz!p zXJ%ly$jrcSiJ5`nB{KuV4`v1i4i*LmDHa9>OBM!(AQlFOOcn-)Towj~d=>_V0u}~_ zE*1uc=`0Kk^H>-d7O*fdEM#F|_{hS*@QH_RhAF%Z410JP7!L3 z3=DAs3=HuC3=Ek93=Hc87#OY#FfhCjU|`@8WMI$|WMD`TWMD`UWMD`WWMD`aWMJ4K z$iT2ukb&WZAOpjFK?a5&f(#6zLJSP%LJSOHLJSP?LJSOLLJSP;LJSNig%}vl3NbLe W7GhxdE5yJcCd|Mf0mYy^0>S{an5@_U literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@onboarding.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@onboarding.cache new file mode 100644 index 0000000000000000000000000000000000000000..1ecf8c3b2b887f32ff1d26ebf8b01b7bad2c8a6b GIT binary patch literal 29643 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 HtTY4w>>K-> literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@role.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@role.cache new file mode 100644 index 0000000000000000000000000000000000000000..c137dcb45188afd68a0c2c170eff57f7d77c74b1 GIT binary patch literal 39348 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�Ak}E`hi!2 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@scheduled_event.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@scheduled_event.cache new file mode 100644 index 0000000000000000000000000000000000000000..6598b3bc3e2336236ae22b3b427207df54b55eb4 GIT binary patch literal 41169 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 X6+8?K_0aOlhL?fC8C2VW{0qeZI(>rT literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@scheduled_event@recurrence_rule.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild@scheduled_event@recurrence_rule.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..3eef4f236c50d3d2fd9d21985bdc8eceef887603 GIT binary patch literal 1283 zcmZQh`;p1Oz`$U?gNcEG4a8z#U=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&_2KTn2%fx!|(CMUDF#EOA|!JC1Bfd@rTenClQex45l14AW-oh6yMsrn$I zxFj*Rpo)QkVHpDhgA`1EQGTv|W?o5ZQC?yWSXq2gTC%aRvE_0G28I(13=CZXpM2tF zwrpczglb`6U;w!fLkY;3HkYQwCFk@t3@L*(Mh+|}6sA6Pb zn8L`wu$+;B;T$6a!w*IV25BY+1_vevh7=|Sh8iXYh6W}Eh9)KkhGr%Ph8au@3=5eU z7*;beFl=FBVA#XNz_5>rfnh%r1H&aI28P>A3=B`17#Kb>F);jLVqjopW?&FtW?+zH zW?+zJW?+zEW?(R2W?%?pW?%?rW?%?mW?)EVW?;x?W?-mdW?*PyW?<-IW?-1W%)l_6 znSo&*GXukNW(J1M%nS^BnHd;PFf%Y*W@ccx%FMuUjhTVr1v3M~CuRl)4i*LmB^Cw- zQx*n>7#0SGHWmhkl`ISl=U5mRF0n8$Tw!5gxXQx7pvKC;pw7y`V93hAkjBcuu$YyB z;VmlzgFYJrLmC?cLkAlJ!z?xihVyI;442s$7{0SHFmSUoFgUR@Fu1WZFr=|FFqE+~ zFsx)}U|7q}z;KbBf#E4T1A{OJ1A{mR1A`R@1A{LI14A_j14BIr1H(ZM28Kr*3=Ho$ z7#MzVFfed%GBAj9GB9XxGB8+kGB9{?GBCt)GBD(GGB8wgGB7l7GB7l8GB7lAGBEUV zGB6zHWMKHf$-uzK#lWD*#lYai#lVon#lXqgB3pm zgAG3egDpP;Lmxi_Lq9(Q!%Th#hLijZ3>*Rs44eWC41od+3_$`641EF&4E+KO47&vw z7?=bZ7z70w7(@jb7{mk_7~BLI7!m{-7?ub!Fsu?}VAv_hz_3@4fnlE@1H&^x1_oUr z1_m1;1_lQq1_nnV28Kc*28KyO3=CI=7#MB~F)+LmVqkbD#K7<#q(+#5AxM~kAz7G# zAyt@xAx)Tpp+J~{VWltw!yaJL(KL>U
U+oMHv|SMHv{Th%zut6J=nSF3P~LP?Uk;oG1gsT~P*xccKgo z3}Orn{9+6Ya$*b&+F}e0mSPMH?qUoK;bII7Sz-(fHDU}5{bCFZi^LchmWeSiY!PE% K*apS0k^unKJiRvn literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild_member.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@guild_member.cache new file mode 100644 index 0000000000000000000000000000000000000000..d7ba29a32feb9f6cfa15b288d0fe2b0e9c3cde85 GIT binary patch literal 36838 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 E0N*BTL;wH) literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@image.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@image.cache new file mode 100644 index 0000000000000000000000000000000000000000..fb69aa239745d05e9a9de0a7d88bd14da00413ef GIT binary patch literal 3826 zcmd;LfPnO({9OIa+{E-$5T6A^FfcHHM46y$n3zjqNg_z7gMooz4g&)Nb`wAK$o zWE!Swm@Ld(m@WneZYa|^Kd&S;uOy_h0PMJp3=9lMsbDb<SIYGN+TzriI%nMlrMV1QfYl%J2{JoK~?QdA0#BCx42v)vMNiV>nbU^xbc(2_Lg z{FKy!{LH)(s1O6PD`AcaN-ZwUDS=ru#R#HyDwGE4fXTsV|7?&bNF7K7gkicE7#LuV zaxE&#FG5!bGnIjXfgPkD$>*RzhA9U{23p`QWME)ei#1JxHGv2ah8l`O!CXWj3cz|` zwi2I{7!h76$&b$}&d&n{2;)H}28L5076StVh=O?+ss>A@hB^N<69dCLkQN3;Mn*=6 zZjc-ZgWU)vNOvk!BT|V1N|azX!%Gd2h(5>{Fyp<7^Yh>u;etpx5IM4-Ir9_)1H(D2 zkp)tWVH)MK~h^AILTa28Ohv{M`7Y%#!%T zqN2n~s316tVOPNj3Q7>AQj|VcxHfC*)aVEj0_C@ASKYe z2{IGJg~@~X$QWiHxai@4$~a|~I3l|pDojB-B+>*}@S~&&nA7ouKFD~OUXr5@q#2B1 zDF)2JLJ*RNxd7yPP?*DvfQe&w8B7+W3#J23BO3z}gz;fCNDLXHL?^8NM61kokTi%h zFff=hFff=gFfg<;Fff2xbn_V)7}hZ`Fl=C8U;s6=PB1VqoM&KQxXQr5aGQaF;VuIM z!#xHD27N{b22gTyXJlZAXJlY#V`N~M#K^#~nUR6v3?l=>V@3vsUyKY4{}>qGBGgxWMW|8U}j*D zWM*KHhGLl0HK7dsjQreG{j`kyw6xsBygYqS5bGBgB|{kCOr!@++EDd~+TT#mP|pA^ z3S+?18a#u+Ya4#360m9d@azp!2&2Ic0NV>oE$*2h&w_QsN_&vhb4CV+SBwnc1`noY z93=(V6b1%!Mg|5~Mo7yNQ!mJLkivkxbeMNwr5{M@0wV*%Wt;+5=3`?MFOlQDM0@b)McfhJ$kjdK_85nk>y8|BFu(VqYZivE?yAU%2 zg9J!3QZo)5A+U50ZfL?(Ycnx07(m^JBl>tj{$pTZfYglfsd>ryDc}+oBreXxz@P$k zI;Q_%1v5&$3er~2#K6$N1c{x0ObiU{%#h&1Gy<*31#|Z$CI*I^Py=8dgVA8$!U6}9 zv7s(xm^#4|E5v@tR; zOk!kU*v!blaE6hA;V~lv!!Je#hJTC<3`R^048}|h49QFk45>^E3~5XZ3f#Eb01H(rq28N$Z3=AC13=ERY3=Gmx401CF0|1kjC%^yz literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@interaction.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@interaction.cache new file mode 100644 index 0000000000000000000000000000000000000000..9110383a4e966154955b2498c0518ca9daac9692 GIT binary patch literal 210051 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 XVPJ4)VPNoKVPJ@4VPJ@cVwl?j7eO*g literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@rest.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@rest.cache new file mode 100644 index 0000000000000000000000000000000000000000..8a12d54fe1bce3ec4bb4eee81a4376470b0ccf41 GIT binary patch literal 9334 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 SLfx^SnStRLlnts9K^OqB8rq)# literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@time_duration.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@time_duration.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..bb19641918e0c2e32c41ebd3f1bceda686ef7749 GIT binary patch literal 481 zcmZQh`;p1Oz`(GnOM-!c1;k=tU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_o{r zgMonoq9!dTKe0rHfq_Affq{VwMJlT}KTn^5fx(A?fk7BWrX({rRX?S)D6u3nKhKwe zfuWp%fniR**9kLquK6H7R5b(0V2}|Yu?hwThDrtohEog-45t|w7_KrfFbFa-Fjz4% zFk~|_Fyt~aFyt{ZFjO!yFzjSxV0g~Rz#z-Sz#z}Wz@Wgyz@Wv%z>velz%ZSOfnf#{ z1H&RF28PW{3=Aik7#Qv{F)-X`Vqkc{#K7AFq~syV0gsB!0>^Ef#DMi1H)$)28O>Z3=Af$3=HYU}DTr0l&-~ zD2suSfdPy`d@Ry%txoy*D0-Mc8WW zJYZKaGBSc`L_C1xVHg%@={c#1x$(s%DLI)*B2Y=Nn0`uSUSe)$vOdI}Fc-N%Tn#lD zs)2!l3(5j(jR&P2RKq|b;3QRCl9*e-2~{1EnVSk0fbtm_7-064 z!0?obf#Eq6!>k7v1q=)f`WgATsrqRd`DtmniFtYYpi)7mtfzt*&q+xm4 zP|r}$0A@U#h6M&{DndP}Rj3xc#UFfgPQ<>$ti=4DpI7pErY=cT~yyvNAE z@E&R>p3sH67*+QPCI*J{P}kw}Ff6u9GE(!PmNBR^GB9XEIr#L#tWHbJ%qcBOh3a59 z!^psJ8OkACdcq8WmO4;<424V#3>8ca44?{(ctaosCcG+m#=yYvmH}c2W|GC8QtmM^ zFg(E;aH#PIOSuJ!MaA&i8s_>CMh1pNC!k>wefq@&Ev|E@M7Yz*pm>4ruz%Mfg z%3@$-U;twfAB!|xt5beHiXJAA1_lPWd`MAgDpZJpffdSfOUx-oi1I*1Lrc<}^HWj_ z@-y>Fph9pbfcR`6f`Ne{D7CmWrvzrv6eEc6R45H{I7|*k`)7khLFzyvAPm#RfN+#+ zQBi&ox;mJt3=9mgP>=(e!@$6hR+OI`Uz(R$5uclxlapDTnw+1PQXHR>nw+1KS_HC$ zF&3l@svIOhoK^;6qZDLvIs*g4Y7m2gk&zMVdME`Jg%PkoPR~hA%#ANDNy*7f5`jvB z#q?7u^AdA2ll37UV28@NK)i!)Bo|Z~tQDNlPz?hmJ#Zofr$0`p>X6LbRImV)&%nSS z31uQe6I(zkF)}b{L)`!hNP@aZ3PT-61_nq98Jb}T3;L3L%=8EfEym@H3=A8Pf)Aet zqCyU87Q;qH28Nel4r<7Og`hDDqNtW!K!zbJ`WMKHg z$iN`Z#K2(8#K7Rf#K7PN#V~8Ep$z?u{M=Oiw2b_;wA{qJJbh5sY9K=%0|P@3v_vPy zk-3QlFsf?A118!Sr{<(KBA!1UNLF)%no^$-(` zFt5OTja-ex%-q4qz;Ff{g0M6T(+BYeYP@6Abg(c9WME*3VqjnZxo;r@1H&q4`zn!< zfgy_#($>1c$iVOr%7*z6YzfR}&%6?t!=6D)UJxJEL8x&8%cszK9ArHx$Y(MzFf0Hu S7#J8L85tN7K;}UCAPNA_vD3W( literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@time_timestamp.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@internal@time_timestamp.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..cc07cde109a8b96ee6bd8a45c8a32ce3b964011e GIT binary patch literal 240 zcmZQh`;p1Oz`(GnOM-!c8N^~>U=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_o{r zgMonoq9!dTKe0rHfq_8}LwiYPZmK?rC@x9NEzoCRV2Ea5UmPBbF(3-0 zA0!{cz`zg-GMa&bVI2bl!yN_&26;vX21P~&1|>!Y1|3EQhCD_Fh9!&)49gf97?v|K kFl=OGV0gpG!0>~SfkB*!fx(!Gfx(4|fx!)mLFR)n0D=o8wg3PC literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@invite.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@invite.cache new file mode 100644 index 0000000000000000000000000000000000000000..5a0e96fc52b6723298e13c37eddec8922f39d0b4 GIT binary patch literal 52754 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 VC^9gFLGx0h3IjtstfLN%G62|%pe_Ia literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..320916ffde0ae3ff7069baf4136e3eece8d70f3a GIT binary patch literal 7512 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@activity.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@activity.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..5f7f3de6286d13ff459063373d415cf1dc52f863 GIT binary patch literal 241 zcmZQh`;p1Oz`(GnOM-!c3B+PxU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mAw zgMonoq9(tfBr`uxhJk^>nSp^}R;Bm1#ow=OQ-G=gQ6M=N1_lOK1_p*{3=9m@85kH= zGcYh*VPIfjWMp8_VPs&4VPs&4V`N~6XJla5z{tR`k&%I6HzNbXD@FzeDJBL6XC?-Q zU?v8JSSALBMkWS^Rwf39HYNs!sZ0zE$CwxxelsyJ=rJ=eq%kuv%wuL?Sj5c0ump-h HZUA8b9giia literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@allowed_mentions.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@allowed_mentions.cache new file mode 100644 index 0000000000000000000000000000000000000000..8c8194e6b32c9e3ec45854bf4ad6b0dd415f372a GIT binary patch literal 10061 zcmb1TfPnO({9OIq)Z*gA^i=)CoSgjf)Rg$#)Vz|+{Jdh25*84_z`y`f$OL8cLup5d zdS4{vXBik6o-!~n5N$rxC@_U=7f}Y_aR}HzFaa|)q_Q9tB+$*sz%YZ6UQR@}9pnNC zW`j@+*iE{=K z5*{pM{J}h!C7{#?V?zxBQ?QUo2bl`9(kHXH1g02WBTNk_NfVXKU>aa42y86_10&4l zlF9<4m_hN;!1)sDNCucA!NCIy+2E3*%)E4%5>R5`gmQ{NsVqJ-1(BwPwtq@N`D38` zs`56|gZizX?2vL{? zp(Sa~`6;Od`I&hoAR|B+WF&|NVOTNsbcwl)3=E5iEwVudfE)LQM7#OOcF$faCrjWFjMl}lq!wRs8jEsy>uRtki z_(Ld|P2fTkT84{2q!_?r`YDxpiMg4{`VjZBL*-l`u17Z#Y$K@RE6UHMpvHmOQj#B^ zRh*v(3KGVC>Fvk#`T?vH*EaibS19C)nb1*PWffh9&uVGV2T5M0{U|`q_Hj&D)4GR>s z_7WuAP$HC+D2AnMNRES)5Zp`*3?iV=U|?VXQD|z23lE697#KvE7#KW2rZ7Ol10;@~ z@i|*-!YB?Mbv!6YPy!H^hLPL(AVm-ilciQg z!U!o4L5AbV>mVr@rnc?yP7N$kpqC9Gi?PXp#6cJ)2TpplO-yL<0gE$unFn$XRzW(t zksiYt@NOAOc@6SCMnMh|1mQsv0wAY=Ff5k9i3WFh1@jI{B?i)uApsHu;i2hIaP5iI zPlNel=;TF=2mv)WKoNo=0TKjZm>0QZbANbb~;az^4$v94rK=m5xOiE|d*tF)+w5Ffdp# zFfjNqFfddxFfddzFfg29U|;~v_`G0XVEE0zz#zrQz+lhFz>vboz|g|Tz|g_Sz|h6W zzyO+Zna#++u$Ym7VKpNI!!||+h8>Iy3_BSa7`T`i7(m@PQ18o|iGiVziGiVxiGiVm ziGg7k69dCOCI*HVObiTPnHU)UGchn2Ff%ZiFf%ZiGBYs5Gcz#cFf%ZeF*7hMXJ%kn z&CI}XgPDQh4KoA7H)aL~2^Izh85RZxSr!I{JQfCq0u}}a(4Z1%7-%&M1H*L|28I_b z3=F)i3=BrB3=FZX3=A!-3=AEt3=CbY3=G|@3=DTz85lr=ETFLwWi|!|e>Mh&32Y1u z^Vt{}7PB!hEMa3{xWUH2Ai~bTpv=y|pw7;~pux_-5W~*EFoT_eVL3Yk!)kU0hBfRA z49`GnI2ag=I2ahrI2ahrIT#pHIT#qqIT#o^I2ahZI2af}{d`c*`~U|7!+8z{h9?{h z3_m#-7(_T37$i6u7&JK<7_>PV7<4!p7-~5g7(gQx3pg1VPH{3YoPlCkwAnxz`WgAT zsrqRd`DtmniFtYYpwy*bT$Bu9;F|u^0~exDBM_szhI%G?25?ar1C|c)b%as6{`xtY z#U&`28QQr)RRtRHk$_r)&us8G1_J{FWCkfQ2j-s1ObiUOq16T#GXsMVG|e|MGca^Q z*`PizCsYGIJ7A*=X^ELRrA4W*Je|wRz)%7$!igGWf~sWzk9$MyWq^%wgWSZ%%D}+Q z3K?-KWo2Nfg0f$5FfhF3fQVxT6RgEk44&KpnF9(pB~Atg4QOe~#m>MW0QE&5I|IWM zC>x)rVeU)H&(DGRSdfK*K@=)Ml#gMK$xAJVnbFG0z|g@78M#CC3n*bg9mbHBlUP!c zng`RA&%wY@290WLX$R^ihT#0%R9Gm3W1;x z1H*Bs1TlI+DGKU1hQ&+_49lTDkYZ+Fkb_E0WM*KP3T5LtpH85q7m*;CjU7-m3|p9DJtgA6pu&0%L?SO8_K zb1*PyL-YG4P6mc;(B!g%lYwCml)aCWf#DF8eT0*N;RKXTRMLfpKYYCa)c*{y`701V zn3aJc8d?DT42qFe+EGN>P|j=?28KmYt6)9^w|1an z44}>()Jg_kMg|5!Mo11eU}Ru0fwDsx85km=Y@#9nM@0d%^BQ#R6v$o+CI$v~Xnx>e zV_*=6CcQrByc8(%h_VxAIH-RBV^4vufC5>xn1z91IkZOFz`?+<1)2(l7#SGEpq|4T zLZD$YkSjpxp_Y+>p%JPWmZ8AG32oBB;v2G{2WDIv0|NtSc?2j<*ccfYc%UX|GBPme zL)m_e3=APqb{QiBLk*OTnNVS#15FEpJO*-?2?GOz1+;vYVS~(Vg510lI`ai$!(0P) gFU+BkL2i)4K*pFdGB8*}EskPjU`T+nL2@7r0EuDpbN~PV literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@allowed_mentions.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@allowed_mentions.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e151081cba6ba252201239d8fa53eb155a34fb10 GIT binary patch literal 593 zcmZQh`;p1Oz`)S4MTLQZ1;k=tU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&_2KTn2%fx!|(CMUDF#EOA|!JC1Bfd@rTenClQex45l14AVP1B2bV)hy2U zOQvQ+)qp6Fi6FfoHB}4@4Al$_3}+Y^7|t>vboz|g|T zz|g_Sz|h6Wz|hUez%ZMUfnhNt1H)=Y28L~n3=BIM85njlGB9v4F)(m5F))ZSF)(;D zF)$P|F)-9IF)(y6F)-|6Vqn~JV!NR~G!@|HI%fi5r$HKr+z{0>#$il!- z&BDO2nuUSkItv5C3l;_jURDMMBUT26SXKsx7FGs^4ps(+E>;GHZdL|{JFE;0cUc)2 zp0hGAD6=sz__HxEOkiVRn9s(*u$Ya3VF?=p!wohD1`&1!24!{z26c7@1`T!wh8T7R zh8gS(49nRW7*?}0Fsxx`V0Z>n!@Npt~ T7H~2!oZ@6)I0MC?_yS=74Lens literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@attachment.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@attachment.cache new file mode 100644 index 0000000000000000000000000000000000000000..4f23dd53497909407d3fe352058709060af79209 GIT binary patch literal 15870 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@attachment.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@attachment.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..67ad62af4a2ba841e7c49ec232b4ee511361748e GIT binary patch literal 911 zcmZQh`;p1Oz`)S4MTLQZ6U1U*U=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1Egj$H2 z%)AmA1_lNl1_lN$kO~F{28cvfaekgI0|SE>hD=Uoafvqr149f00|O6=p8SH6%>2Aq z1_p*M1_lOU6uFYj+*JLP(xOC=l5PeDhONl%Ey~Z;&&(@HEy_#G(N9V&PBk;x#=yXE z5!o0NHEB7C>BW~A7#Q9#Fff3;1+pw1MG45pc$kfE85kJ&85tO=4kzDEKkOpY4^j`s zpa1}Q733+9<3X+i*$y%uq)&j6fkBXwfgy>JfgzcZfuWL-fuWs|fnho$1H)QI28I)i z3=A(B85p>k7#I|o7#Iwh7#KX57#I?n7#J#<7#L#$il!-#KOQ(!@|I@mW6@gHVXrTC@TYlH7f&y2P*?Z5Gw;i z87l)r6)OY7GFAqLovaKDhglgIp0hGAyk=!!kYHnA&|za>Fk@q2h+<=4h+|`5n9Rn& zaF~sO;XfM#gBm*ng9$qWgB?2qLp(bJLoz!9!xnZ1hKKA744>E;82+#`FvxK*Feq^_ zFgS5AFa&WhFf?&6Ftl+nFkInaVED+v!0?ZQfx&>2fx(26fgzBSfgy^MfgzKVfuWR> zfuWs~fngRW1H)=g28OMi3=Bs(85k~cGBDiZWMKFRO43{m4E$US4ANW-3<_Kf3`$%K z49Z*#44zyJ48dFs4DDPD3)6L3)tonLq+q zFwFY&oYcfzmDe)kEP!+K7amvp}i6oFyKzd;EAw{LBAU+tw z0?I8hrx+m$^Ji#Dnsa_iYC(QxUI|z^$N?}00|Nuh??I`>r8y-qi>4SsluU)vpfG{S z!D#<%kSItUNCbpox)>N3V2*MvD#|ZHR|hkdfq?;}4P*q&XUORnlq8YGnHU&B%0U>U z1?EHasK*hyAV>UQU|_Ie#FO_xiWooy%o31A$Qb4?a3X*?j%vvnW)sY7U|R-H!i6~x z=5LZqBt`}XcBmmKsmb{%sYRf0W}L*xz%UyW>d*v%riO4)Ifs#f;Rx6OMn*<3%>a%? zs9kUhTezZT;*`p~#N5neeTeg54sn4v7HTrwII_b5;X0TR$mt$rK1`hCAcp5CJlOzb z0S;-96bNGrDHO4RDLUarI(nu@b~83PkUR*}IRC;@2q;;Dtb+v^HYeee1L*}}8ao$9 zB7pe>ge5=(6w5F$Ft{) zCVDW#;F9o`8NR{`RSwk3K(tO!q7IssrJ%-xotT+dl3J9Pm;=@lUzCBwj0?izR?SmD+kir<=jObuuVCaV$g3l?ircE)p z(Fk(_FB1cUFcG?8C2$T>TNTv)U?oB?%;CkQ$;qk3#W1TEGBGf$fNIAU128N7^7B%m z${6ez7#N(O^sX zhQK_I7GsMU85mYELi!*$^}>7)stTd@Fn}6te;6PdS28g$tYv~!ov^5d`4pUYV1kfN z0?ge4pq$GH(T1WNW*>6vjhm5yfgh?EHIPvA4@T=A=HN6228MiSbQv--Fjzqi!xr5z z_d>cIFcZu`ZB(cUT8s<~22c}73b*&rP9~@cy`PbR;V{$?R6n8STv*(pHFmZ$GBE6e J1tv6Y003tZH8AE?bW^SrJh$t>e%q?(XU|=XA|nICBt`~?IgAVp#~B$I#F!Wu+?W^`Vwo5i(wP_- XrZ6!u%wS?*xWL4~a0!Y*c7QMd);Kjs literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@embed.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@embed.cache new file mode 100644 index 0000000000000000000000000000000000000000..1760aa804409a3e93ca7495a3bbcf3d8a26798c6 GIT binary patch literal 42871 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@interaction_metadata.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@interaction_metadata.cache new file mode 100644 index 0000000000000000000000000000000000000000..6ccab3214a9059d45bc25048cd99dcec1986aabb GIT binary patch literal 10558 zcmY#nfPnO({9OIq)Z*gA^i=)Kypq(S#N?99{Ji+w)RM%M#F9jiG8PcQz`y`f%mih_ z#6l_yQb9syObiUpObiSp84S`0!!QFumJn?SOcTr%FvA!agrQ7Ngr9toy!48Jfq{*Y z-p(c9D5#S`PJ=Repmb#a5!HD3ZQg8%=&1D6X3=D3GImHN3HmGP&YH?{!3Cw`6P#eBMX^>-JaxmII8zc(S z4iW)jm@Wne2AE~8MMe2V=;~mmGBCh>f}99I%3=DU_A>-z=B0zI0%IXC$-v-PP>_?E zoCr$u&iT2yiFqkd2?hoMD9aa;J)HA%3-a?)5fQ*32UUP+hGS_-esX?pK~8E3BAB?L z%6;=w5_5t}lX5fRaY-n-f^>qs1i~OTo`l2>lE;E!p^=`GnwSfVvEY&-dVDjZS4zz9oSiKQhO`9+yknR)5)pb|R02y9t=etBMM zQ8CCoFvjM66fyn$0#GW4+2;@D!MshGQ)R#|U|`5E%1qD9OU#KcN-ZwP&nr%ihgL1| zNC^tXQP6lo5hKZ=$ZS17nWyF%iqV&{~c!H^jeAhw41zwclk8we$a;UqI!;S+g z1zyvMgK>}!MNUxHanHdlhGD1@bl!7_~LZO5UG$utL zQVd|9>ZerZCFW)(>qDFibC?Un@#sb(MdoVFD z_>j_ufn{W{Eil7CB?ZyWhiO8qyI?64OH~1KH+Jb6ObiT*Npn8TdZb#Ea_6%UtT{n` zfEBD*YS$Dd1_n^!Omrx+GJw)OI2I7i0m?nXL}CpO@)oSrCfFt@XJTNON?L4z%%{31 znTct8fNaK-)7TjpL3ta5LGFV^2Bgr3WfE|$56bhf)C(=)J469YpcDWw4`xF~=+f-JwmG@&#F7~xR`EK2|AR~!W4N?ihuuK82gP~oNp~$NiHTMA28r;c7mLUQvpa4gi+i}u^&Jtf-q5;4qjcsiYY8X4KoOf z6v!SBrmhdji3QBU2U7&(QV@oD2wWOKt4;>=7CFojpkfkc6tWn|AP|O$kx+EvOb{@) z;8Xyz5QM24-XIe}n5b}p4`R`^A|tN22es!w9wAOONF@kk3(V2V0eRRF6iV1TgeYN0 zQc(*r6EVGmH#5SEDVQ!;u0w6^AR0WN+(>kH0+(T6%V3!T%prmxRecDOnV@uQ#lXOj z#K6E%#lXNYoq>U21_J}bO9lo8&{QQSBLjm1BLjm0BLjmYBLhPqBLhPK7#OB8F)%D) zVqjRt#K5qeiGkq<69WTi2pKf$Ys<{Qkj>1%FrS%$;Sw_g0|yHOgB}Y5gFOoaLj?;1 zLoW*h!xR<++#=rpTNQ5XZ31Tb3C_(sKH1EF9yAo@+t6^tgXoj*^ zurn~Mhq5oRGceqQvbi`I7zCj+9m;GB3|dfwh|bTTYy^r6{DZHsN*y)=3-atOb_RwY z>sTl<~#j$=`VBm;%h$Ko$lD*eog00gtCY3=3`0 z_^KQuWT}ES8v}zK8{yD~rUeGb%w=K@ECl&LMF})j*RwJ(v_si{Ss55up@{}xOz=U2 z1!itMIA_32Kgzd0PJ28Jn6x8Udxc4$~KK#~eHOc;`x z85lC54ryg!VCaTQ$gwgos6ZXX%f`SU4AqXUB!g;U@XOCjg|TB87#I>5AR{<`Sr`~V z3kEqmp7~o3?HCuq9Xxj0cidY>TU+uWFLs1#?HWy1cLYl9G#Tsl@4rGrI2LppFsb<6SHu^*v%mUbK8OVYS>hVN{UtkBQQz%ZK`68O;M$$*hPV6h2W69RG( zC^JuFU|^UEO*-C;3=IB^5JNyKT3DGNYpqIy7j3t;Wp2U5CyUjWH3lGNZoV>28J073=A(B z7#LoG3}a+qP+(+WFkob0aAag)2xMensAgnfSj5P{uz`_*VLKxO!%Id61`Z|$27M+5 z24f}$22&;m1~VoG1~(=K245xyh9o8ihEgU5hG|R;3`>|87?v?HFf3ctbHLOhFg` D74A*P literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@message_reference.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@message_reference.cache new file mode 100644 index 0000000000000000000000000000000000000000..922f90f2628db1ae6128a7ad84999bfff292c86e GIT binary patch literal 9049 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@frame.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@frame.cache new file mode 100644 index 0000000000000000000000000000000000000000..215a6bd9c53c18cacfa93f1633e09917f1020a00 GIT binary patch literal 48923 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 XJe9IBFjPU=+t?Twc0<{qpaNk4n!g%L literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@frame.cache_meta b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@frame.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f5b8ed3f26795c86f66d1161706441360d7d1a96 GIT binary patch literal 3089 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@handler.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@handler.cache new file mode 100644 index 0000000000000000000000000000000000000000..1f1aecb5f72d524f6256612a85e6f184f5b18ea7 GIT binary patch literal 19530 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@stream.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@stream.cache new file mode 100644 index 0000000000000000000000000000000000000000..3a32da10250f67f3f5361c0d5db78bedb66a3e0f GIT binary patch literal 22829 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@stream.cache_meta b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http2@stream.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d90af77ec951a0a2322e9287cf31d33edc5ebc2e GIT binary patch literal 1356 zcmaEAw=9zZ2Kb>25h$IWlbV>TpIVfYn3t|!P?VpXT3ig0vSeUj-~uri7#JXGGD=Dc ztQZ&=k{B2mgfL|Ei&6_qQ;SQI85kJ485kIZv8gC7$j>WI?O|YG*v7!Xz=@(HGp}Sj z0|Ucx4104ji%U*0FfiO?U|`@u(UMus zfcy&b6Ug}>2evUXFq~s#U;tSTGS`-gfx(W6fgzBIfgzHKfuWj-fuV_sfuV(ofuWU& zfng341H&^W28I_*3=A)s7#J9s85r1^85qQv85mTU85ne#85s1L85j(h85kUy85q2n z85pve85o+G85kBZGcX)rW?;C@%)r3H!oa}6!oa}A!oa}I!oZ-y!oXn4!oU#C!oX0^ z!obkS!oYBZg@NHN3j@PL76yjLEDQ`!SQr>SurM%)vobJfvobKaure^ju`)2!u`)1p zvobK8VP#lmZ47HpL3{yE771_lW}1_n7k1_ot51_l#81_m=e z1_pCJ1_mEK28L8V28KpH28NY<3=F&Y7#Pm*F))1QV*uwg8GZ%^dwvFne0~OoS^NwP z3-}orj`K4xT;XS6_{Pt`ASuAW;3>esP$Iyp}!z)&T`z|bYc zz_3w>f#IeQ0|Tot0|UP>1A~Du1A~Py1B0C~14FDZ14Ftn14F+s1H(FD28K<-3=CU@ z85p(-GcbG=W?+yOVPHrRVPHrVVPGg1VPI$yVPKdj!oaXsgn@xilz~A}lz~B8l!3ue Vl!3uWl!3uVl!3tyib44ZgaHNi+cW?G literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http@handler.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@http@handler.cache new file mode 100644 index 0000000000000000000000000000000000000000..815136bd3fe56a818a6c70d223e9790bfd58549f GIT binary patch literal 13194 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 wAOpi3XxKkxXJB{* zH2~&Z%ov6xi^P|PN#L}FS_>9!Vl+>bPnB(Mx85j(q>JD=-FkFMq&|t&(uQ?bP-a~a0Zg}CJrh=KzB*efVA;iD{3XaXJ3=Ah(8NgLJVe`Sm z1q=)fC<869Tz89!f#Eq5#8_zHLZ?{~NfcxyD7N!b%VFA%@-i@-fEtXN0w8m}un_Xg z&x1_lGcYiGXJBCX4IOjgWM*LCWrp|w7Te%72IGLE9%jlRZU%S zmZU;c5X_JpybKJ_p@9PP2iQ}vIP}kkma+^5+zbpwP#>M(W?(o4WykU`FvRmfeB>+0 zz~B${sR=6sgBjF!GuRjyX0buk5RQGAZ!kxwVXo5UVqmc6f+T_MYzz!1p=RSPabRf$ zHk%m_F8)*Ds|^0~F);8#!wZ@#py>(}knl-REJ;g*gMmRD8c#6CW9DaA_&R|mDPcZ| zVParNWP*(9&0u9?v}v337X>ylk+7^R%g4Z=1`Px3!3QcmV8I8@TQIE#f(#6% z&`^L`2ya)w@_$ikadIhq+TaQ^1H&z-M+miVpk86{gf6UtMSd_N14A?;B<;I_Mv0-x z3E!j$tY!n1hVhAMC5Y^+&BMT83N6^70SOH?lnY^)J+;kfsdGprX`*oW$f*NOK0}$u2eqhUw5Kg*uY~))ItFg+nD7oDx$Ki_&2Z zn8L`wFbf)29|RZ}z6n5z0qiLa6ymUQDmSq*DK$PjHMJlTf#I2jnCps5g=CgCoGPb|Z#Sx_>78Q8+lz|aBBrNSZ%4C2sqbb^zC;SAJ3 zLP-^tJ)A+4qcFewFfuR%LjCT~!@v*%O{b`V4=FOBtvZIxyyAk?WKdZQE?h1MFfd$) zhR6YS28QEMbx;Zv>7W)b%quzh=`d!S00ToORA-YQ14An`Om4C>Fx-J^M|B7!6+opJ zps4`n7g0e51{tUkE+PyJ-q1iI9FNKHB{A{f!UN{OOg;vNN@zSb2s1EDfX1ULCj)~i zG}xg+&}0fq9q{TDnm-s|Q!ucYUBJS?umZ|C&%(fP9m-~9WnkciMpFnY149gyjV%md zPAn;^gfX}CGcfFeCXyl+28L3ov76Z#7`8!^KB`fut;68_TzKp7F9QR3ksK&&LH(IB zXu5#92;y6q#eSJNP^%bDb2BiUgSMU`1sND(pe-hIRt5$uX#GE%je%hvv{i#WZa}3u z)FTYw1u2Mn3zpfB3oo!PiHNW=Fi1oF26Yi;u7(91IDLUa6_j^oax*Z@ zgBHs_I2joJLems(=fJ|*Ei(sR+T<`XFcd)D635HHki-iqzkaYXF#LhKi*OkY3q3xPp4obbT`@VZVjWSA$%dyaE4Fr0$ggRNA97I+Mh_06z^^PYi$;VU#` zb}}(A?1wh_@dYx>h2S;KpyU9`x+jwc2NxK;t7>+<|aaVQ*20v&RK~oSk>41te*mx1lNzm1su(Y{=fq`K; zGLy8f#DS&BqPES2xgBS7XGld3uqxTES2~%F)&0yi-A2n3=Air zS^P2&1H*kN+eCfr){^9qLY4%>W5vXjZ_!^cxn)L0k+BQP4cOnTLU4H#AIico`T>par$I f5CelT)L}b>7#I#f**EwY7#_ecuB;3URjdpQeXI-&%UKy1-mx+;n6NQ0C~Ln=Q5LoPo9Loq)C!%BVzhQs^}3^({07(VbbFmMVmFvtloFz5*|FxUw&Fn9?t zFa!xOFq8=}FmwwrFf0*ZV7Mp1!09UyP!?ohP!VKc@DyZVh!tdD zC=p~}XcuH)m?y}<@Ia7(;e{Xr!#hC+hEIYF44(xV82$({F#Ho_VE8Y{z#t{Wz~C&z zzz`p=xz|bbdz_3_|f#I$Y1H&sJ28Pc<3=BfT3=BTP3=F=)3=EmV3=GqS85s5m uGcX(wW?(od%)oF$n1O*sgn_|Agn=PQgn=OhiWxzfgMon|gAKx9VFCcq8|wG~ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@next.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@next.cache new file mode 100644 index 0000000000000000000000000000000000000000..885e5b45f45e825a137669d93f6ae9dbff1c0790 GIT binary patch literal 3195 zcmWe)fPmc0;u8JLypq(Syu=*+ywr*kkT?s7U|?VX$udIOFfqT>iV`M}m_GvpLk

~D}T%)cNuGl*bdVDQTXc@)e6NrNzmgGHKw4J5(9zz~#LT$%$3CV*H9W{0@%k;r-Nkuvq7RD7Dxt!VY(O)E_W>|$}d7!2Q!s{0Um-*`S~bL1{nu3 z8WwgTMWv}AJ{ZG-#Vs+X7$FK%iX6jEh7+~oRmd`-8f-op_(B+sI z7(osKVUQA-G%U%2JPY#}JU13HFfgnk5LgV5z=D|zqCpsz#X)v~Ff6jbDR~IwX^@*i z{-9+NhlCu+CgcJZ#0Oy-+YYhVf?QDn`Io!F+3= zXQ*cY7C|OJsRzVINeIwd9berHORKnBgfJ6PD+Q2Ukh1>-1Ehk)Z2~OO!U|QGmq3MK zB|+Vo)hbLstZoJQ0hZtK`4#34SUUt3W=aeU3|i2};!FkxhK0})80K?uJV3(|mdqe6 zB$x?q3=9lDP!lFGFfh!3ngEM4un91&kQ55@#6<=MhU-w}xPuVpJy3fQrZa+pfguJO Ke30m1U;qFiSG|7# literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@next.cache_meta b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@next.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e7d18c107cf3bdfb6c6be90db4ae2df5af56c1cd GIT binary patch literal 149 zcmaEAw=9zZ2AH4>5h$IWlbV>TpIVfYn3t|!P?VpXT3ig0(q>>_-~llh7#JXG@(W5b z^Ye5V7#MsR7#NsrDm9*d%TC~gssT|TIX?yl27d+yhI|GFhP4a~47(T@7;Z8!Fx-Y> IkU9_s0IyFNUjP6A literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@websocket.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@websocket.cache new file mode 100644 index 0000000000000000000000000000000000000000..a57acddf84dd644a5bba270f4efa0f4759db121d GIT binary patch literal 23167 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 O1_J{I1|ukj)g}O4U18n; literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form.cache b/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form.cache new file mode 100644 index 0000000000000000000000000000000000000000..43c33ab1efb24d986301ab5e7a143c7ce658193e GIT binary patch literal 2836 zcmd;MfPmc6oRZ9f#G;b;wEUu6kQiOdkO4AaGcu*|inD8C3@9n4e)1_ox3eg*~xzf2U5z;s}dW&qg)(gh2w zw4(gn_@dOp($r#jV1d+NlV=1O48kA-V1^f^7J|6gqX?#$lZk;L31ln-BO}NZ5ECQ^ z!XVu+%mrnn=cFd)#%Gk26bK=U=z~P`p{`|z$^=0ik8TuH79Me6BRQa=PMIZ+MMa5~ zP%ah<7T;iP*rOR;7m5~GBJ#;BE`d1>6l5TuqiY25Q8dHs3oa?j%u5GJqhRb|Edo`8 zntk-sGE;L>VE%T?FUobwPa)Oc=dZjz01d639ppJ_yalFr(o#N|eF8F;wE43IPNU z2q*(3JYXpfcTQ7=Y6DSH3=9mQVD@BSVDMsKU;st)dFflNsGBGf; zGcho1W@2FY#>Bwzi;02ZHxmPcATtAlD>DN_F*5_hQf3B*mCOtbtDzX=9uQ6g5ey6r z`WgATsrqRd`DtmniFtYY=|%au`l%I(xdl0?#o!VtJ~OW*wJ0&UBr`v+SU;&WGbcsA zATc>RF+H_dA1yl;7bT+!>4DQI)G|cbZ=h$WX8;$4F<_YoRy>3C>KB2lA7Q8>G#fz~ z3wJ3BO9zBi6sMNNXQU>kq{3??V@3uBTTq^5U|_&wIn1pk`SD4aCGm-%f)}cbVF@Dx z!v-h^rViOpF#nbmRYFxUG%+zSw1P4wep_MX2)Kg7us492fguiRI7+&OaZsw>8fFHD zMrHD`r;?kTF zDFy}xT?PgQVW?bgX--LIL1Ix!d|G}{u0Dv+V_;zLU|?Wqu-jjF^A9(JGE^yu0%-@y zdonOEcrh?A^e`|m%x7R=*vG)YaE*b1;X4BZgFGVxg90N1gBl|PgFPbyLog!)Ln|W# zLnk8x!y!fnh9is&3`ZFm7&w_2804537+jbb7*d%S7}}W_7&bF8FnnWTVEDzv!0?-i hfkBX&fx(rTfuWe0fng~#1H(#Y28Pv83~~<$0|08ID|7$= literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@field.cache b/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@field.cache new file mode 100644 index 0000000000000000000000000000000000000000..723868f228a85f41192198f6e76282d588d4fa91 GIT binary patch literal 4192 zcmWe(fPmc6oRZ9f#G;b;wEUu6{j|)~oD`4@ABbRJU_g~;go<)NX}A2MT&Mh$N)T6q zfq_Ajfq{XL$sn~bjNKM|CSbD}W*P$nE0oCtr9(^7obyvs3-UAbN}xgvFh79!xJ(17 zVFoE+U|?W_(!nJ~nR)3T8K?jX1+zIlCp9q_RSU8@n1>;Tg=dyzgj5!!G9fELViRL1 zBRf<>a(-S(D#DvkQAC*Fa~UHnz@Uc2msA#{GBB_}4ROoNNreRmR0vKH$}lo8fP7&?AghBkf&&p|H^?+( z3=2Pe=@O?66l2ME*b*to`}PbB z450D{pM#k}fdx_wGaqCJGA1RwVERbW1M@T$!k7`Bs6ZtL%rT&lh2M zN^k`pObe{+Knr(JaFsJKFl-{29+@DS8fG8JPGn3q71QdTD>(KHaNC<>+ z2!o_Rn5f`^r9b3W2+a4`CgLm*1wq&A$zz#z`RzyRth=rAxa*fKCMlrk_d zY-V6!*vi1b0LuEO85kIzGcYi)Gcqu!FfuThFfuR%FfuSSFfuS~V`N}B!N|byjFExi zHzNas91{bBITHhe9}@#Z5)%VMCKCfgGZO>DP9_G1(@YEuPnZ}OelRgG{AXfd&|qd@ zh+}48C}3t_n9R(;u#}mBVGlC{!v$srhU?4>47ZsX8167LFzB%`Fc?5F%-OKElzv8j zZmND-Mt)jaZem`ZetJ=Uu6}AoVs1fBYO#JsNl8I`W?o5ZQDSmQW`16=eo|>>PKtg( zVsdt3dTOygYR#%&T$GG11TI(gz%3$}-S9q*p`NLp0bCTufJGBOQ~<13A6~k{Vhdlv zi<%N(Nfgw^Gl8}!bD0=RCc-j1a?J+|y1fhx3@4%H;Rr@p5GCj5B`21^G`?VFV0Z^D zPM{P#w!ra#o^@e9&}4*EaUg?MGB7Y~g!VV^g$c|oSm_TlOA*@b207p_sHXr8Us%k8 z9RhPdBtT(>(tQR7hNn=gWf&P4l%QHo85kI>q2l2T3=FYQb}j=0Lot-y#K6GN0cFo% NU|^UBWkZu80{{ptL@59O literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@field.cache_meta b/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@field.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..83e44fcfc55097f2c73f8474b09dc157435f28af GIT binary patch literal 252 zcmZpYR?A?30Y)f;A4;d^q$cL-CuNqzCl(bYR)R#u7#JAl%U=~Kf#E+B1A_)L14A4$1498b1H)uy28N}~3=Dgi85k}wGca6dW?;C@%)oGm RnSnu%g@M5UilI(s005CmB5VKv literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@form.cache b/examples/http_interactions/build/lsp/erlang/multipart_form/_gleam_artefacts/multipart_form@form.cache new file mode 100644 index 0000000000000000000000000000000000000000..2cdf165220ef389264f06bc156a0119ebc5ddab7 GIT binary patch literal 5958 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/operating_system/_gleam_artefacts/operating_system.cache b/examples/http_interactions/build/lsp/erlang/operating_system/_gleam_artefacts/operating_system.cache new file mode 100644 index 0000000000000000000000000000000000000000..ec03e628c2fa168a850c1c0987902e1b8acdf3cc GIT binary patch literal 1179 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 H-~?d+$8erN literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/platform/_gleam_artefacts/platform.cache_meta b/examples/http_interactions/build/lsp/erlang/platform/_gleam_artefacts/platform.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..40b7618c847f5a963fd746fcff72bfb315867a48 GIT binary patch literal 477 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�~Pz$vCG2P>#3(H>MRb(;#cQ;W37Jx($v?-k@jsz%_!#^+8AK!v^)i znHJtdK=kE7r|IMJIKGj6*!Ua7UW`-%3m|B)z$vV$1V@y^G-1_4LdpWwX&|#`&?SZ? zTGwJ|4-I4wxNU_YbBu$5;S7$xn-c# z@qaQ53`{s~!(d=wge4ROM}?x)^wONfBJgekSpI+yJRy=7ObQ_at0F-*BA;u+$be|} zfe%nab|ySDVKR=a3=Hl#){zMz9Zd^AF%z`WfojbsA$ao%bg*x6X#uiZVa+Fm)FgHW zhM72;eM(5}Y8+Fd)Jg?P@a+Vk(Pr?j59Hx~m>)px71+7~tO`+@gRlfdxO*ue!@v+J z!@$631GPy3`NXIMP&lKRF6qoFUpE=*10YvcnWXPKadh-GCK?Xs(C)5GhF~ zCMTzYhf_gPpb`n5q+v2o7#SE|F=CsqGX(9=5CLff)i8)aQbI26)yc~l|`7<#vgyC=}GxG3b zVv0g;YH@L5I%tC;D8`7K6ULo3nHgYmko&RnQHKSvNeVGDFi7KAw9Cf82pd)a?MTSY zFD_9?EK1JEEJ;l+DJ@DZ)=?-<%uQ8DELKQ>SeTul2U1y_4-!RHnOVZ6keiyASFE4} zjv>%E1Gw1@yN(Oow$oFBrG28RNtlm7CZf!cF|skhiULU63(=TGpXtG?4(1v}%?!)9 zMRp7fJM0)37{5cKP9Z22F*O6Rv{)f8H5nAeMV0Ucv7k8~P-h-?{RF%%$^||RNe_G` zmqJNKVhLzn3+Nmo@Mereuq;GeDJMTayI28qY^f4-;88~*Q!iDIOCdo|KT$skwCgBA z2PBoB13ESdbb42cLIOk_a^`7LsshA~Dd0WpNvR6OrA4V+prxT;`||U0K%)(*P;2wS zONNV53rjPLQW0x`E1|nWaMv-=z=3CTl#b8PZPLQlNDGV)|X!!V=F| zHY~?N9O;vpmkkPB!Op%w`5Cz+ke7!VtFzb`wCRfU8X~ z0|x=B{BcNug>e@8AX(flLbw{Q0U$#dKy67dW0V-s5CCOckON@p6F!`dQxGHp!Z00p zGxcaRgMw=CVC2pw@~8*Mg~ViReELCVzz2B196SW9e#WB&EJdheOJs|lkTI|l5#}t| zYF3aNu!+Hzpn(%V2|F}lH6_Sg5GE$fVQR1z_^@0EUv3LB6-5vv0K$XL$>969(aRsO zAjr8OJZPK@b0sX9zycWTT9_n?AjtV3JZQXtEfr%E8#E4v1?Lb-*|3xVig*x4E-lfA z_F%ah(P+XH!Kojn6s7>24&yUZU}1(X4sr}KhN%M=U$8`tyb7679;#fy9kkG2!P5n_ zg%LEV2O5f1FIE8CsK*5}C=+y78_XaSL8v)!3MPr$Y9FXt1y7Kn&|CdrXJ&z~hc3;_ zEG$h`$V^GiE6GgDOf6CX_4i?}1MP=}u_4Ps^tfQ7oOz%=5%@q(kR_<1AQ5Ct*robV zsfP!jcTK7}QiaMc5(pLG=n~P!=>y zqX04&GLDg%2RXPI?g4l_9klVq7OEQzQD|M4VgJn>6S?HQ6C|D_gbSZ$=XXz+_ zS0?CyLn#%qpc-mbW`16=9^6T$y2c=lU^@uu1l?wXq}52*0HoEwBqOzmNUdB7ZuvzD zsTGO21v#mp3!fE2^D-;Ij`YdQE3E*HMuB6#41CLKewu=@v7v>90z3_HDI}symw~l{ zA`CfF6N`)UlQR=R3t~W9&=o-8qyY*QuxfC?z*95$93x0DfmA^6Sj#U`02kTCAUA@h zku!@Gk`s$lHTAe)7v&_DWag&E7pErY=cN?G#&@ww!t(%>K{zW!KvgP)WTYzOB!UK1 zK~o;Z3LrZ{X%=cN=unqr$N_z!ex4BusRjAT844N(23BCC5E|kPx|hgM!7DMZG_j~s z!O+s&KogO9i!-ZIp;o~gy~tuPIl?Z6O>#kU2FMJMGm|PyQi~BmmJ4z|EXZKRJ1odh zBw-fAXu=MNgDOP?S#EwxW?E)4*b(o zAsHB>q5>yUJuX3XY;dY4%$V$Q4EH^VRzeoY17c&2tlwXp;r2wuEK_yu#_~;@% zaD*kK73&q3B$gzA;s~q)vKs+(0bD{#YFPr<$b=#gQ-=$5d}=ww6`%{1!Se`7nI+|! z#iEpmY!k5MOY=_kpg89jvLG(AUPv3FFlo7MF2GF z;qxa*328KK4T7{al&cq57CsrdhCs&vtq}-I^Jv1bWDlbWCx7DFhA?vm`yd9a8UT;I48&*;taUReMp0k| zBrG+2gpPrL_@D)%>I@7F77PpwUJMKjpdJorH(4J814BOp1H*F$28P!R3=B4m3=BPt z3=A6>85p)OGBBKEWMFv2$iVQLk%2*&iGe|eiGjh0iGjh2iGjhFiGd-UiGd-5iGiVj ziGg7v69dC+CI*J5ObiSH%nS@N%nS_X%nS?;%nS_C%nS@E%nS_u%nS@Om>C$>F*7i1 zV`gArW?^8kW?^7(VPRlMW?^8+W?^7xW?^9HVPRmH%fi60n1zAiGz$a66&41Dzbp(4 ztgH+S3aktanyd^A_N)vH9;^%uNvsSEd8`Z!ZLACoQ&|}p*0VA&>|kYJxXH@E@R*f> z;U_Bt0}~qqgA5x3gDM*XgA*GAgAW@6LoOQw1E}xQ&&I$ogN=bqtmbB5ILFPv@ROT?L5+uj!Iy`DA)kkVp__++VG|Dn!+9PChSxj{3_QFH z40gN>4Dq}S40XH=4D)#z7|!xCFnr`?U=ZYEV9@7dUsFfcd>Ffe2aFfhy(U|_f*z`*cT zfPq0;kb%KTkbxmpkb$8|kbz-`AOph#K?VjvAqECfAqEC-FXFkF^mU=WpNU~rRWU#H3o(=Y77iF)fgB=)fpIM)fpJP)fpI?)fpI0 zs53B}RA*qgtIoinrNO{ZslmXoMuUL?bfB`TCIf?-CIdsNCIiD{O$LUmnhXrjH5nK{ zr!SajF)%o5F))N`F)-w6F)*}iF)-}bVqiF>#lUb$i-F;W76Zc_Ee3{rS_};LwHO%W zwHX-nv>6zzwHX-twHX+uYcnu_4pm&G&A@O_n}Ok;HUq;SZ3YH*9R>y^9R>zR9R>zx z9R>y$9R`M69R`LP9R`L@9R`M@It&bFbr=}_=`b*Wwhh`MhpyG#taOgJv@EJ3=I9o3=C(D85k}ZGcdd~W?+yuVPJ?gVPKeM!oV=g zgn?nD2?N7669$IICJYR(O&AyyO&J(Kcf*&NGB9+TGB7MQWnkEE%D`~Il!4)oDFcJ0 z83TjA83RLz83RMI83RLs83V&~GX{qFW(*9M%orFzE8Y~%85qpW85q3H85q*d85lCm z85sJ_85pjbGcepVXJB|?&cN{1oPoi{f`P%sf`P%qf`OsVf`MU=1p@<{B?AMyB?E)6 zB?Ci`B?H3*O9qDdmJAGQEEyQ?STZm?v1DL)VadQCZpFahXT`wKWyQeIZN7!)7#R3$85nGB85lBb85nYG85r_x85r_y85lO(GBErE$=NY5fR>Q%wqszpZ^yv! z(2jxOksSkrggpa8s67M2RC@-7Y4!{ZPwW{OUfMG-Fgq|X*f=mSbU83EfbRCxab#eK zbYx(tc4T0fwFdnj-_lYexo#8Yc#ZRwo9ANlpw5_na6Q9yu{EJaJ-RccWfG#?2abaL+bzxxG;ljYcOA1j2i=kjT-|)z8eF>DmMm(&u$D1U)&fNMBNz}GTa#$mbx=A zymDt?VDMmIF!Eqv2=riJ2=-uL2=QQGSm43HaN2``;jae+1BWLAgQ_P3Lx?8>L#rnP zLz^c9!!Az-hI^h24DUP{7___?7!ti07z(`@7^ZnKFwFB}VA$-%!0^nAfkEDzfx*w4 zfg#MBfnlCE1H(RV28Q$A3=Ge_85k;k7#Lc77#Jq|FfcsyVPJUf!@%&yhk@an4+Fyw z9|ne>J`4=zz6=bY8)g>yGB9lMWneh&%fRr|mw`ddkAXqOkAXqWkAXqmkAb1WkAVSn zital<1_n8Q1_m{M1_mF028I}a28K9)28MWl28NCP3=Dhx85j=uGcfQ3Ffh0TFfcR) zFfeQkU|_f%z`$@PfPvvx00V#K7<@h=D;gn1R7Mn1LZVn1P`un1Nw+FayKsU7#Mm(7#LQBFfi;6VPLox!oct;gn@xClz~Aglz~Aclz{L@+Qs01c%^GB7Ad zGB7wsGB9*UGBET;GBET-GB8|=WMH@z$-wX;l7ZoCBm;wL6a#~96azz66axe3q|!4{ z3=D6g7#NtN85rcF85k6z85q2x85sPc85ltOMo&gFFg%N9U=WI7U=WF6U=WRAU;v#; z)fmIT0K%YlJP3o@?jTw}BR@A)KP@9aEiE@OFHb+cC_h&pIvrN5pHWg$5Dz-*y(lpm zbeMjzeo|>>PKtg(Vsdt3dTOygVxC>UxF{Kks|TKm2H60{u+E@?o}r!rSOl4X^+;hO z)?mH*nRz8B%O><8JEdWZjv$JXGE3qULANu)ChsA_Ihn;JsHPRCmcV*?pviX73Sm^! zi&BeAb4oz{8IaRp#vwciYRH3RK*zV26v4VhKiL=<{MkD!hJGIiyVHq*q0b4z#`Kdk)FePHrFD4Pgk2E|iZeGs1_p1ONgbB4Kqu|x7gU0;)qwfI$%TQz&jn)f24@C_ zJ)mpgv933SJ0dADB@L$Cgo%N{k_n=HJvRfx7H(WY0E=(XF*mTx0y{1rH$sciIPU^w6haWQZVP56qWMB~Cgy{JV${#rMGF*QyC`rQfE3q;#Xs|-`U*u(AxXz2q@yMAp zB@-Uund}S<1?&(5oOl@+JaHNTiz@IALYQk{+Xz8{%@WMOzzGdV<6s5`3n;rfn1P`I z%03*-z;FV}jtyd9NWvM|u&4#CkANlUXUYr=uaqH<#SCH4&=n{uAQuB7HdVoNPw-`6 zSl|oM4f71RCV_FlyR=}%(0mmJh9xSH&^(~Rz;G1GKBL0Ga1qL$sl~uB4|IGUs2^m@ zz+hqvQGeZ=f#Hrfggx7bfnfo($hqUl!0-@Sq|R_+V3>=u_<_Y1^aK}JaDz^2c>ql= zPXrklUPH6zU2g`4Cr}F)`YSiE6z`p-vH|imU zB*+b*Sm;t`V3?o|3DOEJ28KFl1tb9)76V-{h1D!RkX8l;22lGF(N=`{`LG28!$k{- znJ+vT7`{P!VI2Mp3}XI}DANpJU@(W)NZ67O%zY(Am9WzNi3bD2a}S8k3q2VamUu$g z9$pL#-k|AL1_p+Ip#HZvL|n*&fk70i-qn$T!NU;TQB0cou&K26lglxM~0c zgGK;^eJp^1;Utvp6Ue~e9|#f0%pEWh@ET?C!Je>SSgFUruvrfh41#J54AN>4uY1}u zFa$$ePc>c)3~eAcV-03lb|_9Q!Iss}M=&tli-1@Fa|WWp01GxqRtI-6VF~4mD+9xQ zSBOT`gq4YCrWBXv!qk5bWnlOQKDi!SriFDKARDt0$pMyW0*x6M5{)6YF?lmE2tiAG zOeez%I5hvFju>o>VqiE08sKAKU~rCPV2Fr>1S_U-l8`uK01r5T#*)AVCt`RBUam|I zV_;Yl2617W3HFiBWB=6HE0M8R?d75Vqo|KzLSxGK`EGlK^vM4J%Sk+ z0-@}JU!$BzfMlb`zLns^5L$H$AHM1D07kZY3f#EU>#P!L% z3=Ek#3nf^#O$LoQz|W_EWi=Uh1_mv6h&gK=85nj$i=p4X3=Et&ivn06qlYahyukqv zO9nEb3=F2B5aW|085nAC#v3e9GmF8g7G^-G6$3+z6~us7HVh0OK^M_r%{Qfnh(it~2IiV6ep5u!6;xUw%nw zNty*LJuTp5U|7xx@%tV@28P4X-qunT28K1zK>%1#gR3=|&HmZ2vdc+@fx!i;v|fdQ zp%KdNQej}|fwFzI7#RGawN}3t1H(ipTgaAyK?GXND%&zJs6yHAd>9x$K)d*CfeZ{B z&>A*9kbxl+%AOg>z%U!i#tbjm*dc864kIV2dowUtKufuB4+e$|4@fAOcrY-yLMuZ| zV`0Gv>h?lUw*aLA7d8e4Z)iNo3ooCzOh zB&3M|I!^}{m#efG7`8wI_LC+9!yiqEj{^M}7^0z7I!rgB3PKL3qSVBaR8ZvrG8Puo z5%vrWnV?Bf1_p+`b_@*XK8R3O))8rXP0d708)C_!t=e;Y_1&{V9c|`S4*G zOJ)WJduSK)IzI!$U1)m{wcyXpD}g0Q*bq-*9%!8vD0&!HOEWNRgN}Hg31VQl4y^{- zI9s+{Zcd057UaDoT=yD`5ev%gMlC0xj{s@-i^|AsjK_ zYi?lrVK>@<+^ogJz+eCk8_e*4w`3s2N-^XLH&~)dGi6{XF~yaXVZi`u1;KPYcVJ+6 z?EujM%bMWe(=Sdff%KOU5nVS09kGB6y#*(!#a0|SUkFd&QFJh5$q6kP#AS_GcfcMPJem%#g*{3F6`!PkbV~d1_m#j<3KQPyFyOD zgE;_p3?9gUI|2+0kD&&@IxCQ1gHJodN*r*L88S2tGl@Zsfq`2MoU0hbbQl;Ep?5uE zh6GF`6MlsuED~N>GcbIyhJ+?e6`}%yd!ZsVxwIq|mLp*|5rMqG9n8QW1f5ig3}#?R zfU=u{85laD?8m_j46mTl;f7ditj7tX+70$NDHz`#%v&cM(GWiJb7VAum?V}=SmU4c&Rg+&JJ zautvd_V6(<97HP=5q^Y)BI^U(goz+- z*MzwgcEu*hrEP)?3=?oRh+w4z>WwTg`zLcTFf8Q4m2_ZwK@|_Uf{q6*BY^p%MUH`C zk{m?uVNV8zi_qbl1V;vjJZOe~9KgWv5jw;H^9>?t!twG35Q=l40+FIpi*jM!@Z?}%@Pl@^E^{$3+yGs5!@vN)Ne8A0oJwGc3Ubt% zF~}(l3=G?0H{>udFf34HU|6OI2?--*1_ldd2>X{Z0|Nupl{_j83_?)$VpRr)m7og+ z7#J81s4_4dgR+;YGcc@Chv;S2WMJUbgs}Ow7#KvMHQ`Qe28R8ho3y-585kBq*?Q&-3?`tX|3Kpz<_rv;Q1)#L28M?g5cQud7#My)+38jc47pYi@&DEg z46M*@@MaqZhMiFMbUOxyxzM(Bu{{Grr9DLbR|f`$-wqHqha&?6A2g{}Ix{fTLu>mk zX9k7|Prl3_ zI|GBIJ4D>Yoq@p%%Fgp(U?_ohh-y3-7@DB$HNFfCn|vYa_xmz19EGw^`7tnDfDT36 z_h(>u3hjD)^JifA17-h+U|{$c0nz&;ih#bl&C#Cj-MfPKY>W=76X+%pb3-Ph=D;6nlQ_a85nAz33G)h1H(FKVhk{6Ugkp#qxdxNI001fU6RpA7@U5h#0+9RtG(XrimLXJBZ7COTn91_nuJLYwN$z%Uz{ zxN=<>7>c0@ip7nAfeV^g?zk~9Jc6?Q-5D4{p^2s4gMpzJnn2F@GB8|%CW@#400gWFeEd~ZvX#Bj=W?*;=ji2{=3=E&3anfqQz|aAW zlR9GthDK;StT$y~*aVG-aB~KRC}=z!wP9d50S)7ob_@(_pkdr<&%n?D4Pz-s1_oJZ z7|(HLV3-dLV{SJF20mz5K6YbZcm`#Mxic_CLc_JsgMncpG+ZzHGB8|&hU*0|^F(#}W`V;gSptQIZh$6-fq$>rl406a#~w6hs`wudwD5=-x;en@fv< zfe)Hx-dZp)e1sM`AN?5^zCyEdQVat_8n$c-8&gHTlNA&Spe&mn!@y7s)dWi@i1rt( z1q4c`;I&JtAU&XQC`JZ`V~miLvyzK}VH333vPY4D;V85S_fuwI2!|HoA}S0Fvd|*@ zrYZx&BWMw^SDk_37_^9x(qv#zh87X(S_}*ZQ2)NyW?=XMEh36_85nAy3FnI*1H)fv z!s#+#V3-U|I1)w-42sYqyxEw6p$A%YY%yhE*b6N>V$2yBQlUl1Zwm$n7E4G()LSty zbU=%6aT^8(1!$3S%7%gAGL*f}j)7r2w0P;VXJD8NEnegu85lI6#mhox28LD8;-%7s zfuRYSI0f7o7$l*^%L_LKhL2Eov^xVs3bd%1?7_e=2U^tJ@MU0l2rX(J`Y|xPf)+LZ z{TUcIpv`8JNCpO5Xzmw@W?+zoCgwfS3=Bu1?6MdJhI(i;!y*-&LqT14Smp{&ErGQ# zOBom#Di|OKTdxmdVAurBL`Opy7*0S-fQKOr3{Rl!zab0^4A2swJCuQ;56Z?&Qm`Tc zl%znD05BURgc%qVppyWTq!}1yL9@243l4D?CfJkTNm8df?FqktiFmRzrp_hSy!Q}tX>fpqOQ$8|5O=e(V0J#t36p*DL!$IbN z)b}wkF!VDpFg#~qV0g{Iz+l73z|h0Uz_5Xlfnf_H1H(y128KtB3=FRs85o3_7#L)j z7#NJ07#OUW7#Mt+7#PBt7#K2`7#Iqe7#JopF)++#Vqkd6#K0iH%)lVS%)nsI%)sEl z%)k)M%)pSs%)rpk%)l^%nSo&)GXujmW(Edk76t}u76t|v76yi776yiF76yi976yhM z76yj7EDQ{bSr`~jvoJ7RVPRnS%fi6G%F4i?z{s$I8Ia z#>&7jm6d^EJu3sl4ps(+o2(2Bk69TQezGz!FtIT($gnXmsIoCIII%G>_^>fB8on|K%)&hs!ZyyjtG;NfLpu;XQ5i05TssN-c|n9s|=aF&;W;Ug~t zgCHLRgFYVvLpUD;LkS-P!%RK~h8=ti443&B7{2f^FsSk~F!=E^FjVq0Fs$NdV7ScB z!0?@)fk9S)fx$t5fgw|Xfnl})1H%mg28OQ!3=GnO3=B?!3=FA)3=BDU;j$D1gQzqEgPSx1L%1{pL%cKt!%}GmhI7&k3>Tys7%oaPFbK#nFvQ3( zFf5Q^U|1x>z_3_`f#I?Y1H*F}28KT}3=GV&3=E>O3=E303=HP73=EF43=ETG85m~C zGBC`MWnh>q%fN6&mVx1hECa)HSq6qLvJ4DnatsVnatsWmatsW$atsW+JkgLSNP^iSfuvUqI z;hqu$gSavSL!>eTLzFTDLxVB{L%%Wu!yIJ>hRezf47ZgT815@GFes`pFxaUuFa)SD zFqEq>FicWmU|6TZz_3k)fnkpd1H)An28Np|3=Fqb7#QxTFfeedGB8M~GBBvCGB8xB zGB7l$GBC8MGB8Y0WnfsX%D`|!m4V@zDg(nuRR#tgH3kMPH3kM9H3kM$kk$C zXxCz3*saCDa7v4T;gS{u!woG4hC5me4EMAc818E^Fvx2&Fz9JBFj#9dF!XCPFih8G zV3@7Vz_3c2f#IMw1H(OS28KV{3=Hf#3=B#-3=EDs3=GaX3=A$h3=FwC3=B0o3=Ew* z3=Btg7#Pm#FfjbnVPG)VWnie&Wnie+WnkE-%fN6zmx1A|E(3#>9s`4s9s`4!9s@&$ z9s|Q7JqCuYdJGI_^cWZ(>oG9=*JEH{&}U$9)@NV{(q~|Z*JohZtIxo2RG)$2q&@?~ zAAJS}a{~qjZvzH~Bm)M9N&^OlsRj%T(+n6Gt{N~fNE$LQ$Qm**=om6ESQ;`gWEnCr z6d5uwEHh+acx1@Hplrmzpkl($4ys%_o5VvAr@UvoI=(1v9=(b{DxMjt_AZg9OAZyLQpkvLz zU}??3kZsMtP;AY>P;Sk@u+o}=;juLXgOUvcgR%_+L%t0IL%$6J!( z47Rom3>mfz3^}$840*N;4EeSU44Z8k7=D7}>=+n|>=+n!+c7ZQw_{*n1xY#bOEx*Ql79yl;C=r}SkL^?7sR68;- z%yDF3IPS>6aLti{;k6?JLyZ#yL#q=5!z3pLhI>v743C@`7@jyWFg$f)U=VX=U`TLg zU|8qOz;MEuf#IPu1H)ry28Jij3=C{83=H-z3=AzU3=FL<3=BJ57#Nsb85rVS85kzH zGBBKVWnhqYV_=YRV_>jxV_?X4V_;b2#=!8|je+5d8v}!=I|D<8I|IW~cLs)6?hFhJ z9t;dd9t;eD9t;e@9t;d29t;c%JQx^GdoVEk^mKOso1u`&92xMSb7|6h|G?0N|c_0JBia-X2 zlYtBj_X8Oio&_>6ybEMt_z}p!@GFpk;ddYdgJ}>0gJ%!}Lu3#GLsk$2Lv0WPLt_vF zLvs)VLrV|?!@(d1hG#(x45Gmd4A#L649UR^3_ZaN46B0~7)}Q>FuV7z0CZ z7z0Cp7z4wEFb0NmVGIlp!x$JohA}XRhBGjjg)=a?hchr_hchtrgflSA4QF837tX+N zD4c=eNH_z-(QpQa_u&i-pTij#7$X=M_#zk>oFW()nj;t(4n#08oQPmxco4zBz!1s6 zpd87-;26ok&>hLZ&>P9X&=<+Ta4nL7;Z`IA!;44;hOdzf460EK47yPa3|UbO4C|s8 z7|uj7FuaLkU|^1BV33byU{Hu=VDO4&VDO7(VCau#U^p4g!0;@Zfk7ySfk7mOfk8Be Pfx#+ZW9n+?_o zCRo8F0|N_`cFNDsfif9Du4967VDceFrKwOM1_qdBx5S)cgeWsq-Y*lyB#>z!87$Ha zY#<2+28N*2;?kTFn4S0aAi@uzG{^*)9E|qQ28n{yfkZ$Uri%gLKG&k6{33L9FjJ9& z7(Iw!T0qA0fCvT#hR~8U=lqn^g8a<95~vV3pkQp6S77SF(Gs7OnwDP#4ih6S7q42*?P5rxp=RE3g^R0Xi{C8& zqRhN>h0Hv#aAsaXX^8?vOb4nnGcTPBLs9|aT!oU1#1aLtPxZLC6dV;wN(*vQ74p** zO3L%0<`gSr7Aq8`mXsFdrKVVMDH!T0gn*m{vACcpH7&COVO*+0ZemGtMrsPeae7<| zMi4z1QVNMs{}h*|fs9E^D@iQ^xx_O~Auk_cghF{@u|isYDMU5IuTU-JnK?NMNvR5{ zxdkPaI&cFa?&4B_NrOVNJR?6RRRKASKvpR$xK4UR5oYS4fiprfE< zsHfmml$w|llV_x-;G3C~9h0Y|$)%vLub^wEpsWFx2b+tch8WZR@)c6^Qa~{WvQr@= zwJ22)#dxd=bQF|8iV_kMxVRV?K>#JumZTQNr=?}0h(W!^CC_%Oj zWIMB{c<7I40&nib{wN!Np@VN_) zj|&tonZ*i8si2k@$UQ}=rLY<^F&DYo%t*`w$%EYsi}U=vRE5;UijPQz*|Y$SK1+5WKn*_Plg5DxQF&b-21G%MxRRO3e z0>YZ$Mi?|lVy*H}5;dMm4@Dg)WrK1xhz6y37)H+%xcC_L5zGJ>4YQ3<0|ZuK!Sums zV)Vn*!f0CQ;f7jJo?n`i5)aNUP!VtmiA@?k++g8c!Oy_3j-P>naSbT+L3J;-eob;l zYI3$hc}6O@-b>7@1XY9JT#H)aCg9M^G6T~2fL6g^OSzz>6sT~ZavuUI z+);8C-Yx{F-;Uf7!DR^6+y~E|;Ep~d3o7905@1OJcyb~Z)u8kNN)aF$mbO4NdOE_z zr%HYVS%{2L@)NA-i#%S!0P_hrPsD?(377#OpJ0&^MGLY6T1$f)uY<1Z z4^9-sXIrG6KV(b+Up)&+aVUo4&(6vkN>~*TSJPri&P3#Lq`U!2*#n--K{YPOA)ri$ zj!|+vp}s3rGckHVo(3t0WqxeM6-*8sUL~nTxcJ!gg3QLi4M-xO;Th2QvMK`ugBk+^ zLjnT>Lka@}!+Hh=hQAC941A0X3_^?y3^9xh3>l0J3^|Mp47(W_7`T}j7^0aN7(jjI zNlXk3^O+bJ4l^+@oMmEQIM2kuaDj<|L4lcp!G)QD!GoEBp@^A*VJZaJ7#dj^7+P5v7$&hWFdSxKU^vdg!0?BKfkB;>fkB&_bKSU;vf$n!F4QTD%MlCAUg@EOF zkn=$<0}bARXa)v`G$sayOeTm{bf3Wr9N>bqyBrBO1 z7}i4_3v)lraB#f9v_S??K}i+lylN%}hDNAYl-L*;G(ckp3=9nVYzz#hP_{W61A{G; zZO_KQ;0k5Wv5CXM`7#J8Nc^Mewp=?E7 z1_pH~dpSP?!&*>(f`NfSM}UFBNPq!6IfLSSn4dsRH&{4r;%8vk2GxijurNNXq=ltO z8R!HZD4yD3gNO_a3<7Kn3=&X}wR18s^g})Hi-&=M3F^?P{0t0pq4rq_Ffcgab0}zd b6&fE5WlRhVRnSmY7hqt}hH3=G00;vB{=D$U literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/splitter/_gleam_artefacts/splitter.cache_meta b/examples/http_interactions/build/lsp/erlang/splitter/_gleam_artefacts/splitter.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6a10bf4723e65f26ecfb6fd1e929833a57c3d027 GIT binary patch literal 587 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus.cache b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus.cache new file mode 100644 index 0000000000000000000000000000000000000000..69138271c41f9e0450371441485a17998644def7 GIT binary patch literal 76868 zcmZQ)fPmtXqQsKYVh{&Sh!-Trz`!5~rJYNQOY(D_bMlK*gHjWV^YcLB+X5IEt_LtM zpy>rkA!9UYG%j8n;KndIL757n#i3CSRr#3zGYl@P0;jLg?mD9KkSPR&bENK}BR zPykCSBT$6^P4Y{vC;>4)aWgPT^I-Ub2_(nBzyP8_xK$q_0Aho18C?=4=>tgQSo#np@Dg_$)w}!UbhI z=jY|6CYNMFQouuI28Q3v6gmLA(NOys7#Q85Oofn)%wmO%{G60xh2s3&RE5mEwEUvn zM37??@={Y%Qd2-NpPZkYTbh@doLG{oP@Y+mp-_^Mst}%<6r7)&om!&D1@pddYH@L5 zdMe29V!3epbJ2BJY2pV15qd?2L^3=AHLc`3yiiP@>}thh6Q zf#E^|hRdPq;S_$C;Wi#_00TFaiB=Xr^krc9>Pw;hILwCH$iTqB17$+;5~3!^W?^7x zV4=`YpR5_6EU@Dn};20_AExE(2_fDFSQA2{p-B{7g@PTu-u!XkW>j)Q<|5OT9i|n znU}6mP?VpXT3oEBpb(i~%B4`8kzbmVqL81LQwc8WKm}bUC=?QtOCY6O0yHoZ6p~VN z^2_zOV3FyWmsygTn3GwYngVj!D`5r(VG)d|0{H_Z4WdD~8yZj`HVF4X`5-kQj6dM8 z*bOoSq!C1eFn$YQtpa#P3ra06$j>VVml7(83=CF@7+wIG4#S`*!tX?!Ho%O4&@g5A zD=Cn5AoZB4m_fo&4DvR5V@Vt;2Q8cQ!I7Gmn4@0|uCCai(vS=X!UfIjPAd`9&~)fgBEEgJ=+j`JI7*5vHXW zss+*b$H;_G!x=y(fiQ><%P1I%>6ZgQ&Vc1WaG=Czl#~<*gM>i|3Y1v&K_dD^(82`f zW#qyIq#6`5Fn@vgFbs=nu;DPxPMIZ+MMa5~Fhxi~39|P^(h~^7e8RxMK&X2Q(hAZ7aw)9vfFvWg#LOI!EJy~1 zVIfCeGK9sxXI@!iPG*X8ehRqz2{s%ihb3iz908Mt(SG?Q0Y&*`nJJJ~9oPt10Q-S@ z#TZTpD?!Tb_>&JKEO?XhQz{u4VTGsysH;|53?53cQZO_yFyMkU6WsGN^U@v56Df>Y zVl6i0f{jrH6y=xXC+Fv&6kjmk!f2E<3dhDcR1$@vt_zI0VAa28UUGh3ab|HzYF-I_-C)iI>mUW>=VT^VhGpjGfJb9M2^Pig zP!4s2zyh8EpygOde!f#?I(;m+V+m|23TzYE=a*mO<17-7J)T23rkarOJGI?rGmLI8$cBgj14muM8m8C8w3kv^cof3 z*(rh;3et#zQA~kV=$?7tQE#w9Mp*t$hK%aQmt^Lq=9iX$8}?uoSO^qTVQxchk)nGG z=6me5Cag+B8mIsn0J0iHgTeK$?*;R!3t5D6G!Q%*%v?0W8o! z#)2@+ewfktJqDA77Ed4tgEWE+0-#mhr1PCQ|9F77l6|XNGZ%H5F2I$NDPGWI|`Nvu$KcM{RoT_nXoAG z%glkRfcItd^Wwo%KZrgKsQr=(avn%AY_JzLehdl$5C+M?ECFkPC3BDjGKMJv=TqFR z3Yd0SKMraHtk(k8P4N%`%o51J39Q)*;UR`mU@mqmO3XzxJ{cHcsVXxMG|rNfT7r;7 zbtYt-0oM2cI~{H80T$80rAb+-$?#qSD9~Yn3km?3GVG}wRx+Ut^}-B*83v+37#4#J z4Df_moSKscvIjhaN1I_ckj)?rOGoffEGLyTvH*)Te99xVBn{MGE6C48RG=_@Aic0$0_i)VcI`px6hMkFrcl7e z9e7+GEDg&OA<%jlEXaTua&SsaNi0His$rQnB(*3vGcU0OsV@kVa!pK0Day<%Ek@*L zSicD@m6u%Ku8BECpdkvl1Y8%WC6SnvUxe%y z6v?90v{L9)1XwrRp&*?_sl};?<_TP1N@{Tl$R9Ly1Id); z6{RL7XMl8oF)X3GX66+X<)?$vI#?V;z(U_Ovl!$UkPsNdeVtpJUYuEl2uZj^UMi{+ z;I`zYmY}-;mUUh8@(Z#Naf#59UjXXR<`*CW0%1>n35v^*%ql5Cj6fo*2aTSCeE}li z>cLikM8FtU*t&v+F{2$GM__r-v@}>7h=6Ma*;9h46&_8XrezAKZ3)r^!f+3lgQm8U za`Kb2LBe1R*Hw{}n1Ve8f@F%3(gQ4`JLTuY8v>ww2P=MH@*zc~hzNw0L2ijTNErmC z6uE%}Tcm-!>;qO@z=9hz9g&$=3U6M3tOjA2-XT3xfbclHb%mHIfF%&f$Ww4hegQZd zK?E!}Iws|ThowN0pm+hvp<`HdfXj4PT19K6!w1ESpzDzEPnv_Qg2fXoUO`1PNF3RE zSl-9p8i9ENq%*YywQvWS0!sm~p(u3IL25u4#e*b z0vr}!Xr&`qJ;-C|?x!?+fDMKcC=P*noah7%b2(~C0~rkpNBl7ki!O{wW{`FijNcsm zbp`{2DAcUv{JgT%qLO$}<0iEv9#Z9ioC%T#1v6fS=y3qkn8wDyuo@H$jGz<*GKv9Q z)WPIIA|S)j^%E$L2vt%bW6_O73nsiqz#43z(J`b2MId8AuEi#eW<1P;@gfWii$OyO zI067<90;$Z_M#)0fgr;{coob7STi;~wFJ7V7+iya{071xwYZev@MAk*7$tM!EMQ^9lPjn@fzs;&!HQeTTqOjb?RU+~N9tmM90bb2pkRl~ z%wuI>Sc1(hVjyJ@j20wlT)ZJ926qc2H9!nx01s-w+yaqdWMBvhU|`6_T$|6$z(vhT z6;Or-VVF}uG@6&txWp%DG>vFnSZV+dprAI$8M)x&6tI;KFgJpt4VEWhatsU%_52JB z?U;*4+2Dgr1QQXgaKJNM2`WQiYh4&w8DLFyEVFl@Py}I+Yw;+9G(f;L2S_!ncLQp9 zgZR(=85p>*r2tOc)A@#mT%e!=VUQ8%n7Fb9rVA~`(75;_kP|-Y1|GeKmdG%3K>-QN zD9G}Rpzs1of)v6+12lFB@(zp-3LTIdG}nN%g0yKwFfh!CU|?YM0P#Spc) zBUo11fC2=T@KB}0*%%m-Ft^e$!Lt{^B7l&?7#Xqm>R}ECxeZsh|9=7lg97G?XJ*jc z1}Hg!Fw7hf4NEZKF6Y3F41k#t5F8BK>-HyD2nVB76yiWn7a`a z7+{3}!MKDKFWAO=7+JtIF34|4>l8tb0bzu&5kCWiGqy^F5wo=q(uJ1J(YW|r1kw%0 zXnN4Nq}EZ446s2Pj4^hQPq9g=vNAB}VQT@woKIQE^S~>1*ib7h;e#9mE4*NGObm>m zk{*OXp#v)!;nQ+3^{`<~kXZ^m3=9dFn*l+_gG&OKVIUfWVMDth3qTm=Pe{h2bY(oM zX@qB9VLl?X>KieZ3vx3Eqa_tIE-c?54~T<&55lkz0I@+BW;P>YyN)ZU`;0j^018xC zn#5FciF@$*v4QIZeLaHPdH zC@Bh*dSGgxgUF!Z02#Geh=D;&n1O*&2c!tpr~vO|OfE`IEJ-a^NK{CIY}x~D>M2ew zQ7A1?NJQ*dR7eCZ;Marf3@p#g$x$dRPE|-rO-n4zDdAEmOUx-vEmla&F9L5NLTaPH z`sr2RHF*l437GuS5*=`>qyVyZR3R}>AvL$4q>@V^tuzn3trO&kq*R6E#GIVe6ovAP z)I6}c#i>PQsYQ^DwCJrNJq6D+(B?#i^2EFng_3+O=(gL;s#Jy4%#w`MBG4|zl8pS~ zR2_xlRLFj)jMSWh)FQaG#R>_a&6@F0_rq2qC2*k)5x~L}6s{l)s{kMw4z#)-*6{<2 zz{bGAtFB~U#lIBtXP4pEjSQvn|#+jHHgVS_Ed2s@WpHN<0 zoS*>OPyuRzm!;-Z>Tz+wwS&9@wi3zxpvE_J>=!JB@FaXB7@TlRGBS&~6w(qibBcAq z3c>kJ0qiW0C@4dK!yOSK>6vAzc?yuxK!rq54lV|-c86p}ki#6)N>V`?GO z1rTg@QmR5hVsSBK(=vE#FK8(hBzsoomntMD=7DlL7uZc0xdpbx9d2exK3E|npMpw@ zR0YT``2>aB)RK(+6tIuD;2YyX8|W3%K&ybj1p;WhJSege0jLM-vw#u}C@Fz3G?jxX zwA6-!3r#a1LqNJfGzedYCT$QKgs(vPAT=l$mcmhTBj(sKNDr(<0#+KIUs3?8UBCkR z`6UJV;JAgkG#HedFsIdE^#)j1JmFdwZXD`*Cy?rDAY~0-UU*X+(pW{>Sp&*Eu%v*H zL`!raMWCoTFT%hO1)8%UP^+S8N8=)T(D0qbFbhC52(N`^XAm2N*FpIpM}RRb9Er_j z_?osL3pS!#faYd2E*V~c+W;HF#F&JF6_B8Gf>RMQ19+SYqy`iau=Yg~c#9v%ComCE zqZzUo3g&lE?FZWfZJ)@%un==Z0PF-10W%+D2gp?PZa9ItB$y*0ZD)`nC>U0zKw=Nn z>Vl0^f<<7hE?TcVKyedFMF9&ktOHq~@IgxtXk47J2eXlp0cn^Co(o{IpuoqP!cck? zFbCu9kV=R!FhpZ+*9Un4TmXaP7U_%<*sf%lQ&98SVC|41wjsmZjTRwjT&#f$(FzI> z5QYT=hz-IpE0AIi%N{!9m_wER<;%dpimg_ISPLf6T#3eoIT}1>i8f`y03YXqIzlnb5D<;#9yBg2r9iAebstDK7^CSy;}RSK zMVuf4YJ=yb#)BK6;KU3~!!TDM%cGZFum;B~O9qBhRv>plS|p%o2AKtu2g!gink#W| zaWqsPLMs-K;UN47$_E(?&Ve|LgEseInH;>00HhzJ4@847Ub8_8@tX}x6nLfuVC6Vu z8vrbRAUuMb0zfVx*lvLZHuBgINFyjB@H?NDK|^G-8WEYWu*}bkhjytzo&aG`L}8Id z^BYJNNHv2v1H&#*F!W*PMS z6i~3jIv4O`U_b`K$`wQ-6*Re$jydT9QjC%;K~4b~i(aUM@(f50>T4no!U5^PW*Rk? zyTL*l+@z)Q_AFv{?7-pzG@pTT$_>cVAPiFg4Kg$?W?X~xfG{JX8pgKE1C&r;en*qP z$il#I7c-I-;44!IRu?dXu_j+OP$B}kkf9i92{|Z!VdVf=9L=L(4Gatnhj|$o-h%qu zXhknf9!8^SN8`e5Laxt1E&$;<&~g^U2I09-K77;wG$tBf3R$xaPWfo98f1C&3;|5`-XekSQP41Q-M6ZE(thB?Ih3zF4(@ zQV&Ri^u;BxG>xrvLiG4SJGVdsrbt_~Kvu%Mh$`L6%D^xITa6D|{|a&?Wkse4yvB#L zN02vXfgA;kI275ltPBj-vAGFWZBphYDfmJcP;nif2;KPu^(<)33JwKmX$F)`!J7CO z7#u+hMZo1DSOi=Uz~v!Aa1v|!L7%{bsz$RCjSDMguvG>NpoKpuBZ2TjD4zi~L7i5V zpBoQ9zYeky9m^7OB9!B*Dpm$EFnlFqjWH|_LHd30LK=MD9-mHNRSRRr=xLU-3bD7MDWF>OmN##&JhFo z4wf}w61n^g3^mxi$1-@m#{zF|xnkKE4Dux`>e1xISQ!|Uu%#(jx}~hx^I%|LTnH5bohqx406kg(G$CG6T9lWXq5vvv6cWJm z)Cmfi#h`^9`2~rErKt*_8UZxZo~MvslmeQR&gX(m0z=P6hdQztG|vs5hK3#?sRLfp z0h)zQ0ZlDu7Axc?78GRWrGw_T-SUeRQY#X33vyDexD*l+62R%4OF=rK?pJkgkgp;GQpc+XvcGaTnsY=AvJ}Cfnheb zu@aaC1am(u^04(r7#R_@AgDQocIF1iNigl;Jzfa;*DMSS-?6z1)=HtwT^#T`Log1; z1IGn)LJL+RU?_s-LRcWdQUa`_zuKRH;fy~;|5||J)DZ_Lw5@ld;2W|I68>)cGgS3J$EC3<(GW`Z`LB0Xmj}4QM zLC}1U#)TzWPsGg=F#m&S5Z(rD0D;&bydBENa2Hlb;mYY~?!dvt(MH4RB$#(VjTE#t z0w^zFmloq^V9>|bv4*b8fJFqz^B@fCyu$(jhufja4c3S!&@X@)inJF4?WA6qbs&$x z3`J8yphC9vXJClHRw1+Du8<8Z2F1ub8+8aKM^FFgZpBhF1v;41X{$`C$f) zMuH*~gkeS?(T+nG$Fo%Fr8O(0D99lAid0+w$ z1H&m$4S`mS!Q??^f$&7MY>Q?b8W&cqVk_QZCV@-^;Yl!)L9PIK0o;l$LOnbVlxlIv z|6*ZaV8d2H!lt4KRz@&`u~v|r@K!EHmlhUAAjiSlau|xx_NKu?2Gq(0>4))Qo%cHd z3=ABB7_DBAQc&6hckDrdgpN@X9>_r;1<1W<5FZ(%Xn>^_GE*u}&%-QaM0g18$UTr# zVTlJJ<;Tjv5Q;5MVI>!3amo*$#fD6lz?=zk46Ka_m)XS1z_0_GTO?rG5T1h;+At46 zxp>o;1i~#S#{{Cd1twF&%D~Ws%`LD+xs-*AFnqQKTKhrHS%>)(fI!7j z?9ae30b9kvj;G?V926A?JAAyx6|}1k`B+0xXuygXWXU(I3=Chf#Sv^J2xW0315YoI z+ciLu0m2~Xz|t;6hLM5cCqDxNC$`}f96OUi27xd&3kO(cfTkF5Nd{dk0V@9CgD#Bl zgEBxJ)%Yav-67bz&9JnNO&QEGXwCy!0P@dwF9wD%Zw3ZNFA$Ha9cSoq4RI7vfWnMM*H2&-A*`N)wCSK_56CPOjFzs@xUj|sfxcHi zdIJ{Q0jQwj8P*O!l?NXh1`7>X7KQ9M1-Z3=hk>C7+gJ-J)6y_wadfBA{EfziHBqrO z=5dbIFfqW^xt8P;u{469V)W>T1=x`g1_r)RP*)oqj-U_&x07M=U;%M@}DT@~tck3=^>p9KkFOh zCRoXZB=>-Yf#C%<_rPo=7~FW>gVf{#o%{^iDh~A|_#g*Z<=~1W$;QUOz>m2-Mu9;N zrjw#3x*P*+8V+%U7iiH)NM!+hc_S#SaH}HFK;G-ez;GYinno5p4P@g%(LiQ_H%B4q z8J6!r;Q`AraG7v^28JwbLuJg^*Py@>6tp#m#w8}xqG>_n5?R2*2iKSpSq1HYc#wDD zSp^{%!OFmpfUPe8TbV^!RuO=&Z311|2TSW9x4_D5kT_^Ki=Tnv8)m?RRAMbHVMd|! z17OifRwV)pd1y@xOD3R30|>)=0f>wW9lV991i2Mv2vmxd0esFn%#koZgU0efX2STO z*)Wi9Sp2}QiUG;POhg**hb0QoTx)7_3G93lkUN3}7#NZT5l)1rOOO;e7-24f1s|+= zPr=wf$Rl9$VP-;Um{nk-3B(Lk3*u@VuqIHDfMk#diLluOQUk(RZA9%#pgR^8y3lY! z!X%TR;YaFzkjQ5F1$>en}Bb0_0F+jMdTT2^eG|EHpp{fiTQQm|#0#z*NG#!2lWmXRQJH;l35uTvaz`bHd;-Ydj#PPAJnUQK(2w6$8ec> zEDQ`wur--Mi4PQ}l(~fo-VOkD9iZlcTg$K#03^=Hz;HEzf#Er}X)RC(3W{OcK{PBI zLCX^~F1}_wNH-Xx=|STXnBzihmdCvK2o%t;5)wsrIST{BMr?r%(gAi8Mh%YUDtsPe zWQMOoz;+)i)O>K@!&V_+Q%9iS_~pmIAct+poE1;OVLm8&1gwaR3hJUE-P{8Te|W)x zkP~8KV35Mrdy|I+CPncj509_%%#sZBW9ML2fHER1zObr8OClikAiZBD7#MOS85kIu zK=mYO2oikaUvX*)7RO7H8t;8eMIWX(Nhe1G72DnT>kziv0-*E)1E?}h@ct-*FtQb)7 z4U>hO8w7GTNEAea?mYwr0!$y`Mn{m1uvQn+MSmd6Va9=K?bH%bmV6@3z_3RKBaea1 z0QYKOIzUz;V~}YeKDajl%hxz(kYQyiST&XRSi^eLkfAxmJ|~z9(XPM*IU0mfokaHW zZ=fgu2NuXof)zQedx>XI8Dtp9P!L93cL_2bB!WH?3Ts|LQ!J6&=|LueFziB0km(?d z8YYmvz9?e)`QXzZVTr;Y%mcX?q#I-+j1A($R6!yJHUSFZA)-kiDT%^TtuwaED?vtr zjK`m6VX+R*dmtBqw1H?4hWQu724PtEG9qr`0vG>~1J?43KzczKq!u=(fT{q^F(8E? z4KH{Y7?MFc(B?H@@*p)JjHVrp3(M;0JvfjlQ=w%ahz-KipnQ<2V2l!PL~IFwmXNPSAm`@K*| zQD%BZi9%j}Ip`=`*aCXcvCZkAqiM_Yi?Tt@p~PZ^%)FA+ycCdbr_vHGaC;7Hc2cSW zbSXY)*by@OjPMPl-2^)Es*<+tq_fbn02HYpd=AP7SLh%CP)vhp5QZrRiGeU$!a(D~ zOK8x!vV&qY2j+f|UqBe{S(sB@F)tnkW)@HJN8n+(uzPVOzHZhnS%;Vq!5mvxqHbJ2U8GMvv)Of@V!r&4C6rR|^ z8JieLEeMZ>Jt!0y7#NUK6I2MkEq`DWCd@UkED7UNi-zSZn0aU=0U8%J?uxNQ8m1k- z&k%|&w#8W&S9 z%vA88FNgz+7s$9PNCbp2ML}_eKA1t?5FdQ&0?K)wAX{P6*l-DuAU2C(r-WgQEy8ue zj0!1A%qvD-tA`j*25mu0ttbJTkJLkf3WKZ$VOUB7=T?eKHCVVph8l?(c!b3$<{6x@ zWB@BFU}lfTBq-iM+rjhmK%;a}9=sJdcw-XgQdpcrm+ioWK{QMXn(}dQp&DT+1eC-< z7`B=XWDpp`k`9=Ig@DP!3_@3eQli4rA6ivLlwN|RG06EK4D$kPY6Yp$ji}gtUSS5bAgNlVOY3=rwU-^gJ}F_qiBOU4@847t{DdS zdJ$MvhBWH|GZ2du$eADv^Upx#Dwxx-Pt3q7K-8&Akh?KuK!F6puz-S}K!G}ciL4nR z0}=&cS_TI!#X>3%#5@%&me8jYVHpxJLkn^tc2STF2*b<*M<>o)2_Lb7P4$Ab!a5%y zF%TbwQG z2O3<5nU5?6*8pV@7p(C56n5MONH@$_WHFF92%{JaOC>1F$Y72GmF^%NFw=&N!$FP) zVOVj4v>gK^48v$KiN?jE85STQ8ia9O(*=)mSXhHc1Yy>q2!f0OVVF_ym8QdM=oEH= z8Pdck$k(7CfwdHet%p(K8{}gUMiB%B1qj0v0W1fCRDv+9kb&1JL8*}I31JFBG+O#a z|WIhNn?tFTuh~wEX;67cLQuNQj;1Y3UUN8hJ_%w{(vPSj3HTg zenP%~5@ZZa2TU9!2Ev1(T7pkt4`7s_hYu))fx-t}93%$9gCg8$n2uon2l*9*Vc841 zj2c-QCWXC=36q6tge7(m4Z^tgalz-`VYM-6F%~R+Ad7)q0uu)r2*NN!K@JCDm>clB z5X(>?$Z(K>APn1K1~L$g@tX=uAc)BbuqI>zzX32$g61_r_JeeSXk<6RJc=%ka4XCX zr0uaFgK!AL91SjHz>QScj(D&L?5^0t($wM-m=Qs#U@pj6Ad_KE0kM%WO8Es#xM%~Q zh=UAZ%O7EZ0Lz9jagc2wn?N>zXb^@421pEqVGhI|>#+6?w&^I4Ze)z&c35IVUbP9o zO#r%X5_!up$RJpk7fl)@g^aP>980W7TbT}6$s3W zupk*NG@$2nfzkyikAN^NpMcoN;)p^6M{)%z1|=*U(jX~h42ukK(E-afqp2AbMBpHS zWx-)n2*L{7AyU`Ck~1hmKzJ`KjWRHRPYl&yU|;~z%Ye@f#Dnj1H(H8 z1_p6P1_mca28Lip28L8d28PLu3=FFn85oW;GBDg`WMFs?GMtHlL5Ych!G?)}p@)fq zVF?oh!!jlYhEq%o3^$n=7=AG^FjzA)FxWCPFhnvlFic}+V3^L#!0?cnf#DxB0|O5W z1A_t!1A`R{149Z6149N2149-I14A|o14A~D+9wTRtAP|tPBh+YzzznYzz$2 zYzz#tYzz!?Yzz!mYzz#cYzz#^Yzz#kYzz!(Yzz$R*ccf0u`w{*Vq;+V!^Xg%z|O$n z#m>M`#LmDliJgIA7dr#PBX$M`HVy^`Jq`wj01gI*3JwN_c^nK3dpH;v?r<>D^!pp$0j+cR94=)450bT}%L%fi?EkE!wF#P3ZVE7MO3C_pB;KIki5Y5NH5X;BF z5XZ;Bu$GU3;RzoDg9twZg8@GSg9|?cLnuE3LpDDHLoGi8LkB+tLl-{-LpMJI!*PBF zh8O${4Bz<~7`Ox&7~};Q7!(B<7?cDU7y<UU`P{WU`Q8aV5k;kU^pqrz#t;Tz>p%uz_3|}fnmQ81H*kG28ORf3=ERO z3=As53=C$%3=Dz73=B2G3=Eru85kZ5GcY_CW?*miZU?7iZU?F7iC~rD9XS9I&XcyCh%fRqZmVv=Yj)B2Uj)9?7j)CEt90LQZJOhKU zJOhJ`JOhKLJOe|MJOjftc?O1A@(c{8uPJw|TM}dJM zSAl_Hp#lTL8wCc2w+ajl9EuDKp^6L)HHr)ja}*gE_9`+kd{$&&kX2$}$WmfpC{bcy zn4`qNutJG}VWScQ!zLvLhPO%#3@XYD464cu3|7hv3^B?K3{#aE7!D{iFx*yVV7Q~q zz@VbSz@Vzaz>uKAz_3<@f#IqO1H*e21_lOI1_oVK28Kjc28I?@28IKw3=B_I85p?K z7#Jkf7#Nh)7#OtF7#K{|7#RH27#IrG7#P;7F)&I@8jLGl_53|blt48a-<422pD480l*43jk&7}jVo zFr3w3V0f#+!0=szf#Ht^1B1FI1A~<&14FPT14E%E14Fwe1H(Q|28I)w3=EGo85kI~ z7#Jk97#NJS7#PyE7#NDR7#JpMF)+;3Vqn;(#lUb;i-F;}76SvLHUoo%HUmS7HUmR} zHUmSGHUq;FZ3c#y+6)Zev>6yUbr=}5bQl;Cbr=|y=rAxG)nQ z%K8ipKKcv{pjp+``V0(5^cfgHx6ErBFfdpeFfc?LFfgPWFfbGvFfddaFfdFoU|`r~ zz`*d%fPvwg0Rsb%Ap?WFAp?V{Ap?WIAp=92Ap^rELk5O@h71gM3>g?$j2IZqj2IZ2 zj2IXu88I--Gh$$PW5mF~WX!;zWz4|fV$8tMWz4{E(wKpP*Mxz=(}aN`--Lmo-h_dn z&xC@a0uIAzMfAYjJ8 zpl8OwP-4cw&}YWLFwcyEVT~CB!wEA6h971O48rCN42tFq4DRL(4CUqw3=7Q}7}lFJ zFzhyGU^r^dz;M=_f#JD11B0>!14Ebv14Dxa1H)7c28Ov73=Bst7#MC^Ffd41GB7w< zGBAW&GBCtjGBBiCGB9LWGB8wIGBDIyGBAK{huUe$!0_CXf#Ibk1H&sz28Q333=Eo9 z3=F|m3=HX33=EA{3=C(j7#QwYF)(miGced$GcfpBGce>?Gcc^MW?a~z&W?d0*^YrB-;RNy(vE>)o*e_jbvp)zPj(Cpoc0V1D)tNvIra<;74{4a zlk6E7=GZeZEU{-`SY^+^aMPZF;iWwT1E&K6gPQ{bL!bi#Ly`jnL!$!&!!`#7hVKpx z3}TK94Azbe3=xhD3}ucC467X(7_K=oFi1HuFt|H0FqAkkFmyRFFwAjcU^wT*z;MTj zfq~JPfq~bVfkE7vfx*U^fuYoyfnkC(1H*D>28Kh<3=CW@3=Co}3=9S?3=CE-3=B># z3=AGF3=Fw03=Fee7#Oa*FfiP3VPKGSWnj>EWneIMWnc((Wnjp5Wnd_DWnh@*%D}MF zm4V@aD+9w%R|W<~HwFfOHwK0THwK0@HwK0VHwK1jZVU`_+!z=ZxG^v+a${gv?8dcPP9+=GGPg$DzJsV4&i=#HHxPX>l*o(v32JsB8|c``6u z@nm55>dC;s;Kjfo;>EyV@5R92>czlN<;B3T+>3!>r56LkDlY~G&@scp-V6-t-V6*D z-V6+p-V6-s-V6*~-V6*=y%`u*dNVLw@n&GS<;}pr@58`gCeD$)}MjlsXqe)cK`!JPyho%UH}6_K>!0oVE_Zewg3i(TLBCVoPi7s+<^=X zpzGn>0~r`X0~r{?0~r`10vQ-)1Truz3uIte5y-#*x}o$(AOizO5Cel@5CcO>5Cg;7 zAO?mPK@1Gq!3+!$!3+%3gBchu1v4;6hA=S1g)lJmhcGbg4Pjt77{b7CID~-#beN}L zCPzDCZFa`#fFb0OgFb0MVVGInf!x$Kh!WkIK!xT=lh>m1n0NtRR70JL*7sQgBS(|-xvmlych|xEjO2 z@F|9YfgzTGK`@qqK{S?uK`fSmK_!-fK_`}h!8MkFAt#oBp*EI*p)Zz!VR0-2!@*bv zhKsQb3^!vL81Bb1Fg%E5V0atL!0;=Ufx$G6fx$11fx$nHfgvf5fniM?1H-2{1_rHo z28QH#28O1128Ltt3=F5@85lSd7#K7X7#I=~7#LC#7#JobFfeROU|`shz`(FGfq~(2 z0t3T~1O|q`2@DL1i3|+Ni3|)Xi3|+3i3|)ui3|*Bi3|)Ki3|+$5*Zj4B{DE9Nn~JH zn#jPgJCT9mav}r6>qG{I--!$iI!O!+K1mD=(Mb#pO-T$4hm#l>P9`xh+(=?zxRb=d za5ss8;Z+g?16wi!gLg6mLtrukLr^jULwqs=LwhmI14D5N14B;=1H=3j28QD)3=CIO7#JR> zFfe>cVPN>1!oXmd%D`Ze%D`Zg%D`Zo%D|AE%D|A4%D_;U%D`|om4V?)Dg(nG&{3ah z3=E8E3=9Hk3=GO?3=GL>3=HXM3=A1*3=BHqHy>Ffgd(FfiEXFfiogFfh!_VPIIC z!@#gIhk;>R4gq=gD@ytfoT1V{M=Oiw2b_;wA{qJJpJ^d{9JwLMWV&v zq2PGX9_pgRTQNti9KR*X%8!R@#F`k#0qhDN{gBsNNB?bE6 z%j;l{NUcat1)nVf8yCvSPfyRxONYrr+*DeW3HNEMk%3{eB7}_^V4y?;)18}G02%`VnQj-uz~Bg~5EvL3;C@BB&>I#g0r`39 z;MJ+1(OHn@J|+f+=}ZjZH5QJp3=CeN))gLuU=fDx$TFCjML`S{bnBU@!o+S{N7@2p1e6mxH_G=F1MokQ2U|0(E zZeu6|!&Inui7dQaGxCc|O7n_R6O%JwaS+YHz>p602Abz!sT-^f*>|d}3=Ddp;~E$k z7~D7*82quvK3qTO#52$pF0jynUDX2eBB(N`<${C`u51QN$gZS2Av!T zyTyiqp~D8kR=T1_p*Y7Y2qV7l`<66$Xa;DiC&@ zIs-$JI)r^$hk@a`4uoxI#=ziY24PEiF)+w^LD=iP85lN$?ot7zT3-eR7GH?CoG$}| z5|r&5#lR341raBZCSYYC^wzK9)Dl>Vc+1Yf@Ew{IPy-QCm4RXnN@%)ah)7{z zh=!i2Uzy6lPy=eTF)%RHrZF%yq=6JOFw~|qFf^n?*qj**3_KYSHeV(KgJ33veO-xx z;g%AFJyV5&VGd{j3u*5fS_OcT9$?M_-Fnmj%`caO7#JQv^UJAF28Qd<`~vkI1Jpd& zvLmF;Hjo2WVCe;PJJ{O*NFJOM!oaXL1QNzg;S3Cu!y#}W?)zWz2gO!mtdLPJwG!q-LX8e5@wKn5Cek;G+*X~GBA|l z$(OLW%S|kR1vy6?0|R#)#2wn&3=DeO5O$>@14E4=gzag>z~Ey9VZ+ihqOHBX)!zvv9mKU2trdh zl!7I6SY*Mk|AASYH|sOXnJ7nxzN1Sih|VSlGGH~9SpEA1zn?X9$F#0gfK9K zL34*$I0J(RGQ14Ee`#ItBN z!%{Wkpk0^&u+w%yZu`&0z`)53F@Qj_fmNj_XB@#irOnR3U;%Xjl!B!km_s0I?m)m*nTh=O&gUXCMq)o5aAdKM7(Ou4sX!Jy0?Rg&s&XsNq%wjgALa3=A)=AfdU= zo`GSzJ%o)GBCxzvl$w|l4_b8&GfO?2fx#dfqUU@j1H+9>NZ?p!Ffh1eK*V8%0VHrx zyMwS~11c!c*Bf6}U|_fpEg;a`18XrOU1JT4A_wSQ)*u&7;$UEyi@m)DD=>pn6H_Wd zYC$2B%g?}21kIIu)EO8KLYpiOnhXrCpt%~*ygEoB0|P9rpdW<^D-e_O^U6|-O5!0s zKX{S+HHm?N1vFjHz`)?D#lR4v1u<@#DFee&QwW=Idma`Vpb|PY8Qz#_PGMl^g0@Ux z0SgIqh$~=;!7o2A73TkTMh1p%Xp8VZ4+Fzv9!R7e6JcODB?4hjS7%_D4Gju@4F(2b z4Tw0*8PJjgmgFI~TEgN3cC{rajIVMsFx-aL90Za-Jcbd+s=+c*Cl>?56ljh^a{|oQ zkeurbIz280<`CEwM<9p1XJcUa&4$$%un0tJae#DzqQ}yofx!dX;%W$HVCWBqm{Jz`9j#UqZk;LL_yfj zkqitzkq|a4Dxf(5=4NPrBdxSJ6`l-XR|$g>)Kzu{hKKBs5Jxit=0*4wRIp?WyN3#- ze-9S}!%+hLNti(anP|f{jZ6#-UC?SE#D#$&7TRyaRh6K(=QGjU^Krfm4EfN2B9H}O zjsjP1sYNC5!&he{F)%EI)@iVu2#I<4I4P`{Nh~Nx%}aq53Q|T43<}WZFv14H9gZ9BE+c7ZA zg|d~L85ne&AqmOQnSsF?$_{m7V2FpdwhZ(c7_9UmYIc}0FdTyRy9q=UEc9H#_XER% zTbGl8!5HdVSO`D|q03W~ARAR+(Eutjit=+{0iVjmz>ot?cRnr*48hQJXX(no-~b&u zEOljIsDZKxxDOVHh*l%ajUTxf7=A;gVQztDP*`c|nOByWlbPb2p8|3fI4_5@F)$=S zn|N;{7#My;K!Oa-1oYw%mgN~W0UHJ_v zJGXE!Fzkl54GFinU}X{XKw;SGd6>c1vKSbiWWt; z$;8002%0%wSurqthPKUN9)hM{Sgbnd=jEk>C$~Ue0^85Rz`(@)S>qwSPnc7S;usj}p<@(#;usiCLfK653=I77V5=Fd;u#p+ zq3qISG`&K*y!*eKGGJ%0XEdipwB!PjU5z1bWz`(E;%0}}oEN_9wW}!p6u#h)o zWni#{4oqZoFff!uqZG{uSgM6xF$vQSyJr#q9ir1 z7&N5_(g*U(YJUcXJo14ET1 z*rVvX6``gv5{hV*nu;Q{k5v|$Ar=Yw7Q*bm$i={L z6PobQ?1vSg1*t`8`9-<$ptvZ`NX&*eyH+GJFzkd*W%DUAFvvrvuW>~LENn6oi{n#L z({d6^;5||^bp{4UXe$DTGr(iQp!4L=i8}O8kf#Ee&ibw-M!}BGTuy9|`$-uA`n)cyN1l8>@4!8pj z^Ui%c28JilR%Wd;149$Euh5vyz|aOA%A1tUz%U)kzLdeha2-07_c()r;RTevIFo^4 z1$4o|41ETMxzN4^k1+#-AT%32k6>VU18qRVV-n;teZ(3HPyr3f21owqG@zC}K ztU!Ube2~W5ia}dLKsp&vs+X!H28McQ7ZNS{ag@%m?68}af#DRiE%A$!fq@g+wT6Wp zq)Y=1hQLJ9@^ez+OY=%Hb6{~1YRte8Zwz)71Cfylxxo)+z)3a+hV#(WfaZLtCT#BH&67QX1#O$Ee@)Ffjaq_F!NE2Jtz( zT8E}JhN9Gx(xSY~ymXl3Z)q?vJl25t9?kKvvfuR#Rk~&F`fnk;�g*Z7#RLS+1>^W48hPTkC&zl3?HGR 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 G`vm}9mv1!y literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@socket.cache b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@socket.cache new file mode 100644 index 0000000000000000000000000000000000000000..1b4d8aba81b2273d3712d24139a625289dc4acfd GIT binary patch literal 32204 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@socket.cache_meta b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@socket.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..f226af5f5cfca7a5e78254a81871f6f57d5fc956 GIT binary patch literal 909 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@ssl.cache_meta b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@ssl.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..767437cd312b1a1ba173acb81fc29a99a14667e1 GIT binary patch literal 377 zcmaE6w=9zZ23VjB0VtiGlbV>TpHx|rS{z?el$r_>)?i>@5Ct(97#JY(sYN-7dFlGe z8Hq(XnZ+fV3=9mR3=9k+*c25Mj0Fw`+IFw`?K zFf3qVV0g#G!0?`lfkA+ofkA_rfx(KIfx(lRfgzHafuWX}fuW9>fnhx}1H%Dk28K(_ z3=FTC85p=&7#O%&7#J*B7#Lz$7#Lz%7#L=9 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@tcp.cache b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@tcp.cache new file mode 100644 index 0000000000000000000000000000000000000000..5e0a45b396194271f570659097bbfb400615fadc GIT binary patch literal 6240 zcmWe(fPmtXqQsKYV*Sj#lGLKS#2o#SL228?#f&(DDhF)%VP zFfc(mF!_+8(p0Dr0|P6R<(8OJj1Xmpiuz@um;^EnB!fkofej?Vz`zicT3nh_0<-gr z9z^&nlm?jqlY`Oz*&tDnI*3vRz#K*fMg~T1kQgG4;?vSH zIiNx%$p!IX>p-eOUIEciIY=bI90!eHai}Dei%1W}`N`R-C9n_=2J@h55fm&$(sNQ1 zbK{FkQgSkr1fbGjG5w^N302sW(AFbx&}!LR@Txe6Ob$v(K#04)M*2&CBx0I(8V6z zu+W4yb-nV0gs9 zzyPX#q!<|(^cfi#Y#13BA{ZGMG8h>cx)>Q4x)~W5_AoLqa4<12a56D4fO^GEObiUo zObiU5`2EJj!0?@kfdSNvHDP98aARg*2xVqqNM&YV=wxPK=wfDI0Clj=Ff%aRV`gCZ z&dk6d#=^iL&H`!b6jxszZ(R+`2QYhKorSYb>cy}-tBLXr#o3qUCxX4Vx328O%P@)>X9fjI`&&;ysO3=9lD3=9k*Py{D-e=#yJurWc52Mr=NK-n*t7#O}l z+11Pp4DHMiH5@Dq3?eKL_7WBbh7C|QEO&zQ9W23uT9z~L*3ThyV zMoC$7p=?;D0y_$= zFo0PVkO>(~WME*ZU|?XVhK5le3j@PMsNV<#JFMP;1UoFiw=gg;?1CBw8Vss}y88+v z1H&yS+lz^TApq)!{Y(rDN1<#xW(Ecqs2{E|GceqNvMX5_80w*pf`t#*Z!jl7`ed*y zlFP`zPz<%|9U}w77brV}iGd*xYSncn28O#(b`~=OLjlyPKgG8Vg%+c zSPKwl+)@SxhP6=R9x^g8yo3e_il<-+!!I)jmX5zNGBEsrs?BF&U?_q*gPDbafejik P^H~@e7D3sdBn-j;^ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@tcp.cache_meta b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@tcp.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..a1af1f1b9fb99de5f9dc07eff35bc5aaa0861001 GIT binary patch literal 365 zcmaE6w=9zZ23VjB0VtiGlbV>TpHx|rS{z?el$r_>)?i>@5Ct(97#JY(sYN-7dFlGe z8Hq(XnZ+fV3=9mR3=9k+*c25MmLr`3r=8>$>cfvf=;15z`Ofq~&Z0|UcD1_p*l3=9lh zj0_A?j0_C=j0_Amj0_ABj0_AJj0_B2j0_Cjj0_BW7#SEim>3v1nHU(%m>3wEm>3wE znHU&WFflNEV`5~JF%FMu!%FMvf$;`me#mvC4o0);(3^N16 rJ!S@m@5~GgVk`^{;w%gd!7L06`78_!jVufdlUW!THbF7Sy&wz#P$oVU literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@transport.cache b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@transport.cache new file mode 100644 index 0000000000000000000000000000000000000000..e8714cba59b97349db171399ff725a952c82ab42 GIT binary patch literal 8023 zcmb1QfPmtXqQsKYV*Sj#lGLKS#2o#SqQt!7g8ZTqkUTqxU|?W?s$zt4IiYk2Of3^g z;4=dQ11lo~193)!bbv5eE1EroO~7yg$S4G6g)&*7v{Qb54wT6N@&wpY1_lP0d`MAg zDomaMrr9kqrx+p143+oGL@^0u8b}68R}S)p9@ukpioUJE=kGBOyY$~LrhAk%uCG8Oom0Y3xo?* zf`vly5XkW$HAII1_G|_+0vrY~S3uK(I7k9fq#+8v;{4?7)DoCs!C)R#ErcS&G+0nU zObbd)EY8n^7zQF?K1IquAR!P2NkfDM7#SFZi7Hi@zFn@tO1F~Xprw?o{BvAUm!Ymjq zJV72t!QjAwrLn=BLSRvb)NTL?fiTEJ5MeGx1_oXt%Q%oO8kBK}7E4iTa%yH7Jad9# z5msctq=-++znK^q(!rh`$|)Hd=3ojX<$-<1z`#I-x*BXalt8fqmM5GtOB{=e5-Xu< zAQUKe&`SXVd5p*sEwv;*zo4WTVhyBDg}I~{CP#ca*uu)dpa#yT!!jMf+5y9+5ekkf zjIJ9jF~De)bPCH0G_Q2|q1Ho7qxh1{+|>M1lr9!7C4>_!EG928GcbgKIj9L1ECh7` zw#-M^23Ru)K6n5%4MJfvlR$=s#TBH90x<>)i8Zo_F1TQ^OSA@#U|;}^ zKXEWJFz_)lFo-ZRFo-cSFn~r3)EOBV>=_vt0vQQ4)-f_L9Aso* z_{PYV93P40P3CCGchoPGchn^GBGgJFflO9Wny61z{J3CiHU*X3ljsw z4<-hNUrY=Pp!%JgnSnu>nSsHSnSsHDnSmjTnSr5}nSo&mGXukEW(J1y%nS?{nHd;B zS?U!t0|OHa1A_<)1A{UP1A{RO14ASW149lA1H%#)28KH<3=9ug7#JS0Fff2x!CzPy z7S&!?1&PVoiRr1u`p~|$esNJUoDEiw zGNPyl?j^y}9ekY4K+jOm0O}3|1SOo6!xBYcXBbI{tYFzr3e3=ES& zE?{6_z^T0?xd4au&CCo8y9jA7F3y360h;Rz*%%lqp~WNNFu+lj!TfTQnStR2GvS~h zU`#a&149?IZX@g_909|`%)r1&gj?VN1MTI({8Gfqz)%HE_c-IFxENGcfHMVX)CHz@ zKNADPVQ2}8Q*TLf0o-o1NbzQ4U@+q8hD?7|FGdE2zfgZ% zV`5;q3H8ScW(J1UP=BmoVPIGd^+yjY14BR59}a8`49-x0!1{i8D+pK?feqY4(*%PL z0|P?{bm+mIk%7S<>L6&6gUkYeTnqAuXI=?RJd%-tAqJ|pm63s=1Io5%W?*oFI$n;2 zfk6@Kcs&*d216*DK!pTzDr~$6=F~a{28K4MQ&m|R7<8dw0do?#B8EBDFEa|kPG z=!S-^H7f&y9aNtP8v}zlGzo$F6;BohhEQloIIuD>_&}W_$;QB-N}O|`MH&OdIWULxGB7YqhB{;#BLl-6 zs6!G#!!=Nc=rS`f7(*Sx!@|HI1a*iQD+7ZJ)FIDV85rI|*#yD`-po!e@Gk&0qM&($ zVJ!m#!#1c}P~rzx#DF^0u!zlMWMIgJI`%s&1H*5qBf{Ae?s(XQFDxMcfaW8irIiUI1A{HpZ9Ys44583~lwf9HP=s3h9hB0b)-tj( zFmOTLe~FcW;Wm_wt5pYc1bqA)<}W!01_pI#2o^FiFjPS;MhO&HLV)zgV7z`t28O9n uH#agdFtkIpdNMOG_(R=n!NR~`2X(UvD+7ZS)SbL+3=G21R0zuQAPfK+3lww! literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@transport.cache_meta b/examples/http_interactions/build/lsp/erlang/stratus/_gleam_artefacts/stratus@internal@transport.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d8faea669411b4425c926d20039b953bcb88ca9c GIT binary patch literal 557 zcmaE6w=9zZ23VmC0VtiGlbV>TpHx|rS{z?el$r_>)?i>@5Ct(97#JY(sYN-7dFlGe z8Hq(XnZ+fV3=9mR3=9n7P({ThMTsS)#rm0fC86;Y!VPN24WnhqFWnj=}Wnc(kWnf5VWnk!FWnfs#%D}LZm4RV1D+9w8 yRtAQntPBiySs57KvobJnurV-bvN15&urV+su`w`ourV<7urV<7K`|&SK^OpMnqN`? literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp.cache b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp.cache new file mode 100644 index 0000000000000000000000000000000000000000..38053186afc7a01b47436775545a49396f16ae87 GIT binary patch literal 124959 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 ME%cx=h$RdR0Grg9kpKVy literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp.cache_meta b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..07a01b58fc165501b25176d2cbbdcebfdd18a6d7 GIT binary patch literal 9554 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@internal.cache b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@internal.cache new file mode 100644 index 0000000000000000000000000000000000000000..f0deb0db78e8dc2637428a6e021af0b963531ce6 GIT binary patch literal 9791 zcmd;OfPnJM;sX85ypq(Syu=)k5DSQ4U|;}AFhki~P}(^^FE2H@Br`vc2_)jo$iNWB z$iRTpERZ4)2CGIk7^@CUTR?i@m~kPLsSuKps*ntKib6@gLJ3GDCo?s#L?JD|NFhn#=x@^BQlFp<8w=MN-_%)i%Q}_79{9#alw2Vl$w|V z_Ie!)14B0p)dC9Hd}x?JDK;n#vN^Q~#PMNaV5lKvH6sH90|1F;!kJ_02=tZ4(L0OT>al_0G!W8i`e3=AkP zfH~PO6P&z})WO__&1{gdAmc&GVa7RUl;&l_1VIwW7^VmuOdL=dr_2(^qN2n~C>KP5 z3}HrRNeg0-c4Umzi6}wK2(u_JwW5T9fgh?H6mgk(>28^M znZ+5YDNs=c2Hcq*6iOfrQi`bx7I`2QAPiE4jn1KIq872%hw6OZyWR@Xa|23)5+gXnLCZOitbaC06vP6_fG|uKQfcK{RFq$Yt`24@0|NsONIwGu zLug5wbAC!{L4GEp5Qb?3DS;InNVyUu4Z=92;l;00em=aA1gQtP6r=~1r9+BJ5djNJ zFm8!CNWl#&`e2DJJvA@2D6u3pKD8tf9%~?Tu`7Uyf=VfPvjn6ZT2Mg|6Mh#3q;iFqmcx$(t0r6}13n>1GcK#gIDXJcTf1qTi!L4zEJD+WM% zP`n7%$H2gVmfT=vE-0+9RO+C(!>wck3j@P`+{p~;Hjp#nDU_s0V`RkBr~o+)TVsL| zSk{h}&>iGngFyqbn3wIi$y4DVSP z7#Izp7Qw4&g+v92ISR#Ki(tL+^vtrG8!miNzVI#ZY_So1RO`VwRgc3F@NGA3*bEDk`vfmHn<{ivd#$OK`K3_2!k1DXyL z4KRBKI;+Cc0!E_+AQ+DssWa7crskTHrqu;4?lUJ10`K<435 z0+L3?L|F+-A227u=+Sfq3QAh0D?Bj?i#a?>K(0f^FfDisXjobWMJ`%{9b^^`X^<2$ zMzIQOmcd@gflG1*1_tc1$Too3gl&O2mkt>dmP7#JA*7#JAh85kHqEiKTzQ9T0#LjwZ?Lo)*dLq7up!z2a< zhRF;J3?~>E7|t*-Fi0{oFxWFPFo5P4BN-VO(is^Tsu>v=CNnZHtYKteIL64p@R*T- zfrW{IfrE*Gfs2WO0o1-$W@2ElVq#zjWny4RXJTO3%EZ9%kcokTkC}l%otc3_o0)+j zhnaz)ikX37GBX3iMrHAOA_28MMk3=9WY7#I$*FfbfuVPH7M!oYBcg@NHd3j+hF@z>vVoz)--- zz)-@AkN0XV9&P zb5r%xGV;^Xauf6N^wW#-bM;dz5_1c3Qj7I7N=gdi!85*zkU35Lq|(fs6#at4*mOi@YB8*whA2zQEQwD9%{6jDje`hh=9Qot z2kCgjvNC9Rydbe8LlCM7RV~D7P)-I(7nc;}=cUI(I`HvHl_jagu+aF;%D}(}bv_Pf z!E6A{&6t_Qmloy3r{*Q+r=-HnP-A0YFo&9f#kcGrConKDl;jqG=44>Ht}-z&+yO-l z1CBv4SOSDP8{&Pa28PqD3=B6|85lt6tci_*p`Q)XU4v;tNwoyKhA>kdSQ!{1LE|3` z3=GdfDG6!=R#RcY1xjg1L#Z(T!3Ik~F2t)JZM*`eUxKcQw+ zNUDaJP@I#Q3KN#p0?bXmObiUc(0J!$WnkckW*`w($cPHa zeJ^1Hi=gF6&}j{jxCJ|OUXX!-;Wi@!!$U@hwSO2H7?_}DqBsHMK9I*f^GaZTOlDzV zNP{}}Au9vJ6R7<%>c^eFfh!6YQUPXapw;o=yENP@1vl@ksxDH b>;*}J9Ojpq1Cj$VD_9s9s-aeb_#g}b0&@sa literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@internal.cache_meta b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@internal.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..be30dfcb5b1c509f706e4a92a614f93c7457dd1a GIT binary patch literal 576 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@wisp_mist.cache b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@wisp_mist.cache new file mode 100644 index 0000000000000000000000000000000000000000..19777186163885d6005987609b021cab3c421e92 GIT binary patch literal 6784 zcmd;MfPnJM;sSjT5uck`Tmlkf0TB!g3?M-$lL02i1{DZOEiTO|VS@6mw?Y^@gIX@+}AU`t? z>|~HIOdE*J3?dj982mDGpezQEGzf$ESft@Tbjr_1(F1ZRNDnLoLW)XLL3}WVx!Wx< zrx;Td7Q~={jW15kONmd*%t?hg0%S0D6$~(8Pz)gA2IlH%oD2-VIYIU_GB7eiLKs{rrNG z%=|o`&lv-SnpI4mfl30=mw}2PQ0x66yf~tVI z1r#4x72t>tm_9oW28J#Ukjbdg0h0&GfG`(`U|?W?gj7aJNr5o3h(1U}ALb~qc?=8; zFmIq62J6aO-G_=;sj7&!khqNa{ zfoOQZ#=zjf&cMLH2+9+PT!9{PQ2XIU4mdhssS~wWgDFM!9Woo1TAe{YfGLL4$SRT9 z;G7GN+Qbxac?Z{l!GKvnz>T1k3(pXs6ah=dAhv#HUP)?EUSbZ+4MC}iDHt}v1W;^* z<$I)3lz{;h#30ofiFqkGsYM_r2!q^!E`_}q1E~b*xWdf9P|Cu&nzxbC`v6XO)V}_fZC*xnOBmpkf@MWnwJbJQ4~rt5=#`46Y~_3QWc6y^AyT6 zOEMHnGExU+GLK$fYB{PvL4Hw*hJ}HF0h+?% zlEk8tcu=7MQeRM%pPX7;tXG_qnpzN_mS2=wmRh8t$)!*W_AM8eLP$m`)LRPKsg(*~ zZ)FxMloqF^D5T{VDHLa>=Vj)lDZu5G)UZQU%sb3bV{PKQAv8oXlbR z5j0E;p3e#D*7q;2470NcVsR zIRzH&AU6K$Q2-P=3=9k)mGMdWDV6a>sfj76C~aa~Y6w)2-&q+LT)=SzN&BEQ4~iEM zhT00IU?GJ_Ua(LFi-1E@A6m>2o8Cd*ftdng6CXDq*MTtDX=nsW>Vs8Q*cz!YeJC2h zK>@CZk-IV=rJ#_ZTYU<$5QMWp1QZK0FfeE`Fff2xu5%a|7&b64FdSiEV7S4+!0?2D zf#CzF%f!gQz{beHz{$wKAkWCapv1_)pv=g?P{PQ-(89>T(80*SFrATsVKyTJ!wNC#^SQr>|Sr`~9SQr>)urM&JWnp0W!NR~`z{Fc+ASc@PUv1inUjHGDklR2s3*0OlYwC;Cj-L?P6mdjoD2;AI2jmpxfmFnxEL5BxEL5( zxEL7baWOC)P)lY@C zJHQzy9^4vB1eff^`bnjkIVt)DiOJcZ`V-vK(=RSchA=>N1f=Bv&Ua8N5PfAsJrg|x zxG0PP%e}B72V@GYzyO=0pHx|rS{z?el$r`FiXh@e;4VF^b(&g{oCBMwv0`v16Kh3>r^U`WB@0;qWm7=t)mTnr4t zP*))P4_3JpmlUNY=E6J}%*MbF36+Am0~GP_>JZlT%}vZsjZcQR0b%<0urM&3hFXI? z>R|?;b+2K8#>c_HAj<(s(2v;|7(PK$!A>>?h7-`ljT*C%EDQ}2hRnR;g4ASC6$6TA zh7v9YhH5n5gZu*vuAKaI)bLH_VqnODYJepHaKQrOfIGynbn3>%z~BwdikWN-44^?I zP*7m^I!qLlybz;pFtcWIGB7OXgrs?AP6mcRPDm&~LlfNKg0Wrl^WdX&i`W?$mO;Ib zyC{UFJqGlgF2u^fAO-asEKuMjD^#2zB{dDvz;k0|VDN^@z}y20QW!fVwE`aeJ2)5^ z_CT%1o18#h{>;2|x6HiE;*8W3SeR^PXJ9x4^;;(!1H%kx(u9>C;Fv+GFJP%CFSQ&L z4WJ~ji;IC_FEm175dd;7xC#T;UQqKHpxs}X#@oyc43C&0@d5QNWMm9xiC<<8Ol>MV z149NoB))LDjT__|1_p+-qWs)=NTP*lzr(@6@EjUUuwVqa5oRs8Sb-`2!NI`r7pfR* zn8I|Slw9B085sUReG1DzFsnf65vwLnb_NDMs3sz65!g^5%&aB`28J%^KsyHu1A_q6 z!Y~#Fh9nk9X&%SHz>o&@O*RJuLpcXT9A*YMhM}Ai=~SU__r0|Ns9%_m6) literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@wisp_mist.cache_meta b/examples/http_interactions/build/lsp/erlang/wisp/_gleam_artefacts/wisp@wisp_mist.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..65a9ba261cba0cf13e67aac6e44dd293d9e85f57 GIT binary patch literal 752 zcmZ=PKbFY=16)uBCzMXDNKP#%$;{6K@dX$d7(f`rPR~hA%+*h-EJ-bnFDXh*6=Yyw z&|qL-5CTauFfc&mGfGMd^ovppOH+$WG#MBe5*Qd5gt4h8F38U-PEBNBV3^Ioz`%o| zBp>X6ISdR8>oMetQj1G-N;WVsFdW8^D=sO@%u7GQz`$^wfq{VqYG!U`amft^28PE- zV&$2|1y2|l7~V55Fz`avfyDGP^GZ^S@)C1CfD|(_FdX~Wyw9>JARr5>2t1_pUX1_mWY1_otD28I$w28I?!28Iqs28QX3 z3=Fdw85mYDGBE66WMH_*$iVQOk%57aiGjg}iGd-2iGd-TiGiVwiGg7W69dC4CI*I0 zObiU$m>3usm>C#+m>C#mFf%Y*U}j*r!py+%gqeXsh=qYcmxY0$f`x%$1`7kjS{4R| zA1n+E2CNJWhO7(>!K@4n6ImG;X0S3a%w%O?Si{P|u#1&};WR4)!y{G(hR3W73?En- z7{0JFFnnWWVEE3;z~Iitz>vzuz%Yl6f#D__1H)Z528R1=3=9w07#O~=F)%2yGcY)_ zGcb6wGcaVaGcdHVGcZhNXJ9zY&cN`Doq@rCgMq<}gMq=CgMq=8gMq=0gMp!!gMnc^ z2Lr=<4h9A(P6mc_P6mc-P6mcDP6md_oD2+8IT;wHaWXKhh literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/bigi/LICENSE b/examples/http_interactions/build/packages/bigi/LICENSE new file mode 100644 index 0000000..efd7dbe --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) Mikko Ahlroth, 2024. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/http_interactions/build/packages/bigi/README.md b/examples/http_interactions/build/packages/bigi/README.md new file mode 100644 index 0000000..05b3700 --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/README.md @@ -0,0 +1,77 @@ +# bigi + +[![Package Version](https://img.shields.io/hexpm/v/bigi)](https://hex.pm/packages/bigi) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/bigi/) + +Arbitrary precision integer arithmetic for Gleam. + +In the Erlang target, all integers are automatically "big integers" that are subject to +arbitrary precision arithmetic. This means there is no need for this package if you are only +targeting Erlang. + +In JavaScript, Gleam's `Int` type corresponds to the `number` type, which is implemented using +floating-point arithmetic. That means it's subject to the following limits: + +- [Maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) + and + [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER) + beyond which the accuracy of arithmetic suffers, and +- [maximum value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE) + and + [minimum value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_VALUE) + beyond which the numbers are converted to `Infinity` or `-Infinity`. + +This package thus provides big integers for the JavaScript target and additionally provides a +consistent interface for packages that target both Erlang and JavaScript. In Erlang, regular +integers are used. In JavaScript, the +[`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) +type is used. + +```sh +gleam add bigi +``` + +```gleam +import bigi + +pub fn main() { + bigi.power( + bigi.from_int(2), + bigi.from_int(65_535) + ) + + // Ok(1001764965203423232489536175780127875223912737784875709632508486855447029778...) +} +``` + +Further documentation can be found at . + +## Limitations + +Erlang does have [a limit for big integers at around 4 megabits](https://elixirforum.com/t/is-there-an-integer-size-limit/65647/8?u=nicd), +as demonstrated below: + +```gleam +import bigi + +pub fn main() { + // Works + bigi.power(bigi.from_int(2), bigi.from_int(4_194_239)) + + // Blows up + bigi.power(bigi.from_int(2), bigi.from_int(4_194_240)) +} +``` + +For Node.js, the limit is somewhere higher. + +`bigi` has no facilities to warn about or handle errors due to these system limits. It is up to the developer +to be mindful of them. + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +gleam shell # Run an Erlang shell +``` diff --git a/examples/http_interactions/build/packages/bigi/gleam.toml b/examples/http_interactions/build/packages/bigi/gleam.toml new file mode 100644 index 0000000..5fdf030 --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/gleam.toml @@ -0,0 +1,23 @@ +name = "bigi" +version = "4.1.1" +gleam = ">= 1.13.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +description = "Arbitrary precision integer arithmetic for Gleam" +licences = ["MIT"] +repository = { type = "forgejo", host = "git.ahlcode.fi", user = "nicd", repo = "bigi" } +links = [ + { title = "Changelog", href = "https://git.ahlcode.fi/nicd/bigi/src/branch/trunk/CHANGELOG.txt" }, + { title = "Sponsor", href = "https://liberapay.com/Nicd" }, +] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.60.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/bigi/src/bigi.app.src b/examples/http_interactions/build/packages/bigi/src/bigi.app.src new file mode 100644 index 0000000..6f942b6 --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/src/bigi.app.src @@ -0,0 +1,9 @@ +{application, bigi, [ + {vsn, "4.1.1"}, + {applications, [gleam_stdlib]}, + {description, "Arbitrary precision integer arithmetic for Gleam"}, + {modules, [bigi, + bigi@@main, + bigi_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/bigi/src/bigi.erl b/examples/http_interactions/build/packages/bigi/src/bigi.erl new file mode 100644 index 0000000..f9b194f --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/src/bigi.erl @@ -0,0 +1,773 @@ +-module(bigi). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/bigi.gleam"). +-export([zero/0, one/0, negative_one/0, ten/0, from_int/1, from_string/1, from_bytes/3, to_int/1, to_string/1, to_bytes/4, compare/2, absolute/1, negate/1, add/2, subtract/2, multiply/2, divide/2, divide_no_zero/2, remainder/2, floor_divide/2, remainder_no_zero/2, modulo/2, modulo_no_zero/2, power/2, do_decode/1, decoder/0, bitwise_and/2, bitwise_exclusive_or/2, bitwise_not/1, bitwise_or/2, bitwise_shift_left/2, bitwise_shift_right/2, is_odd/1, max/2, min/2, clamp/3, sum/1, product/1, undigits/2, digits/1, from_base2/1, from_base8/1, from_base16/1, from_base/3, to_base2/1, to_base8/1, to_base16/1, to_base/3]). +-export_type([big_int/0, endianness/0, signedness/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type big_int() :: any(). + +-type endianness() :: little_endian | big_endian. + +-type signedness() :: signed | unsigned. + +-file("src/bigi.gleam", 28). +?DOC(" Create a big integer representing zero.\n"). +-spec zero() -> big_int(). +zero() -> + bigi_ffi:zero(). + +-file("src/bigi.gleam", 33). +?DOC(" Create a big integer representing one.\n"). +-spec one() -> big_int(). +one() -> + bigi_ffi:one(). + +-file("src/bigi.gleam", 38). +?DOC(" Create a big integer representing negative one.\n"). +-spec negative_one() -> big_int(). +negative_one() -> + bigi_ffi:n_one(). + +-file("src/bigi.gleam", 43). +?DOC(" Create a big integer representing ten.\n"). +-spec ten() -> big_int(). +ten() -> + bigi_ffi:ten(). + +-file("src/bigi.gleam", 56). +?DOC( + " Create a big integer from a regular integer.\n" + "\n" + " Note that in the JavaScript target, if your integer is bigger than the\n" + " [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\n" + " or smaller than the\n" + " [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER),\n" + " you may lose precision when operating on it, including when converting it\n" + " into a big integer (as the JavaScript Number type has already reduced the\n" + " precision of the value).\n" +). +-spec from_int(integer()) -> big_int(). +from_int(Int) -> + bigi_ffi:from(Int). + +-file("src/bigi.gleam", 64). +?DOC( + " Convert a string into a big integer.\n" + "\n" + " If the string does not represent a big integer in base 10, an error is\n" + " returned. Trailing non-digit content is not allowed.\n" +). +-spec from_string(binary()) -> {ok, big_int()} | {error, nil}. +from_string(Str) -> + bigi_ffi:from_string(Str). + +-file("src/bigi.gleam", 72). +?DOC( + " Convert raw bytes into a big integer.\n" + " \n" + " If the bit array does not contain a whole number of bytes then an error is\n" + " returned.\n" +). +-spec from_bytes(bitstring(), endianness(), signedness()) -> {ok, big_int()} | + {error, nil}. +from_bytes(Bytes, Endianness, Signedness) -> + bigi_ffi:from_bytes(Bytes, Endianness, Signedness). + +-file("src/bigi.gleam", 87). +?DOC( + " Convert a big integer to a regular integer.\n" + "\n" + " In Erlang, this cannot fail, as all Erlang integers are big integers. In the\n" + " JavaScript target, this will fail if the integer is bigger than the\n" + " [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\n" + " or smaller than the\n" + " [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER).\n" +). +-spec to_int(big_int()) -> {ok, integer()} | {error, nil}. +to_int(Bigint) -> + bigi_ffi:to(Bigint). + +-file("src/bigi.gleam", 92). +?DOC(" Convert the big integer into a simple string - a sequence of digits.\n"). +-spec to_string(big_int()) -> binary(). +to_string(Bigint) -> + erlang:integer_to_binary(Bigint). + +-file("src/bigi.gleam", 101). +?DOC( + " Convert a big integer to raw bytes.\n" + " \n" + " The size of the returned bit array is specified by `byte_count`, e.g. 8 will\n" + " return a bit array containing 8 bytes (64 bits). If the big integer doesn't\n" + " fit in the specified number of bytes then an error is returned.\n" +). +-spec to_bytes(big_int(), endianness(), signedness(), integer()) -> {ok, + bitstring()} | + {error, nil}. +to_bytes(Bigint, Endianness, Signedness, Byte_count) -> + bigi_ffi:to_bytes(Bigint, Endianness, Signedness, Byte_count). + +-file("src/bigi.gleam", 112). +?DOC( + " Compare two big integers, returning an order that denotes if the first\n" + " argument is lower, bigger than, or equal to the second.\n" +). +-spec compare(big_int(), big_int()) -> gleam@order:order(). +compare(A, B) -> + bigi_ffi:compare(A, B). + +-file("src/bigi.gleam", 117). +?DOC(" Get the absolute value of a big integer.\n"). +-spec absolute(big_int()) -> big_int(). +absolute(Bigint) -> + erlang:abs(Bigint). + +-file("src/bigi.gleam", 122). +?DOC(" Returns the negative of the value provided.\n"). +-spec negate(big_int()) -> big_int(). +negate(Bigint) -> + bigi_ffi:negate(Bigint). + +-file("src/bigi.gleam", 127). +?DOC(" Add two big integers together.\n"). +-spec add(big_int(), big_int()) -> big_int(). +add(A, B) -> + bigi_ffi:add(A, B). + +-file("src/bigi.gleam", 132). +?DOC(" Subtract the subtrahend from the minuend.\n"). +-spec subtract(big_int(), big_int()) -> big_int(). +subtract(A, B) -> + bigi_ffi:subtract(A, B). + +-file("src/bigi.gleam", 137). +?DOC(" Multiply two big integers together.\n"). +-spec multiply(big_int(), big_int()) -> big_int(). +multiply(A, B) -> + bigi_ffi:multiply(A, B). + +-file("src/bigi.gleam", 144). +?DOC( + " Divide the dividend with the divisor using integer division.\n" + "\n" + " Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.\n" +). +-spec divide(big_int(), big_int()) -> big_int(). +divide(A, B) -> + bigi_ffi:divide(A, B). + +-file("src/bigi.gleam", 177). +?DOC( + " Divide the dividend with the divisor using integer division.\n" + "\n" + " Returns an error if the divisor is 0.\n" +). +-spec divide_no_zero(big_int(), big_int()) -> {ok, big_int()} | {error, nil}. +divide_no_zero(A, B) -> + bigi_ffi:divide_no_zero(A, B). + +-file("src/bigi.gleam", 188). +?DOC( + " Divide the dividend with the divisor using integer division and return the\n" + " remainder.\n" + "\n" + " Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.\n" +). +-spec remainder(big_int(), big_int()) -> big_int(). +remainder(A, B) -> + bigi_ffi:remainder(A, B). + +-file("src/bigi.gleam", 153). +?DOC( + " Performs a *floored* integer division, which means that the result will\n" + " always be rounded towards negative infinity.\n" + "\n" + " If you want to perform truncated integer division (rounding towards zero),\n" + " use `divide` or `divide_no_zero` instead.\n" + "\n" + " Returns an error if the divisor is 0.\n" +). +-spec floor_divide(big_int(), big_int()) -> {ok, big_int()} | {error, nil}. +floor_divide(Dividend, Divisor) -> + Z = bigi_ffi:zero(), + case Divisor =:= Z of + true -> + {error, nil}; + + false -> + case bigi_ffi:compare(bigi_ffi:multiply(Dividend, Divisor), Z) of + lt -> + case bigi_ffi:remainder(Dividend, Divisor) /= Z of + true -> + {ok, + bigi_ffi:subtract( + bigi_ffi:divide(Dividend, Divisor), + bigi_ffi:one() + )}; + + false -> + {ok, bigi_ffi:divide(Dividend, Divisor)} + end; + + _ -> + {ok, bigi_ffi:divide(Dividend, Divisor)} + end + end. + +-file("src/bigi.gleam", 196). +?DOC( + " Divide the dividend with the divisor using integer division and return the\n" + " remainder.\n" + "\n" + " Returns an error if the divisor is 0.\n" +). +-spec remainder_no_zero(big_int(), big_int()) -> {ok, big_int()} | {error, nil}. +remainder_no_zero(A, B) -> + bigi_ffi:remainder_no_zero(A, B). + +-file("src/bigi.gleam", 206). +?DOC( + " Calculate a mathematical modulo operation.\n" + "\n" + " Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.\n" +). +-spec modulo(big_int(), big_int()) -> big_int(). +modulo(A, B) -> + bigi_ffi:modulo(A, B). + +-file("src/bigi.gleam", 213). +?DOC( + " Calculate a mathematical modulo operation.\n" + "\n" + " Returns an error if the divisor is 0.\n" +). +-spec modulo_no_zero(big_int(), big_int()) -> {ok, big_int()} | {error, nil}. +modulo_no_zero(A, B) -> + bigi_ffi:modulo_no_zero(A, B). + +-file("src/bigi.gleam", 223). +?DOC( + " Raise the base to the exponent.\n" + "\n" + " If the exponent is negative, an error is returned.\n" +). +-spec power(big_int(), big_int()) -> {ok, big_int()} | {error, nil}. +power(A, B) -> + bigi_ffi:power(A, B). + +-file("src/bigi.gleam", 242). +?DOC(false). +-spec do_decode(gleam@dynamic:dynamic_()) -> {ok, big_int()} | + {error, big_int()}. +do_decode(Dyn) -> + bigi_ffi:decode(Dyn). + +-file("src/bigi.gleam", 234). +?DOC( + " Returns a decoder that decodes a Dynamic value into a big integer, if\n" + " possible.\n" +). +-spec decoder() -> gleam@dynamic@decode:decoder(big_int()). +decoder() -> + gleam@dynamic@decode:new_primitive_decoder( + <<"BigInt"/utf8>>, + fun bigi_ffi:decode/1 + ). + +-file("src/bigi.gleam", 251). +?DOC(" Calculates the bitwise AND of its arguments.\n"). +-spec bitwise_and(big_int(), big_int()) -> big_int(). +bitwise_and(A, B) -> + bigi_ffi:bitwise_and(A, B). + +-file("src/bigi.gleam", 256). +?DOC(" Calculates the bitwise XOR of its arguments.\n"). +-spec bitwise_exclusive_or(big_int(), big_int()) -> big_int(). +bitwise_exclusive_or(A, B) -> + bigi_ffi:bitwise_exclusive_or(A, B). + +-file("src/bigi.gleam", 261). +?DOC(" Calculates the bitwise NOT of its argument.\n"). +-spec bitwise_not(big_int()) -> big_int(). +bitwise_not(Bigint) -> + bigi_ffi:bitwise_not(Bigint). + +-file("src/bigi.gleam", 266). +?DOC(" Calculates the bitwise OR of its arguments.\n"). +-spec bitwise_or(big_int(), big_int()) -> big_int(). +bitwise_or(A, B) -> + bigi_ffi:bitwise_or(A, B). + +-file("src/bigi.gleam", 271). +?DOC(" Calculates the result of an arithmetic left bitshift by the given amount.\n"). +-spec bitwise_shift_left(big_int(), integer()) -> big_int(). +bitwise_shift_left(Bigint, Amount) -> + bigi_ffi:bitwise_shift_left(Bigint, Amount). + +-file("src/bigi.gleam", 276). +?DOC(" Calculates the result of an arithmetic right bitshift by the given amount.\n"). +-spec bitwise_shift_right(big_int(), integer()) -> big_int(). +bitwise_shift_right(Bigint, Amount) -> + bigi_ffi:bitwise_shift_right(Bigint, Amount). + +-file("src/bigi.gleam", 286). +?DOC(" Returns whether the big integer provided is odd.\n"). +-spec is_odd(big_int()) -> boolean(). +is_odd(Bigint) -> + bigi_ffi:remainder(Bigint, bigi_ffi:from(2)) /= bigi_ffi:zero(). + +-file("src/bigi.gleam", 291). +?DOC(" Compares two big integers, returning the larger of the two.\n"). +-spec max(big_int(), big_int()) -> big_int(). +max(A, B) -> + case bigi_ffi:compare(A, B) of + lt -> + B; + + _ -> + A + end. + +-file("src/bigi.gleam", 299). +?DOC(" Compares two big integers, returning the smaller of the two.\n"). +-spec min(big_int(), big_int()) -> big_int(). +min(A, B) -> + case bigi_ffi:compare(A, B) of + lt -> + A; + + _ -> + B + end. + +-file("src/bigi.gleam", 279). +?DOC(" Restricts a big integer between a lower and upper bound.\n"). +-spec clamp(big_int(), big_int(), big_int()) -> big_int(). +clamp(Bigint, Min_bound, Max_bound) -> + _pipe = Bigint, + _pipe@1 = min(_pipe, Max_bound), + max(_pipe@1, Min_bound). + +-file("src/bigi.gleam", 309). +?DOC( + " Sums a list of big integers.\n" + "\n" + " Returns 0 if the list was empty.\n" +). +-spec sum(list(big_int())) -> big_int(). +sum(Bigints) -> + gleam@list:fold(Bigints, bigi_ffi:zero(), fun bigi_ffi:add/2). + +-file("src/bigi.gleam", 316). +?DOC( + " Multiplies a list of big integers.\n" + "\n" + " Returns 1 if the list was empty.\n" +). +-spec product(list(big_int())) -> big_int(). +product(Bigints) -> + gleam@list:fold(Bigints, bigi_ffi:one(), fun bigi_ffi:multiply/2). + +-file("src/bigi.gleam", 323). +?DOC( + " Joins a list of digits into a single value. Returns an error if the base is\n" + " less than 2 or if the list contains a digit greater than or equal to the\n" + " specified base.\n" +). +-spec undigits(list(integer()), integer()) -> {ok, big_int()} | {error, nil}. +undigits(Digits, Base) -> + case Base < 2 of + true -> + {error, nil}; + + false -> + Base@1 = bigi_ffi:from(Base), + gleam@list:try_fold( + Digits, + bigi_ffi:zero(), + fun(Acc, Digit) -> + Digit@1 = bigi_ffi:from(Digit), + case bigi_ffi:compare(Digit@1, Base@1) of + gt -> + {error, nil}; + + eq -> + {error, nil}; + + _ -> + {ok, + bigi_ffi:add( + bigi_ffi:multiply(Acc, Base@1), + Digit@1 + )} + end + end + ) + end. + +-file("src/bigi.gleam", 339). +-spec get_digit(big_int(), list(integer()), big_int()) -> list(integer()). +get_digit(Bigint, Digits, Divisor) -> + case bigi_ffi:compare(Bigint, Divisor) of + lt -> + Digit@1 = case bigi_ffi:to(Bigint) of + {ok, Digit} -> Digit; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"bigi"/utf8>>, + function => <<"get_digit"/utf8>>, + line => 342, + value => _assert_fail, + start => 11791, + 'end' => 11828, + pattern_start => 11802, + pattern_end => 11811}) + end, + [Digit@1 | Digits]; + + _ -> + Digit@3 = case begin + _pipe = bigi_ffi:remainder(Bigint, Divisor), + bigi_ffi:to(_pipe) + end of + {ok, Digit@2} -> Digit@2; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"bigi"/utf8>>, + function => <<"get_digit"/utf8>>, + line => 346, + value => _assert_fail@1, + start => 11876, + 'end' => 11953, + pattern_start => 11887, + pattern_end => 11896}) + end, + Digits@1 = [Digit@3 | Digits], + get_digit(bigi_ffi:divide(Bigint, Divisor), Digits@1, Divisor) + end. + +-file("src/bigi.gleam", 228). +?DOC( + " Get the digits in a given bigint as a list of integers in base 10.\n" + "\n" + " The list is ordered starting from the most significant digit.\n" +). +-spec digits(big_int()) -> list(integer()). +digits(Bigint) -> + get_digit(Bigint, [], bigi_ffi:ten()). + +-file("src/bigi.gleam", 364). +?DOC( + " Parse a binary string into a big integer.\n" + "\n" + " The string may contain an optional dash at the start to denote a negative\n" + " number, followed by an optional `0b` prefix. Following those, only 0 and 1\n" + " are allowed. The string is NOT trimmed for whitespace.\n" + "\n" + " Note that no conversion is done for the number. This means that it is always\n" + " treated as unsigned, and will only be negative if it is preceded by a dash.\n" + " As an example, `\"0b100\"` returns 4, and `\"-0b100\"` returns -4.\n" +). +-spec from_base2(binary()) -> {ok, big_int()} | {error, nil}. +from_base2(Base2) -> + {Sign, Rest@1} = case Base2 of + <<"-"/utf8, Rest/binary>> -> + {bigi_ffi:n_one(), Rest}; + + _ -> + {bigi_ffi:one(), Base2} + end, + Maybe_parsed = case Rest@1 of + <<"0b"/utf8, Rest@2/binary>> -> + bigi_ffi:from_base2(Rest@2); + + _ -> + bigi_ffi:from_base2(Rest@1) + end, + gleam@result:'try'( + Maybe_parsed, + fun(Parsed) -> {ok, bigi_ffi:multiply(Sign, Parsed)} end + ). + +-file("src/bigi.gleam", 384). +?DOC( + " Parse an octal string into a big integer.\n" + "\n" + " The string may contain an optional dash at the start to denote a negative\n" + " number, followed by an optional `0o` prefix. Following those, only numbers 0\n" + " through 7 are allowed. The string is NOT trimmed for whitespace.\n" +). +-spec from_base8(binary()) -> {ok, big_int()} | {error, nil}. +from_base8(Base8) -> + {Sign, Rest@1} = case Base8 of + <<"-"/utf8, Rest/binary>> -> + {bigi_ffi:n_one(), Rest}; + + _ -> + {bigi_ffi:one(), Base8} + end, + Maybe_parsed = case Rest@1 of + <<"0o"/utf8, Rest@2/binary>> -> + bigi_ffi:from_base8(Rest@2); + + _ -> + bigi_ffi:from_base8(Rest@1) + end, + gleam@result:'try'( + Maybe_parsed, + fun(Parsed) -> {ok, bigi_ffi:multiply(Sign, Parsed)} end + ). + +-file("src/bigi.gleam", 406). +?DOC( + " Parse a hexadecimal string into a big integer.\n" + "\n" + " The string may contain an optional dash at the start to denote a negative\n" + " number, followed by an optional `0x` prefix. Following those, only numbers 0\n" + " through 9 and letters _a_ through _f_ are allowed. The string is NOT trimmed\n" + " for whitespace. The string may be upper, lower, or mixed case, but the\n" + " prefix `0x` must always be lower case.\n" +). +-spec from_base16(binary()) -> {ok, big_int()} | {error, nil}. +from_base16(Base16) -> + {Sign, Rest@1} = case Base16 of + <<"-"/utf8, Rest/binary>> -> + {bigi_ffi:n_one(), Rest}; + + _ -> + {bigi_ffi:one(), Base16} + end, + Maybe_parsed = case Rest@1 of + <<"0x"/utf8, Rest@2/binary>> -> + bigi_ffi:from_base16(Rest@2); + + _ -> + bigi_ffi:from_base16(Rest@1) + end, + gleam@result:'try'( + Maybe_parsed, + fun(Parsed) -> {ok, bigi_ffi:multiply(Sign, Parsed)} end + ). + +-file("src/bigi.gleam", 441). +?DOC( + " Parse a big integer from an arbitrary base.\n" + "\n" + " The passed alphabet function must return `Ok(n)` for a given character,\n" + " where _n_ is the base-10 numerical value of that character. This allows\n" + " using any kind of alphabet. The alphabet must contain enough characters to\n" + " cover the entire value range of the chosen base.\n" + "\n" + " The base must be positive and larger than 1.\n" +). +-spec from_base( + binary(), + integer(), + fun((binary()) -> {ok, integer()} | {error, nil}) +) -> {ok, big_int()} | {error, nil}. +from_base(Input, Base, Alphabet) -> + gleam@bool:guard( + Base =< 1, + {error, nil}, + fun() -> + Base_b = bigi_ffi:from(Base), + Res = begin + _pipe = Input, + _pipe@1 = gleam@string:to_graphemes(_pipe), + _pipe@2 = lists:reverse(_pipe@1), + gleam@list:try_fold( + _pipe@2, + {bigi_ffi:zero(), bigi_ffi:zero()}, + fun(Acc, Char) -> + {Value, I} = Acc, + case Alphabet(Char) of + {ok, Int} when Int >= Base -> + {error, nil}; + + {ok, Int@1} -> + P@1 = case bigi_ffi:power(Base_b, I) of + {ok, P} -> P; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"bigi"/utf8>>, + function => <<"from_base"/utf8>>, + line => 460, + value => _assert_fail, + start => 15697, + 'end' => 15732, + pattern_start => 15708, + pattern_end => 15713} + ) + end, + Value@1 = begin + _pipe@3 = Int@1, + _pipe@4 = bigi_ffi:from(_pipe@3), + _pipe@5 = bigi_ffi:multiply(_pipe@4, P@1), + bigi_ffi:add(_pipe@5, Value) + end, + {ok, {Value@1, bigi_ffi:add(I, bigi_ffi:one())}}; + + {error, nil} -> + {error, nil} + end + end + ) + end, + case Res of + {ok, {Res@1, _}} -> + {ok, Res@1}; + + {error, nil} -> + {error, nil} + end + end + ). + +-file("src/bigi.gleam", 480). +?DOC( + " Stringify a big integer into a binary string.\n" + "\n" + " A dash is added at the front if the number is negative. The number that\n" + " follows will always be an unsigned binary number.\n" +). +-spec to_base2(big_int()) -> binary(). +to_base2(Int) -> + bigi_ffi:to_base2(Int). + +-file("src/bigi.gleam", 487). +?DOC( + " Stringify a big integer into an octal string.\n" + "\n" + " A dash is added at the front if the number is negative.\n" +). +-spec to_base8(big_int()) -> binary(). +to_base8(Int) -> + bigi_ffi:to_base8(Int). + +-file("src/bigi.gleam", 494). +?DOC( + " Stringify a big integer into a hexadecimal string.\n" + "\n" + " A dash is added at the front if the number is negative. The resulting string\n" + " will be lower case.\n" +). +-spec to_base16(big_int()) -> binary(). +to_base16(Int) -> + _pipe = bigi_ffi:to_base16(Int), + string:lowercase(_pipe). + +-file("src/bigi.gleam", 525). +-spec do_to_base( + binary(), + big_int(), + big_int(), + fun((integer()) -> {ok, binary()} | {error, nil}) +) -> {ok, binary()} | {error, nil}. +do_to_base(Acc, Value, Base, Alphabet) -> + case bigi_ffi:compare(Value, Base) of + lt -> + I@1 = case bigi_ffi:to(Value) of + {ok, I} -> I; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"bigi"/utf8>>, + function => <<"do_to_base"/utf8>>, + line => 534, + value => _assert_fail, + start => 17930, + 'end' => 17962, + pattern_start => 17941, + pattern_end => 17946}) + end, + case Alphabet(I@1) of + {ok, C} -> + {ok, <>}; + + {error, nil} -> + {error, nil} + end; + + _ -> + Rem = bigi_ffi:remainder(Value, Base), + Mod_i@1 = case bigi_ffi:to(Rem) of + {ok, Mod_i} -> Mod_i; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"bigi"/utf8>>, + function => <<"do_to_base"/utf8>>, + line => 545, + value => _assert_fail@1, + start => 18217, + 'end' => 18251, + pattern_start => 18228, + pattern_end => 18237}) + end, + case Alphabet(Mod_i@1) of + {ok, C@1} -> + Acc@1 = <>, + New_value = bigi_ffi:divide( + bigi_ffi:subtract(Value, Rem), + Base + ), + do_to_base(Acc@1, New_value, Base, Alphabet); + + {error, nil} -> + {error, nil} + end + end. + +-file("src/bigi.gleam", 509). +?DOC( + " Stringify a big integer into a number of arbitrary base.\n" + "\n" + " The passed alphabet function must return `Ok(c)` for a given base-10\n" + " integer, where _c_ is the symbol of that integer in the given base. This\n" + " allows using any kind of alphabet. The alphabet must contain enough symbols\n" + " to cover the entire value range of the chosen base.\n" + "\n" + " The base must be positive and larger than 1.\n" +). +-spec to_base( + big_int(), + integer(), + fun((integer()) -> {ok, binary()} | {error, nil}) +) -> {ok, binary()} | {error, nil}. +to_base(Input, Base, Alphabet) -> + gleam@bool:guard( + Base =< 1, + {error, nil}, + fun() -> + gleam@result:'try'( + do_to_base(<<""/utf8>>, Input, bigi_ffi:from(Base), Alphabet), + fun(Res) -> + Sign = case bigi_ffi:compare(Input, bigi_ffi:zero()) of + lt -> + <<"-"/utf8>>; + + _ -> + <<""/utf8>> + end, + {ok, <>} + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/bigi/src/bigi.gleam b/examples/http_interactions/build/packages/bigi/src/bigi.gleam new file mode 100644 index 0000000..3d37fe0 --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/src/bigi.gleam @@ -0,0 +1,557 @@ +import gleam/bool +import gleam/dynamic/decode +import gleam/list +import gleam/order +import gleam/result +import gleam/string + +/// A big integer. +pub type BigInt + +/// Endianness specifier used when converting between a big integer and +/// raw bytes. +pub type Endianness { + LittleEndian + BigEndian +} + +/// Signedness specifier used when converting between a big integer and +/// raw bytes. +pub type Signedness { + Signed + Unsigned +} + +/// Create a big integer representing zero. +@external(erlang, "bigi_ffi", "zero") +@external(javascript, "./bigi_ffi.mjs", "zero") +pub fn zero() -> BigInt + +/// Create a big integer representing one. +@external(erlang, "bigi_ffi", "one") +@external(javascript, "./bigi_ffi.mjs", "one") +pub fn one() -> BigInt + +/// Create a big integer representing negative one. +@external(erlang, "bigi_ffi", "n_one") +@external(javascript, "./bigi_ffi.mjs", "n_one") +pub fn negative_one() -> BigInt + +/// Create a big integer representing ten. +@external(erlang, "bigi_ffi", "ten") +@external(javascript, "./bigi_ffi.mjs", "ten") +pub fn ten() -> BigInt + +/// Create a big integer from a regular integer. +/// +/// Note that in the JavaScript target, if your integer is bigger than the +/// [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +/// or smaller than the +/// [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER), +/// you may lose precision when operating on it, including when converting it +/// into a big integer (as the JavaScript Number type has already reduced the +/// precision of the value). +@external(erlang, "bigi_ffi", "from") +@external(javascript, "./bigi_ffi.mjs", "from") +pub fn from_int(int: Int) -> BigInt + +/// Convert a string into a big integer. +/// +/// If the string does not represent a big integer in base 10, an error is +/// returned. Trailing non-digit content is not allowed. +@external(erlang, "bigi_ffi", "from_string") +@external(javascript, "./bigi_ffi.mjs", "from_string") +pub fn from_string(str: String) -> Result(BigInt, Nil) + +/// Convert raw bytes into a big integer. +/// +/// If the bit array does not contain a whole number of bytes then an error is +/// returned. +@external(erlang, "bigi_ffi", "from_bytes") +@external(javascript, "./bigi_ffi.mjs", "from_bytes") +pub fn from_bytes( + bytes: BitArray, + endianness: Endianness, + signedness: Signedness, +) -> Result(BigInt, Nil) + +/// Convert a big integer to a regular integer. +/// +/// In Erlang, this cannot fail, as all Erlang integers are big integers. In the +/// JavaScript target, this will fail if the integer is bigger than the +/// [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +/// or smaller than the +/// [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER). +@external(erlang, "bigi_ffi", "to") +@external(javascript, "./bigi_ffi.mjs", "to") +pub fn to_int(bigint: BigInt) -> Result(Int, Nil) + +/// Convert the big integer into a simple string - a sequence of digits. +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "./bigi_ffi.mjs", "to_string") +pub fn to_string(bigint: BigInt) -> String + +/// Convert a big integer to raw bytes. +/// +/// The size of the returned bit array is specified by `byte_count`, e.g. 8 will +/// return a bit array containing 8 bytes (64 bits). If the big integer doesn't +/// fit in the specified number of bytes then an error is returned. +@external(erlang, "bigi_ffi", "to_bytes") +@external(javascript, "./bigi_ffi.mjs", "to_bytes") +pub fn to_bytes( + bigint: BigInt, + endianness: Endianness, + signedness: Signedness, + byte_count: Int, +) -> Result(BitArray, Nil) + +/// Compare two big integers, returning an order that denotes if the first +/// argument is lower, bigger than, or equal to the second. +@external(erlang, "bigi_ffi", "compare") +@external(javascript, "./bigi_ffi.mjs", "compare") +pub fn compare(a: BigInt, with b: BigInt) -> order.Order + +/// Get the absolute value of a big integer. +@external(erlang, "erlang", "abs") +@external(javascript, "./bigi_ffi.mjs", "absolute") +pub fn absolute(bigint: BigInt) -> BigInt + +/// Returns the negative of the value provided. +@external(erlang, "bigi_ffi", "negate") +@external(javascript, "./bigi_ffi.mjs", "negate") +pub fn negate(bigint: BigInt) -> BigInt + +/// Add two big integers together. +@external(erlang, "bigi_ffi", "add") +@external(javascript, "./bigi_ffi.mjs", "add") +pub fn add(a: BigInt, b: BigInt) -> BigInt + +/// Subtract the subtrahend from the minuend. +@external(erlang, "bigi_ffi", "subtract") +@external(javascript, "./bigi_ffi.mjs", "subtract") +pub fn subtract(minuend a: BigInt, subtrahend b: BigInt) -> BigInt + +/// Multiply two big integers together. +@external(erlang, "bigi_ffi", "multiply") +@external(javascript, "./bigi_ffi.mjs", "multiply") +pub fn multiply(multiplicand a: BigInt, multiplier b: BigInt) -> BigInt + +/// Divide the dividend with the divisor using integer division. +/// +/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0. +@external(erlang, "bigi_ffi", "divide") +@external(javascript, "./bigi_ffi.mjs", "divide") +pub fn divide(dividend a: BigInt, divisor b: BigInt) -> BigInt + +/// Performs a *floored* integer division, which means that the result will +/// always be rounded towards negative infinity. +/// +/// If you want to perform truncated integer division (rounding towards zero), +/// use `divide` or `divide_no_zero` instead. +/// +/// Returns an error if the divisor is 0. +pub fn floor_divide( + dividend dividend: BigInt, + divisor divisor: BigInt, +) -> Result(BigInt, Nil) { + let z = zero() + case divisor == z { + True -> Error(Nil) + False -> + case compare(multiply(dividend, divisor), z) { + order.Lt -> + case remainder(dividend, divisor) != z { + True -> Ok(subtract(divide(dividend, divisor), one())) + False -> Ok(divide(dividend, divisor)) + } + _ -> Ok(divide(dividend, divisor)) + } + } +} + +/// Divide the dividend with the divisor using integer division. +/// +/// Returns an error if the divisor is 0. +@external(erlang, "bigi_ffi", "divide_no_zero") +@external(javascript, "./bigi_ffi.mjs", "divide_no_zero") +pub fn divide_no_zero( + dividend a: BigInt, + divisor b: BigInt, +) -> Result(BigInt, Nil) + +/// Divide the dividend with the divisor using integer division and return the +/// remainder. +/// +/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0. +@external(erlang, "bigi_ffi", "remainder") +@external(javascript, "./bigi_ffi.mjs", "remainder") +pub fn remainder(dividend a: BigInt, divisor b: BigInt) -> BigInt + +/// Divide the dividend with the divisor using integer division and return the +/// remainder. +/// +/// Returns an error if the divisor is 0. +@external(erlang, "bigi_ffi", "remainder_no_zero") +@external(javascript, "./bigi_ffi.mjs", "remainder_no_zero") +pub fn remainder_no_zero( + dividend a: BigInt, + divisor b: BigInt, +) -> Result(BigInt, Nil) + +/// Calculate a mathematical modulo operation. +/// +/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0. +@external(erlang, "bigi_ffi", "modulo") +@external(javascript, "./bigi_ffi.mjs", "modulo") +pub fn modulo(dividend a: BigInt, divisor b: BigInt) -> BigInt + +/// Calculate a mathematical modulo operation. +/// +/// Returns an error if the divisor is 0. +@external(erlang, "bigi_ffi", "modulo_no_zero") +@external(javascript, "./bigi_ffi.mjs", "modulo_no_zero") +pub fn modulo_no_zero( + dividend a: BigInt, + divisor b: BigInt, +) -> Result(BigInt, Nil) + +/// Raise the base to the exponent. +/// +/// If the exponent is negative, an error is returned. +@external(erlang, "bigi_ffi", "power") +@external(javascript, "./bigi_ffi.mjs", "power") +pub fn power(base a: BigInt, exponent b: BigInt) -> Result(BigInt, Nil) + +/// Get the digits in a given bigint as a list of integers in base 10. +/// +/// The list is ordered starting from the most significant digit. +pub fn digits(bigint: BigInt) { + get_digit(bigint, [], ten()) +} + +/// Returns a decoder that decodes a Dynamic value into a big integer, if +/// possible. +pub fn decoder() -> decode.Decoder(BigInt) { + decode.new_primitive_decoder("BigInt", internal_do_decode) +} + +@deprecated("This function was accidentally made public. Please use decoder() instead.") +@external(erlang, "bigi_ffi", "decode") +@external(javascript, "./bigi_ffi.mjs", "decode") +@internal +pub fn do_decode(dyn: decode.Dynamic) -> Result(BigInt, BigInt) + +@external(erlang, "bigi_ffi", "decode") +@external(javascript, "./bigi_ffi.mjs", "decode") +fn internal_do_decode(dyn: decode.Dynamic) -> Result(BigInt, BigInt) + +/// Calculates the bitwise AND of its arguments. +@external(erlang, "bigi_ffi", "bitwise_and") +@external(javascript, "./bigi_ffi.mjs", "bitwise_and") +pub fn bitwise_and(a: BigInt, b: BigInt) -> BigInt + +/// Calculates the bitwise XOR of its arguments. +@external(erlang, "bigi_ffi", "bitwise_exclusive_or") +@external(javascript, "./bigi_ffi.mjs", "bitwise_exclusive_or") +pub fn bitwise_exclusive_or(a: BigInt, b: BigInt) -> BigInt + +/// Calculates the bitwise NOT of its argument. +@external(erlang, "bigi_ffi", "bitwise_not") +@external(javascript, "./bigi_ffi.mjs", "bitwise_not") +pub fn bitwise_not(bigint: BigInt) -> BigInt + +/// Calculates the bitwise OR of its arguments. +@external(erlang, "bigi_ffi", "bitwise_or") +@external(javascript, "./bigi_ffi.mjs", "bitwise_or") +pub fn bitwise_or(a: BigInt, b: BigInt) -> BigInt + +/// Calculates the result of an arithmetic left bitshift by the given amount. +@external(erlang, "bigi_ffi", "bitwise_shift_left") +@external(javascript, "./bigi_ffi.mjs", "bitwise_shift_left") +pub fn bitwise_shift_left(bigint: BigInt, by amount: Int) -> BigInt + +/// Calculates the result of an arithmetic right bitshift by the given amount. +@external(erlang, "bigi_ffi", "bitwise_shift_right") +@external(javascript, "./bigi_ffi.mjs", "bitwise_shift_right") +pub fn bitwise_shift_right(bigint: BigInt, by amount: Int) -> BigInt + +/// Restricts a big integer between a lower and upper bound. +pub fn clamp(bigint: BigInt, min min_bound: BigInt, max max_bound: BigInt) { + bigint + |> min(max_bound) + |> max(min_bound) +} + +/// Returns whether the big integer provided is odd. +pub fn is_odd(bigint: BigInt) { + remainder(bigint, from_int(2)) != zero() +} + +/// Compares two big integers, returning the larger of the two. +pub fn max(a: BigInt, or b: BigInt) { + case compare(a, b) { + order.Lt -> b + _ -> a + } +} + +/// Compares two big integers, returning the smaller of the two. +pub fn min(a: BigInt, or b: BigInt) { + case compare(a, b) { + order.Lt -> a + _ -> b + } +} + +/// Sums a list of big integers. +/// +/// Returns 0 if the list was empty. +pub fn sum(bigints: List(BigInt)) { + list.fold(bigints, zero(), add) +} + +/// Multiplies a list of big integers. +/// +/// Returns 1 if the list was empty. +pub fn product(bigints: List(BigInt)) { + list.fold(bigints, one(), multiply) +} + +/// Joins a list of digits into a single value. Returns an error if the base is +/// less than 2 or if the list contains a digit greater than or equal to the +/// specified base. +pub fn undigits(digits: List(Int), base: Int) -> Result(BigInt, Nil) { + case base < 2 { + True -> Error(Nil) + False -> { + let base = from_int(base) + list.try_fold(digits, zero(), fn(acc, digit) { + let digit = from_int(digit) + case compare(digit, base) { + order.Gt | order.Eq -> Error(Nil) + _ -> Ok(add(multiply(acc, base), digit)) + } + }) + } + } +} + +fn get_digit(bigint: BigInt, digits: List(Int), divisor: BigInt) { + case compare(bigint, divisor) { + order.Lt -> { + let assert Ok(digit) = to_int(bigint) + [digit, ..digits] + } + _ -> { + let assert Ok(digit) = + remainder(bigint, divisor) + |> to_int() + let digits = [digit, ..digits] + get_digit(divide(bigint, divisor), digits, divisor) + } + } +} + +/// Parse a binary string into a big integer. +/// +/// The string may contain an optional dash at the start to denote a negative +/// number, followed by an optional `0b` prefix. Following those, only 0 and 1 +/// are allowed. The string is NOT trimmed for whitespace. +/// +/// Note that no conversion is done for the number. This means that it is always +/// treated as unsigned, and will only be negative if it is preceded by a dash. +/// As an example, `"0b100"` returns 4, and `"-0b100"` returns -4. +pub fn from_base2(base2: String) -> Result(BigInt, Nil) { + let #(sign, rest) = case base2 { + "-" <> rest -> #(negative_one(), rest) + _ -> #(one(), base2) + } + + let maybe_parsed = case rest { + "0b" <> rest -> do_from_base2(rest) + _ -> do_from_base2(rest) + } + + use parsed <- result.try(maybe_parsed) + Ok(multiply(sign, parsed)) +} + +/// Parse an octal string into a big integer. +/// +/// The string may contain an optional dash at the start to denote a negative +/// number, followed by an optional `0o` prefix. Following those, only numbers 0 +/// through 7 are allowed. The string is NOT trimmed for whitespace. +pub fn from_base8(base8: String) -> Result(BigInt, Nil) { + let #(sign, rest) = case base8 { + "-" <> rest -> #(negative_one(), rest) + _ -> #(one(), base8) + } + + let maybe_parsed = case rest { + "0o" <> rest -> do_from_base8(rest) + _ -> do_from_base8(rest) + } + + use parsed <- result.try(maybe_parsed) + Ok(multiply(sign, parsed)) +} + +/// Parse a hexadecimal string into a big integer. +/// +/// The string may contain an optional dash at the start to denote a negative +/// number, followed by an optional `0x` prefix. Following those, only numbers 0 +/// through 9 and letters _a_ through _f_ are allowed. The string is NOT trimmed +/// for whitespace. The string may be upper, lower, or mixed case, but the +/// prefix `0x` must always be lower case. +pub fn from_base16(base16: String) -> Result(BigInt, Nil) { + let #(sign, rest) = case base16 { + "-" <> rest -> #(negative_one(), rest) + _ -> #(one(), base16) + } + + let maybe_parsed = case rest { + "0x" <> rest -> do_from_base16(rest) + _ -> do_from_base16(rest) + } + + use parsed <- result.try(maybe_parsed) + Ok(multiply(sign, parsed)) +} + +@external(erlang, "bigi_ffi", "from_base2") +@external(javascript, "./bigi_ffi.mjs", "from_base2") +fn do_from_base2(base2: String) -> Result(BigInt, Nil) + +@external(erlang, "bigi_ffi", "from_base8") +@external(javascript, "./bigi_ffi.mjs", "from_base8") +fn do_from_base8(base8: String) -> Result(BigInt, Nil) + +@external(erlang, "bigi_ffi", "from_base16") +@external(javascript, "./bigi_ffi.mjs", "from_base16") +fn do_from_base16(base16: String) -> Result(BigInt, Nil) + +/// Parse a big integer from an arbitrary base. +/// +/// The passed alphabet function must return `Ok(n)` for a given character, +/// where _n_ is the base-10 numerical value of that character. This allows +/// using any kind of alphabet. The alphabet must contain enough characters to +/// cover the entire value range of the chosen base. +/// +/// The base must be positive and larger than 1. +pub fn from_base( + input: String, + from base: Int, + using alphabet: fn(String) -> Result(Int, Nil), +) -> Result(BigInt, Nil) { + use <- bool.guard(base <= 1, Error(Nil)) + + let base_b = from_int(base) + + let res = + input + |> string.to_graphemes() + |> list.reverse() + |> list.try_fold(#(zero(), zero()), fn(acc, char) { + let #(value, i) = acc + case alphabet(char) { + // If result from alphabet is too big, there's something faulty going on + Ok(int) if int >= base -> Error(Nil) + Ok(int) -> { + let assert Ok(p) = power(base_b, i) + let value = int |> from_int() |> multiply(p) |> add(value) + Ok(#(value, add(i, one()))) + } + Error(Nil) -> Error(Nil) + } + }) + + case res { + Ok(#(res, _)) -> Ok(res) + Error(Nil) -> Error(Nil) + } +} + +/// Stringify a big integer into a binary string. +/// +/// A dash is added at the front if the number is negative. The number that +/// follows will always be an unsigned binary number. +@external(erlang, "bigi_ffi", "to_base2") +@external(javascript, "./bigi_ffi.mjs", "to_base2") +pub fn to_base2(int: BigInt) -> String + +/// Stringify a big integer into an octal string. +/// +/// A dash is added at the front if the number is negative. +@external(erlang, "bigi_ffi", "to_base8") +@external(javascript, "./bigi_ffi.mjs", "to_base8") +pub fn to_base8(int: BigInt) -> String + +/// Stringify a big integer into a hexadecimal string. +/// +/// A dash is added at the front if the number is negative. The resulting string +/// will be lower case. +@external(javascript, "./bigi_ffi.mjs", "to_base16") +pub fn to_base16(int: BigInt) -> String { + erl_do_to_base16(int) |> string.lowercase() +} + +@external(erlang, "bigi_ffi", "to_base16") +fn erl_do_to_base16(int: BigInt) -> String + +/// Stringify a big integer into a number of arbitrary base. +/// +/// The passed alphabet function must return `Ok(c)` for a given base-10 +/// integer, where _c_ is the symbol of that integer in the given base. This +/// allows using any kind of alphabet. The alphabet must contain enough symbols +/// to cover the entire value range of the chosen base. +/// +/// The base must be positive and larger than 1. +pub fn to_base( + input: BigInt, + to base: Int, + using alphabet: fn(Int) -> Result(String, Nil), +) -> Result(String, Nil) { + use <- bool.guard(base <= 1, Error(Nil)) + use res <- result.try(do_to_base("", input, from_int(base), alphabet)) + + let sign = case compare(input, zero()) { + order.Lt -> "-" + _ -> "" + } + + Ok(sign <> res) +} + +fn do_to_base( + acc: String, + value: BigInt, + base: BigInt, + alphabet: fn(Int) -> Result(String, Nil), +) { + case compare(value, base) { + order.Lt -> { + // Since base comes from int, this is safe as value is smaller than base + let assert Ok(i) = to_int(value) + case alphabet(i) { + Ok(c) -> Ok(c <> acc) + Error(Nil) -> Error(Nil) + } + } + _ -> { + let rem = remainder(value, base) + + // Since base comes from int, remainder result is smaller and thus this is + // safe + let assert Ok(mod_i) = to_int(rem) + + case alphabet(mod_i) { + Ok(c) -> { + let acc = c <> acc + let new_value = divide(subtract(value, rem), base) + do_to_base(acc, new_value, base, alphabet) + } + Error(Nil) -> Error(Nil) + } + } + } +} diff --git a/examples/http_interactions/build/packages/bigi/src/bigi_ffi.erl b/examples/http_interactions/build/packages/bigi/src/bigi_ffi.erl new file mode 100644 index 0000000..813355c --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/src/bigi_ffi.erl @@ -0,0 +1,201 @@ +-module(bigi_ffi). + +-export([ + from/1, + from_string/1, + from_bytes/3, + to/1, + to_bytes/4, + zero/0, + one/0, + n_one/0, + ten/0, + compare/2, + negate/1, + add/2, + subtract/2, + multiply/2, + divide/2, + divide_no_zero/2, + remainder/2, + remainder_no_zero/2, + modulo/2, + modulo_no_zero/2, + power/2, + decode/1, + bitwise_and/2, + bitwise_exclusive_or/2, + bitwise_not/1, + bitwise_or/2, + bitwise_shift_left/2, + bitwise_shift_right/2, + from_base2/1, + from_base8/1, + from_base16/1, + to_base2/1, + to_base8/1, + to_base16/1 +]). + +from(Int) -> Int. + +from_string(Str) -> + case string:to_integer(Str) of + {_, Rest} when Rest /= <<"">> -> {error, nil}; + {Int, _} -> {ok, Int} + end. + +from_bytes(Bytes, Endianness, Signedness) -> + BitSize = erlang:bit_size(Bytes), + + case BitSize rem 8 of + 0 -> + case Endianness of + little_endian -> + case Signedness of + signed -> + <> = Bytes, + {ok, Int}; + unsigned -> + <> = Bytes, + {ok, Int} + end; + big_endian -> + case Signedness of + signed -> + <> = Bytes, + {ok, Int}; + unsigned -> + <> = Bytes, + {ok, Int} + end + end; + _ -> + {error, nil} + end. + +to(BigInt) -> {ok, BigInt}. + +to_bytes(BigInt, Endianness, Signedness, ByteCount) -> + case ByteCount * 8 of + BitCount when BitCount >= 8 -> + RangeMin = + case Signedness of + signed -> -(1 bsl (BitCount - 1)); + unsigned -> 0 + end, + + RangeMax = + case Signedness of + signed -> (1 bsl (BitCount - 1)) - 1; + unsigned -> (1 bsl BitCount) - 1 + end, + + % Error if the value is out of range for the available bits + case BigInt >= RangeMin andalso BigInt =< RangeMax of + true -> + case Endianness of + little_endian -> {ok, <>}; + big_endian -> {ok, <>} + end; + false -> + {error, nil} + end; + _ -> + {error, nil} + end. + +zero() -> 0. +one() -> 1. +n_one() -> -1. +ten() -> 10. + +compare(A, B) when A < B -> lt; +compare(A, B) when A > B -> gt; +compare(_, _) -> eq. + +negate(A) -> -A. + +add(A, B) -> A + B. + +subtract(A, B) -> A - B. + +multiply(A, B) -> A * B. + +divide(_, 0) -> 0; +divide(A, B) -> A div B. + +divide_no_zero(_, 0) -> {error, nil}; +divide_no_zero(A, B) -> {ok, divide(A, B)}. + +remainder(_, 0) -> 0; +remainder(A, B) -> A rem B. + +remainder_no_zero(_, 0) -> {error, nil}; +remainder_no_zero(A, B) -> {ok, remainder(A, B)}. + +modulo(_, 0) -> 0; +modulo(A, B) -> ((A rem B) + B) rem B. + +modulo_no_zero(_, 0) -> {error, nil}; +modulo_no_zero(A, B) -> {ok, modulo(A, B)}. + +power(_, Exp) when Exp < 0 -> {error, nil}; +power(Base, Exp) -> {ok, do_power(Base, Exp)}. + +do_power(_, 0) -> + 1; +do_power(A, 1) -> + A; +do_power(A, N) -> + B = do_power(A, N div 2), + B * B * + (case N rem 2 of + 0 -> 1; + 1 -> A + end). + +decode(Dyn) when is_integer(Dyn) -> {ok, Dyn}; +decode(_Dyn) -> {error, 0}. + +bitwise_and(A, B) -> A band B. + +bitwise_exclusive_or(A, B) -> A bxor B. + +bitwise_not(A) -> bnot A. + +bitwise_or(A, B) -> A bor B. + +bitwise_shift_left(A, B) -> A bsl B. + +bitwise_shift_right(A, B) -> A bsr B. + +from_base2(Str) -> + try + Res = binary_to_integer(Str, 2), + {ok, Res} + catch + error:badarg -> {error, nil} + end. + +from_base8(Str) -> + try + Res = binary_to_integer(Str, 8), + {ok, Res} + catch + error:badarg -> {error, nil} + end. + +from_base16(Str) -> + try + Res = binary_to_integer(Str, 16), + {ok, Res} + catch + error:badarg -> {error, nil} + end. + +to_base2(Int) -> integer_to_binary(Int, 2). + +to_base8(Int) -> integer_to_binary(Int, 8). + +to_base16(Int) -> integer_to_binary(Int, 16). diff --git a/examples/http_interactions/build/packages/bigi/src/bigi_ffi.mjs b/examples/http_interactions/build/packages/bigi/src/bigi_ffi.mjs new file mode 100644 index 0000000..f8134e1 --- /dev/null +++ b/examples/http_interactions/build/packages/bigi/src/bigi_ffi.mjs @@ -0,0 +1,280 @@ +import { Result$Ok, Result$Error, BitArray$BitArray } from "./gleam.mjs"; +import { Order$Lt, Order$Eq, Order$Gt } from "../gleam_stdlib/gleam/order.mjs"; +import { Endianness$isBigEndian, Signedness$isSigned } from "./bigi.mjs"; + +export function from(int) { + return BigInt(int); +} + +export function from_string(string) { + try { + return Result$Ok(BigInt(string)); + } catch { + return Result$Error(undefined); + } +} + +export function from_bytes(bit_array, endianness, signedness) { + if (bit_array.bitSize % 8 !== 0) { + return Result$Error(undefined); + } + + let value = 0n; + + // Read bytes as an unsigned integer value + if (Endianness$isBigEndian(endianness)) { + for (let i = 0; i < bit_array.byteSize; i++) { + value = value * 256n + BigInt(bit_array.byteAt(i)); + } + } else { + for (let i = bit_array.byteSize - 1; i >= 0; i--) { + value = value * 256n + BigInt(bit_array.byteAt(i)); + } + } + + if (Signedness$isSigned(signedness)) { + const byteSize = BigInt(bit_array.byteSize); + + const highBit = 2n ** (byteSize * 8n - 1n); + + // If the high bit is set and this is a signed integer, reinterpret as + // two's complement + if (value >= highBit) { + value -= highBit * 2n; + } + } + + return Result$Ok(value); +} + +export function to(bigint) { + if (bigint > Number.MAX_SAFE_INTEGER || bigint < Number.MIN_SAFE_INTEGER) { + return Result$Error(undefined); + } else { + return Result$Ok(Number(bigint)); + } +} + +export function to_string(bigint) { + return bigint.toString(); +} + +export function to_bytes(bigint, endianness, signedness, byte_count) { + const bit_count = BigInt(byte_count * 8); + + if (bit_count < 8n) { + return Result$Error(undefined); + } + + let range_min = 0n; + let range_max = 0n; + + // Error if the value is out of range for the available bits + if (Signedness$isSigned(signedness)) { + range_min = -(2n ** (bit_count - 1n)); + range_max = -range_min - 1n; + } else { + range_max = 2n ** bit_count - 1n; + } + + if (bigint < range_min || bigint > range_max) { + return Result$Error(undefined); + } + + // Convert negative number to two's complement representation + if (bigint < 0) { + bigint = (1n << bit_count) + bigint; + } + + const byteArray = new Uint8Array(byte_count); + + if (Endianness$isBigEndian(endianness)) { + for (let i = byteArray.length - 1; i >= 0; i--) { + const byte = bigint % 256n; + byteArray[i] = Number(byte); + bigint = (bigint - byte) / 256n; + } + } else { + for (let i = 0; i < byteArray.length; i++) { + const byte = bigint % 256n; + byteArray[i] = Number(byte); + bigint = (bigint - byte) / 256n; + } + } + + return Result$Ok(BitArray$BitArray(byteArray)); +} + +export function zero() { + return 0n; +} + +export function one() { + return 1n; +} + +export function n_one() { + return -1n; +} + +export function ten() { + return 10n; +} + +export function compare(a, b) { + if (a < b) { + return Order$Lt(); + } else if (a > b) { + return Order$Gt(); + } else { + return Order$Eq(); + } +} + +export function absolute(bigint) { + if (bigint < 0) { + return -bigint; + } else { + return bigint; + } +} + +export function negate(bigint) { + return -bigint; +} + +export function add(a, b) { + return a + b; +} + +export function subtract(a, b) { + return a - b; +} + +export function multiply(a, b) { + return a * b; +} + +export function divide(a, b) { + if (b === 0n) { + return 0n; + } + + return a / b; +} + +export function divide_no_zero(a, b) { + if (b === 0n) { + return Result$Error(undefined); + } + + return Result$Ok(divide(a, b)); +} + +export function remainder(a, b) { + if (b === 0n) { + return 0n; + } + + return a % b; +} + +export function remainder_no_zero(a, b) { + if (b === 0n) { + return Result$Error(undefined); + } + + return Result$Ok(remainder(a, b)); +} + +export function modulo(a, b) { + if (b === 0n) { + return 0n; + } + + return ((a % b) + b) % b; +} + +export function modulo_no_zero(a, b) { + if (b === 0n) { + return Result$Error(undefined); + } + + return Result$Ok(modulo(a, b)); +} + +export function power(a, b) { + if (b < 0) { + return Result$Error(undefined); + } + + return Result$Ok(a ** b); +} + +export function decode(dyn) { + if (typeof dyn === "bigint") { + return Result$Ok(dyn); + } else { + return Result$Error(0n); + } +} + +export function bitwise_and(a, b) { + return a & b; +} + +export function bitwise_exclusive_or(a, b) { + return a ^ b; +} + +export function bitwise_not(a) { + return ~a; +} + +export function bitwise_or(a, b) { + return a | b; +} + +export function bitwise_shift_left(a, b) { + return a << BigInt(b); +} + +export function bitwise_shift_right(a, b) { + return a >> BigInt(b); +} + +export function from_base2(str) { + try { + return Result$Ok(BigInt("0b" + str)); + } catch { + return Result$Error(undefined); + } +} + +export function from_base8(str) { + try { + return Result$Ok(BigInt("0o" + str)); + } catch { + return Result$Error(undefined); + } +} + +export function from_base16(str) { + try { + return Result$Ok(BigInt("0x" + str)); + } catch { + return Result$Error(undefined); + } +} + +export function to_base2(int) { + return int.toString(2); +} + +export function to_base8(int) { + return int.toString(8); +} + +export function to_base16(int) { + return int.toString(16); +} diff --git a/examples/http_interactions/build/packages/directories/LICENSE b/examples/http_interactions/build/packages/directories/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/directories/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/directories/README.md b/examples/http_interactions/build/packages/directories/README.md new file mode 100644 index 0000000..4a4c2ac --- /dev/null +++ b/examples/http_interactions/build/packages/directories/README.md @@ -0,0 +1,49 @@ +# directories + +A lightweight gleam package to get standard locations of directories for temporary files, config, cache, etc. + + +[![Package Version](https://img.shields.io/hexpm/v/directories)](https://hex.pm/packages/directories) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/directories/) + +```sh +gleam add directories@1 +``` +```gleam +import directories + +pub fn main() { + // All functions return an Result(String, Nil) + // Nil will be returned whenever it couldn't find a valid directory + let tmp_dir = io.debug(directories.tmp_dir()) + let home_dir = io.debug(directories.home_dir()) + let cache_dir = io.debug(directories.cache_dir()) + let config_dir = io.debug(directories.config_dir()) + let config_local_dir = io.debug(directories.config_local_dir()) + let data_dir = io.debug(directories.data_dir()) + let data_local_dir = io.debug(directories.data_local_dir()) + let executable_dir = io.debug(directories.executable_dir()) + let preferences_dir = io.debug(directories.preferences_dir()) + let runtime_dir = io.debug(directories.runtime_dir()) + let state_dir = io.debug(directories.state_dir()) +} +``` + +Further documentation can be found at . + +## Dependencies +- It depends on `gleam_stdlib` +- It depends on `platform` to find out what is the host operating system. +- It depends on `simplifile` to check if a folder exists and is a valid file +- It depends on `envoy` to get environment variables + +## TODO +- [x] Publish to hexpm +- [x] Remove dependency on `gleam_erlang` to make it work in the js runtime + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/examples/http_interactions/build/packages/directories/gleam.toml b/examples/http_interactions/build/packages/directories/gleam.toml new file mode 100644 index 0000000..39dd782 --- /dev/null +++ b/examples/http_interactions/build/packages/directories/gleam.toml @@ -0,0 +1,22 @@ +name = "directories" +version = "1.2.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +description = "A lightweight package to get standard locations of directories for temporary files, config, cache, etc." +licences = ["Apache-2.0"] +repository = { type = "github", user = "ditherwither", repo = "directories" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" +simplifile = ">= 2.0.1 and < 3.0.0" +envoy = ">= 1.0.1 and < 2.0.0" +platform = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/directories/src/directories.app.src b/examples/http_interactions/build/packages/directories/src/directories.app.src new file mode 100644 index 0000000..041dbb7 --- /dev/null +++ b/examples/http_interactions/build/packages/directories/src/directories.app.src @@ -0,0 +1,11 @@ +{application, directories, [ + {vsn, "1.2.0"}, + {applications, [envoy, + gleam_stdlib, + platform, + simplifile]}, + {description, "A lightweight package to get standard locations of directories for temporary files, config, cache, etc."}, + {modules, [directories, + directories@@main]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/directories/src/directories.erl b/examples/http_interactions/build/packages/directories/src/directories.erl new file mode 100644 index 0000000..e7bdf1e --- /dev/null +++ b/examples/http_interactions/build/packages/directories/src/directories.erl @@ -0,0 +1,520 @@ +-module(directories). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([tmp_dir/0, home_dir/0, cache_dir/0, config_dir/0, config_local_dir/0, data_dir/0, data_local_dir/0, executable_dir/0, preference_dir/0, runtime_dir/0, state_dir/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/directories.gleam", 16). +?DOC(" Return the first directory from the list that exists, or Nil\n"). +-spec check_dirs(list(binary())) -> {ok, binary()} | {error, nil}. +check_dirs(Paths) -> + _pipe = Paths, + _pipe@1 = gleam@list:filter( + _pipe, + fun(A) -> + not gleam@string:is_empty(A) andalso gleam@result:unwrap( + simplifile_erl:is_directory(A), + false + ) + end + ), + gleam@list:first(_pipe@1). + +-file("src/directories.gleam", 11). +?DOC( + " Return the first environment variable from the list\n" + " that is set and is a valid directory\n" +). +-spec check_dir_from_env(list(binary())) -> {ok, binary()} | {error, nil}. +check_dir_from_env(Vars) -> + _pipe = Vars, + _pipe@1 = gleam@list:filter_map(_pipe, fun envoy_ffi:get/1), + check_dirs(_pipe@1). + +-file("src/directories.gleam", 24). +-spec get_env(binary()) -> binary(). +get_env(Var) -> + gleam@result:unwrap(envoy_ffi:get(Var), <<""/utf8>>). + +-file("src/directories.gleam", 28). +-spec home_dir_path(binary()) -> binary(). +home_dir_path(Path) -> + <<(get_env(<<"HOME"/utf8>>))/binary, Path/binary>>. + +-file("src/directories.gleam", 32). +-spec other_os_message(binary()) -> {ok, binary()} | {error, nil}. +other_os_message(Other_os) -> + gleam@io:print_error( + <<<<"[WARN][directories] Operating system '"/utf8, Other_os/binary>>/binary, + "' is not supported by this library"/utf8>> + ), + {error, nil}. + +-file("src/directories.gleam", 48). +?DOC( + " Returns the path to a temporary directory\n" + " \n" + " It'll first check `%TMPDIR%`, `%TEMP%`, `%TMP%`, and return the first one that is a valid directory\n" + " \n" + " If that fails, It'll check `C:\\TEMP`, `C:\\TMP`, `\\TEMP`, `\\TMP` on windows.\n" + " \n" + " On MacOS, Linux, and FreeBSD, it'll check `/tmp`, `/var/tmp`, `/usr/tmp`,\n" +). +-spec tmp_dir() -> {ok, binary()} | {error, nil}. +tmp_dir() -> + case check_dir_from_env( + [<<"TMPDIR"/utf8>>, <<"TEMP"/utf8>>, <<"TMP"/utf8>>] + ) of + {ok, Path} -> + {ok, Path}; + + {error, nil} -> + case platform:os() of + win32 -> + check_dirs( + [<<"C:\\TEMP"/utf8>>, + <<"C:\\TMP"/utf8>>, + <<"\\TEMP"/utf8>>, + <<"\\TMP"/utf8>>] + ); + + darwin -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + linux -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + free_bsd -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + open_bsd -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + sun_os -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + aix -> + check_dirs( + [<<"/tmp"/utf8>>, + <<"/var/tmp"/utf8>>, + <<"/usr/tmp"/utf8>>] + ); + + {other_os, Os} -> + other_os_message(Os) + end + end. + +-file("src/directories.gleam", 74). +?DOC( + " Returns the path to the user's home directory\n" + " \n" + " It'll check `%UserProfile%` and `%Profile%` on windows, returning first one that is a valid directory\n" + " \n" + " On MacOS, Linux, and FreeBSD, it'll return the value of `$HOME` if it exists\n" +). +-spec home_dir() -> {ok, binary()} | {error, nil}. +home_dir() -> + case platform:os() of + win32 -> + check_dir_from_env([<<"UserProfile"/utf8>>, <<"Profile"/utf8>>]); + + darwin -> + check_dir_from_env([<<"HOME"/utf8>>]); + + linux -> + check_dir_from_env([<<"HOME"/utf8>>]); + + free_bsd -> + check_dir_from_env([<<"HOME"/utf8>>]); + + open_bsd -> + check_dir_from_env([<<"HOME"/utf8>>]); + + sun_os -> + check_dir_from_env([<<"HOME"/utf8>>]); + + aix -> + check_dir_from_env([<<"HOME"/utf8>>]); + + {other_os, Os} -> + other_os_message(Os) + end. + +-file("src/directories.gleam", 94). +?DOC( + " Returns the path to the user-specific cache directory\n" + " \n" + " On Windows, it'll return the value of `%APPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return value of `$HOME/Library/Caches` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check `$XDG_CACHE_HOME` and `$HOME/.cache`, returning the first one that is a valid directory\n" +). +-spec cache_dir() -> {ok, binary()} | {error, nil}. +cache_dir() -> + case platform:os() of + win32 -> + check_dir_from_env([<<"APPDATA"/utf8>>]); + + darwin -> + check_dirs( + [<<(get_env(<<"HOME"/utf8>>))/binary, "/Library/Caches"/utf8>>] + ); + + linux -> + check_dirs( + [get_env(<<"XDG_CACHE_HOME"/utf8>>), + home_dir_path(<<"/.cache"/utf8>>)] + ); + + free_bsd -> + check_dirs( + [get_env(<<"XDG_CACHE_HOME"/utf8>>), + home_dir_path(<<"/.cache"/utf8>>)] + ); + + open_bsd -> + check_dirs( + [get_env(<<"XDG_CACHE_HOME"/utf8>>), + home_dir_path(<<"/.cache"/utf8>>)] + ); + + sun_os -> + check_dirs( + [get_env(<<"XDG_CACHE_HOME"/utf8>>), + home_dir_path(<<"/.cache"/utf8>>)] + ); + + aix -> + check_dirs( + [get_env(<<"XDG_CACHE_HOME"/utf8>>), + home_dir_path(<<"/.cache"/utf8>>)] + ); + + {other_os, Os} -> + other_os_message(Os) + end. + +-file("src/directories.gleam", 115). +?DOC( + " Returns the path to the user-specific config directory. This directory may be synced across computers\n" + " \n" + " On Windows, it'll return the value of `%APPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check `$XDG_CONFIG_HOME` and `$HOME/.config`, returning the first one that is a valid directory\n" +). +-spec config_dir() -> {ok, binary()} | {error, nil}. +config_dir() -> + case platform:os() of + win32 -> + check_dir_from_env([<<"APPDATA"/utf8>>]); + + darwin -> + check_dirs( + [<<(get_env(<<"HOME"/utf8>>))/binary, + "/Library/Application Support"/utf8>>] + ); + + linux -> + check_dirs( + [get_env(<<"XDG_CONFIG_HOME"/utf8>>), + home_dir_path(<<"/.config"/utf8>>)] + ); + + free_bsd -> + check_dirs( + [get_env(<<"XDG_CONFIG_HOME"/utf8>>), + home_dir_path(<<"/.config"/utf8>>)] + ); + + open_bsd -> + check_dirs( + [get_env(<<"XDG_CONFIG_HOME"/utf8>>), + home_dir_path(<<"/.config"/utf8>>)] + ); + + sun_os -> + check_dirs( + [get_env(<<"XDG_CONFIG_HOME"/utf8>>), + home_dir_path(<<"/.config"/utf8>>)] + ); + + aix -> + check_dirs( + [get_env(<<"XDG_CONFIG_HOME"/utf8>>), + home_dir_path(<<"/.config"/utf8>>)] + ); + + {other_os, Os} -> + other_os_message(Os) + end. + +-file("src/directories.gleam", 137). +?DOC( + " Returns the path to the user-specific local config directory. Similar to `config_dir`, except Windows won't sync it when connected to a domain with a roaming profile\n" + " \n" + " On Windows, it'll return the value of `%LOCALAPPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check `$XDG_CONFIG_HOME` and `$HOME/.config`, returning the first one that is a valid directory\n" +). +-spec config_local_dir() -> {ok, binary()} | {error, nil}. +config_local_dir() -> + case platform:os() of + win32 -> + check_dir_from_env([<<"LOCALAPPDATA"/utf8>>]); + + _ -> + config_dir() + end. + +-file("src/directories.gleam", 151). +?DOC( + " Returns the path to the user-specific data directory. This directory may be synced across computers\n" + " \n" + " On Windows, it'll return the value of `%APPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check `$XDG_DATA_HOME``and $HOME/.local/share, returning the first one that is a valid directory\n" +). +-spec data_dir() -> {ok, binary()} | {error, nil}. +data_dir() -> + case platform:os() of + linux -> + check_dirs( + [get_env(<<"XDG_DATA_HOME"/utf8>>), + home_dir_path(<<"/.local/share"/utf8>>)] + ); + + free_bsd -> + check_dirs( + [get_env(<<"XDG_DATA_HOME"/utf8>>), + home_dir_path(<<"/.local/share"/utf8>>)] + ); + + _ -> + config_dir() + end. + +-file("src/directories.gleam", 166). +?DOC( + " Returns the path to the user-specific data directory. Similar to `data_dir`, except Windows won't sync it when connected to a domain with a roaming profile\n" + " \n" + " On Windows, it'll return the value of `%LOCALAPPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check DG_DATA_HOME ```$H`````ocal/share, r```g``` the first one that is a valid directory\n" +). +-spec data_local_dir() -> {ok, binary()} | {error, nil}. +data_local_dir() -> + case platform:os() of + win32 -> + check_dir_from_env([<<"LOCALAPPDATA"/utf8>>]); + + _ -> + data_dir() + end. + +-file("src/directories.gleam", 178). +?DOC( + " Returns the path to which user-specific executable files may be written. \n" + " \n" + " On Linux and FreeBSD, it'll check $XDG_BIN_HOME, $HOME/.local/bin, $XDG_DATA_HOME/../bin and return the first one that is a valid directory\n" + " \n" + " On all other platforms, it'll always return `Error(Nil)`\n" +). +-spec executable_dir() -> {ok, binary()} | {error, nil}. +executable_dir() -> + case platform:os() of + win32 -> + {error, nil}; + + darwin -> + {error, nil}; + + linux -> + check_dirs( + [get_env(<<"XDG_BIN_HOME"/utf8>>), + home_dir_path(<<"/.local/bin"/utf8>>), + <<(get_env(<<"XDG_DATA_HOME"/utf8>>))/binary, + "../bin"/utf8>>] + ); + + free_bsd -> + check_dirs( + [get_env(<<"XDG_BIN_HOME"/utf8>>), + home_dir_path(<<"/.local/bin"/utf8>>), + <<(get_env(<<"XDG_DATA_HOME"/utf8>>))/binary, + "../bin"/utf8>>] + ); + + open_bsd -> + check_dirs( + [get_env(<<"XDG_BIN_HOME"/utf8>>), + home_dir_path(<<"/.local/bin"/utf8>>), + <<(get_env(<<"XDG_DATA_HOME"/utf8>>))/binary, + "../bin"/utf8>>] + ); + + sun_os -> + check_dirs( + [get_env(<<"XDG_BIN_HOME"/utf8>>), + home_dir_path(<<"/.local/bin"/utf8>>), + <<(get_env(<<"XDG_DATA_HOME"/utf8>>))/binary, + "../bin"/utf8>>] + ); + + aix -> + check_dirs( + [get_env(<<"XDG_BIN_HOME"/utf8>>), + home_dir_path(<<"/.local/bin"/utf8>>), + <<(get_env(<<"XDG_DATA_HOME"/utf8>>))/binary, + "../bin"/utf8>>] + ); + + {other_os, Os} -> + other_os_message(Os) + end. + +-file("src/directories.gleam", 202). +?DOC( + " Returns the path to the user-specific preferences directory. This directory may be synced across computers\n" + " \n" + " On Windows, it'll return the value of `%APPDATA%` if it exists\n" + " \n" + " On MacOS, it'll return the value of `$HOME/Library/Preferences` if it exists\n" + " \n" + " On Linux and FreeBSD, it'll check $XDG_CONFIG_HOME and $HOME/.config, returning the first one that is a valid directory\n" +). +-spec preference_dir() -> {ok, binary()} | {error, nil}. +preference_dir() -> + case platform:os() of + darwin -> + check_dirs([home_dir_path(<<"/Library/Preferences"/utf8>>)]); + + _ -> + config_dir() + end. + +-file("src/directories.gleam", 214). +?DOC( + " Returns the path to which user-specific runtime files and other file objects may be placed. \n" + " \n" + " On Linux and FreeBSD, it'll check $XDG_RUNTIME_DIR if it is a valid directory\n" + " \n" + " On all other platforms, it'll always return `Error(Nil)`\n" +). +-spec runtime_dir() -> {ok, binary()} | {error, nil}. +runtime_dir() -> + case platform:os() of + win32 -> + {error, nil}; + + darwin -> + {error, nil}; + + linux -> + check_dir_from_env([<<"XDG_RUNTIME_DIR"/utf8>>]); + + free_bsd -> + check_dir_from_env([<<"XDG_RUNTIME_DIR"/utf8>>]); + + open_bsd -> + check_dir_from_env([<<"XDG_RUNTIME_DIR"/utf8>>]); + + sun_os -> + check_dir_from_env([<<"XDG_RUNTIME_DIR"/utf8>>]); + + aix -> + check_dir_from_env([<<"XDG_RUNTIME_DIR"/utf8>>]); + + {other_os, Os} -> + other_os_message(Os) + end. + +-file("src/directories.gleam", 234). +?DOC( + " Returns the path to which user-specific state may be stored. \n" + " \n" + " The state directory contains data that should be retained between sessions (unlike the runtime directory), \n" + " but may not be important/portable enough to be synchronized across machines (unlike the config/preferences/data directories).\n" + " \n" + " On Linux and FreeBSD, it'll check $XDG_STATE_HOME and $HOME/.local/state, returning the first one that is a valid directory\n" + " \n" + " On all other platforms, it'll always return `Error(Nil)`\n" +). +-spec state_dir() -> {ok, binary()} | {error, nil}. +state_dir() -> + case platform:os() of + win32 -> + {error, nil}; + + darwin -> + {error, nil}; + + linux -> + check_dirs( + [get_env(<<"XDG_STATE_HOME"/utf8>>), + home_dir_path(<<"/.local/state"/utf8>>)] + ); + + free_bsd -> + check_dirs( + [get_env(<<"XDG_STATE_HOME"/utf8>>), + home_dir_path(<<"/.local/state"/utf8>>)] + ); + + open_bsd -> + check_dirs( + [get_env(<<"XDG_STATE_HOME"/utf8>>), + home_dir_path(<<"/.local/state"/utf8>>)] + ); + + sun_os -> + check_dirs( + [get_env(<<"XDG_STATE_HOME"/utf8>>), + home_dir_path(<<"/.local/state"/utf8>>)] + ); + + aix -> + check_dirs( + [get_env(<<"XDG_STATE_HOME"/utf8>>), + home_dir_path(<<"/.local/state"/utf8>>)] + ); + + {other_os, Os} -> + other_os_message(Os) + end. diff --git a/examples/http_interactions/build/packages/directories/src/directories.gleam b/examples/http_interactions/build/packages/directories/src/directories.gleam new file mode 100644 index 0000000..0785fb4 --- /dev/null +++ b/examples/http_interactions/build/packages/directories/src/directories.gleam @@ -0,0 +1,245 @@ +import envoy +import gleam/io +import gleam/list +import gleam/result +import gleam/string +import platform +import simplifile + +/// Return the first environment variable from the list +/// that is set and is a valid directory +fn check_dir_from_env(vars: List(String)) -> Result(String, Nil) { + vars |> list.filter_map(envoy.get) |> check_dirs +} + +/// Return the first directory from the list that exists, or Nil +fn check_dirs(paths: List(String)) -> Result(String, Nil) { + paths + |> list.filter(fn(a) { + !string.is_empty(a) && result.unwrap(simplifile.is_directory(a), False) + }) + |> list.first +} + +fn get_env(var: String) -> String { + result.unwrap(envoy.get(var), "") +} + +fn home_dir_path(path: String) -> String { + get_env("HOME") <> path +} + +fn other_os_message(other_os: String) -> Result(String, Nil) { + io.print_error( + "[WARN][directories] Operating system '" + <> other_os + <> "' is not supported by this library", + ) + Error(Nil) +} + +/// Returns the path to a temporary directory +/// +/// It'll first check `%TMPDIR%`, `%TEMP%`, `%TMP%`, and return the first one that is a valid directory +/// +/// If that fails, It'll check `C:\TEMP`, `C:\TMP`, `\TEMP`, `\TMP` on windows. +/// +/// On MacOS, Linux, and FreeBSD, it'll check `/tmp`, `/var/tmp`, `/usr/tmp`, +pub fn tmp_dir() -> Result(String, Nil) { + // This behavior is mostly copied from how python's tempfile module works with small changes + // It'll first check the set of env vars, then the known paths, and if none of them exist, the current working directory + // TODO: check if this even works on windows + case check_dir_from_env(["TMPDIR", "TEMP", "TMP"]) { + Ok(path) -> Ok(path) + Error(Nil) -> { + case platform.os() { + platform.Win32 -> check_dirs(["C:\\TEMP", "C:\\TMP", "\\TEMP", "\\TMP"]) + platform.Darwin + | platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> check_dirs(["/tmp", "/var/tmp", "/usr/tmp"]) + platform.OtherOs(os) -> other_os_message(os) + } + } + } +} + +/// Returns the path to the user's home directory +/// +/// It'll check `%UserProfile%` and `%Profile%` on windows, returning first one that is a valid directory +/// +/// On MacOS, Linux, and FreeBSD, it'll return the value of `$HOME` if it exists +pub fn home_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 -> check_dir_from_env(["UserProfile", "Profile"]) + platform.Darwin + | platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> check_dir_from_env(["HOME"]) + platform.OtherOs(os) -> other_os_message(os) + } +} + +/// Returns the path to the user-specific cache directory +/// +/// On Windows, it'll return the value of `%APPDATA%` if it exists +/// +/// On MacOS, it'll return value of `$HOME/Library/Caches` if it exists +/// +/// On Linux and FreeBSD, it'll check `$XDG_CACHE_HOME` and `$HOME/.cache`, returning the first one that is a valid directory +pub fn cache_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 -> check_dir_from_env(["APPDATA"]) + platform.Darwin -> check_dirs([get_env("HOME") <> "/Library/Caches"]) + platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> + check_dirs([get_env("XDG_CACHE_HOME"), home_dir_path("/.cache")]) + platform.OtherOs(os) -> other_os_message(os) + } +} + +/// Returns the path to the user-specific config directory. This directory may be synced across computers +/// +/// On Windows, it'll return the value of `%APPDATA%` if it exists +/// +/// On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists +/// +/// On Linux and FreeBSD, it'll check `$XDG_CONFIG_HOME` and `$HOME/.config`, returning the first one that is a valid directory +pub fn config_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 -> check_dir_from_env(["APPDATA"]) + platform.Darwin -> + check_dirs([get_env("HOME") <> "/Library/Application Support"]) + platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> + check_dirs([get_env("XDG_CONFIG_HOME"), home_dir_path("/.config")]) + platform.OtherOs(os) -> other_os_message(os) + } +} + +/// Returns the path to the user-specific local config directory. Similar to `config_dir`, except Windows won't sync it when connected to a domain with a roaming profile +/// +/// On Windows, it'll return the value of `%LOCALAPPDATA%` if it exists +/// +/// On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists +/// +/// On Linux and FreeBSD, it'll check `$XDG_CONFIG_HOME` and `$HOME/.config`, returning the first one that is a valid directory +pub fn config_local_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 -> check_dir_from_env(["LOCALAPPDATA"]) + _ -> config_dir() + } +} + +/// Returns the path to the user-specific data directory. This directory may be synced across computers +/// +/// On Windows, it'll return the value of `%APPDATA%` if it exists +/// +/// On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists +/// +/// On Linux and FreeBSD, it'll check `$XDG_DATA_HOME``and $HOME/.local/share, returning the first one that is a valid directory +pub fn data_dir() -> Result(String, Nil) { + case platform.os() { + platform.Linux | platform.FreeBsd -> + check_dirs([get_env("XDG_DATA_HOME"), home_dir_path("/.local/share")]) + _ -> config_dir() + } +} + +/// Returns the path to the user-specific data directory. Similar to `data_dir`, except Windows won't sync it when connected to a domain with a roaming profile +/// +/// On Windows, it'll return the value of `%LOCALAPPDATA%` if it exists +/// +/// On MacOS, it'll return the value of `$HOME/Library/Application Support` if it exists +/// +/// On Linux and FreeBSD, it'll check DG_DATA_HOME ```$H`````ocal/share, r```g``` the first one that is a valid directory +pub fn data_local_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 -> check_dir_from_env(["LOCALAPPDATA"]) + _ -> data_dir() + } +} + +/// Returns the path to which user-specific executable files may be written. +/// +/// On Linux and FreeBSD, it'll check $XDG_BIN_HOME, $HOME/.local/bin, $XDG_DATA_HOME/../bin and return the first one that is a valid directory +/// +/// On all other platforms, it'll always return `Error(Nil)` +pub fn executable_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 | platform.Darwin -> Error(Nil) + platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> + check_dirs([ + get_env("XDG_BIN_HOME"), + home_dir_path("/.local/bin"), + get_env("XDG_DATA_HOME") <> "../bin", + ]) + platform.OtherOs(os) -> other_os_message(os) + } +} + +/// Returns the path to the user-specific preferences directory. This directory may be synced across computers +/// +/// On Windows, it'll return the value of `%APPDATA%` if it exists +/// +/// On MacOS, it'll return the value of `$HOME/Library/Preferences` if it exists +/// +/// On Linux and FreeBSD, it'll check $XDG_CONFIG_HOME and $HOME/.config, returning the first one that is a valid directory +pub fn preference_dir() -> Result(String, Nil) { + case platform.os() { + platform.Darwin -> check_dirs([home_dir_path("/Library/Preferences")]) + _ -> config_dir() + } +} + +/// Returns the path to which user-specific runtime files and other file objects may be placed. +/// +/// On Linux and FreeBSD, it'll check $XDG_RUNTIME_DIR if it is a valid directory +/// +/// On all other platforms, it'll always return `Error(Nil)` +pub fn runtime_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 | platform.Darwin -> Error(Nil) + platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> check_dir_from_env(["XDG_RUNTIME_DIR"]) + platform.OtherOs(os) -> other_os_message(os) + } +} + +/// Returns the path to which user-specific state may be stored. +/// +/// The state directory contains data that should be retained between sessions (unlike the runtime directory), +/// but may not be important/portable enough to be synchronized across machines (unlike the config/preferences/data directories). +/// +/// On Linux and FreeBSD, it'll check $XDG_STATE_HOME and $HOME/.local/state, returning the first one that is a valid directory +/// +/// On all other platforms, it'll always return `Error(Nil)` +pub fn state_dir() -> Result(String, Nil) { + case platform.os() { + platform.Win32 | platform.Darwin -> Error(Nil) + platform.Linux + | platform.FreeBsd + | platform.OpenBsd + | platform.SunOs + | platform.Aix -> + check_dirs([get_env("XDG_STATE_HOME"), home_dir_path("/.local/state")]) + platform.OtherOs(os) -> other_os_message(os) + } +} diff --git a/examples/http_interactions/build/packages/envoy/README.md b/examples/http_interactions/build/packages/envoy/README.md new file mode 100644 index 0000000..bb5889f --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/README.md @@ -0,0 +1,33 @@ +# envoy + +A zero dependency cross platform Gleam package for reading environment variables. + +[![Package Version](https://img.shields.io/hexpm/v/envoy)](https://hex.pm/packages/envoy) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/envoy/) + +```sh +gleam add envoy@1 +``` + +```gleam +import envoy + +pub fn main() { + // Get an environment variable + envoy.get("PORT") + + // Set an environment variable + envoy.set("PORT", "8080") + + // Unset an environment variable + envoy.unset("PORT") + + // Get all environment variables + envoy.all() +} +``` + +That's pretty much it, but documentation can be found at +. + +(OK I lied, it has one dep. The standard library so we can use `Dict` for `all`). diff --git a/examples/http_interactions/build/packages/envoy/gleam.toml b/examples/http_interactions/build/packages/envoy/gleam.toml new file mode 100644 index 0000000..7dde3a4 --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/gleam.toml @@ -0,0 +1,15 @@ +name = "envoy" +version = "1.2.0" +gleam = ">= 1.13.0" +description = "A zero dependency cross platform Gleam package for reading environment variables" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "envoy" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" + +[dev-dependencies] diff --git a/examples/http_interactions/build/packages/envoy/src/envoy.app.src b/examples/http_interactions/build/packages/envoy/src/envoy.app.src new file mode 100644 index 0000000..094c337 --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/src/envoy.app.src @@ -0,0 +1,9 @@ +{application, envoy, [ + {vsn, "1.2.0"}, + {applications, [gleam_stdlib]}, + {description, "A zero dependency cross platform Gleam package for reading environment variables"}, + {modules, [envoy, + envoy@@main, + envoy_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/envoy/src/envoy.erl b/examples/http_interactions/build/packages/envoy/src/envoy.erl new file mode 100644 index 0000000..8b11dfd --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/src/envoy.erl @@ -0,0 +1,73 @@ +-module(envoy). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/envoy.gleam"). +-export([get/1, set/2, unset/1, all/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/envoy.gleam", 17). +?DOC( + " Get an environment variable by name.\n" + "\n" + " ```gleam\n" + " get(\"HOME\")\n" + " // -> Ok(\"/home/lucy\")\n" + " ```\n" + "\n" + " ```gleam\n" + " get(\"WORLD_PEACE\")\n" + " // -> Error(Nil)\n" + " ```\n" +). +-spec get(binary()) -> {ok, binary()} | {error, nil}. +get(Name) -> + envoy_ffi:get(Name). + +-file("src/envoy.gleam", 33). +?DOC( + " Set an environment variable.\n" + "\n" + " ```gleam\n" + " get(\"FAVOURITE_COLOUR\")\n" + " // -> Error(Nil)\n" + " set(\"FAVOURITE_COLOUR\", \"Pink\")\n" + "\n" + " get(\"FAVOURITE_COLOUR\")\n" + " // -> Ok(\"Pink\")\n" + " ```\n" +). +-spec set(binary(), binary()) -> nil. +set(Name, Value) -> + envoy_ffi:set(Name, Value). + +-file("src/envoy.gleam", 51). +?DOC( + " Unset an environment variable.\n" + "\n" + " ```gleam\n" + " set(\"FAVOURITE_COLOUR\", \"Pink\")\n" + "\n" + " get(\"FAVOURITE_COLOUR\")\n" + " // -> Ok(\"Pink\")\n" + "\n" + " unset(\"FAVOURITE_COLOUR\")\n" + "\n" + " get(\"FAVOURITE_COLOUR\")\n" + " // -> Error(Nil)\n" + " ```\n" +). +-spec unset(binary()) -> nil. +unset(Name) -> + envoy_ffi:unset(Name). + +-file("src/envoy.gleam", 57). +?DOC(" Get all the environment variables.\n"). +-spec all() -> gleam@dict:dict(binary(), binary()). +all() -> + envoy_ffi:all(). diff --git a/examples/http_interactions/build/packages/envoy/src/envoy.gleam b/examples/http_interactions/build/packages/envoy/src/envoy.gleam new file mode 100644 index 0000000..ed55f56 --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/src/envoy.gleam @@ -0,0 +1,57 @@ +import gleam/dict.{type Dict} + +/// Get an environment variable by name. +/// +/// ```gleam +/// get("HOME") +/// // -> Ok("/home/lucy") +/// ``` +/// +/// ```gleam +/// get("WORLD_PEACE") +/// // -> Error(Nil) +/// ``` +/// +@external(erlang, "envoy_ffi", "get") +@external(javascript, "./envoy_ffi.mjs", "get") +pub fn get(name: String) -> Result(String, Nil) + +// +/// Set an environment variable. +/// +/// ```gleam +/// get("FAVOURITE_COLOUR") +/// // -> Error(Nil) +/// set("FAVOURITE_COLOUR", "Pink") +/// +/// get("FAVOURITE_COLOUR") +/// // -> Ok("Pink") +/// ``` +/// +@external(erlang, "envoy_ffi", "set") +@external(javascript, "./envoy_ffi.mjs", "set") +pub fn set(name: String, value: String) -> Nil + +/// Unset an environment variable. +/// +/// ```gleam +/// set("FAVOURITE_COLOUR", "Pink") +/// +/// get("FAVOURITE_COLOUR") +/// // -> Ok("Pink") +/// +/// unset("FAVOURITE_COLOUR") +/// +/// get("FAVOURITE_COLOUR") +/// // -> Error(Nil) +/// ``` +/// +@external(erlang, "envoy_ffi", "unset") +@external(javascript, "./envoy_ffi.mjs", "unset") +pub fn unset(name: String) -> Nil + +/// Get all the environment variables. +/// +@external(erlang, "envoy_ffi", "all") +@external(javascript, "./envoy_ffi.mjs", "all") +pub fn all() -> Dict(String, String) diff --git a/examples/http_interactions/build/packages/envoy/src/envoy_ffi.erl b/examples/http_interactions/build/packages/envoy/src/envoy_ffi.erl new file mode 100644 index 0000000..8da31da --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/src/envoy_ffi.erl @@ -0,0 +1,24 @@ +-module(envoy_ffi). + +-export([get/1, set/2, unset/1, all/0]). + +get(Key) -> + case os:getenv(unicode:characters_to_list(Key)) of + false -> {error, nil}; + Value -> {ok, unicode:characters_to_binary(Value)} + end. + +set(Key, Value) -> + os:putenv(unicode:characters_to_list(Key), unicode:characters_to_list(Value)), + nil. + +unset(Key) -> + os:unsetenv(unicode:characters_to_list(Key)), + nil. + +all() -> + BinVars = lists:map(fun(VarString) -> + [VarName, VarVal] = string:split(VarString, "="), + {unicode:characters_to_binary(VarName), unicode:characters_to_binary(VarVal)} + end, os:getenv()), + maps:from_list(BinVars). diff --git a/examples/http_interactions/build/packages/envoy/src/envoy_ffi.mjs b/examples/http_interactions/build/packages/envoy/src/envoy_ffi.mjs new file mode 100644 index 0000000..ed3e5bb --- /dev/null +++ b/examples/http_interactions/build/packages/envoy/src/envoy_ffi.mjs @@ -0,0 +1,53 @@ +import { Result$Ok, Result$Error } from "./gleam.mjs"; +import { new$ as newDict, insert } from "../gleam_stdlib/gleam/dict.mjs"; + +export function get(key) { + let value; + + if (globalThis.Deno) { + value = Deno.env.get(key); + } else if (globalThis.process) { + value = process.env[key]; + } + + if (value === undefined) { + return Result$Error(undefined); + } else { + return Result$Ok(value); + } +} + +export function set(key, value) { + if (globalThis.Deno) { + Deno.env.set(key, value); + } else if (globalThis.process) { + process.env[key] = value; + } +} + +export function unset(key) { + if (globalThis.Deno) { + Deno.env.delete(key); + } else if (globalThis.process) { + delete process.env[key]; + } +} + +export function all() { + let environmentVariables = {}; + + if (globalThis.Deno) { + environmentVariables = Deno.env.toObject(); + } else if (globalThis.process) { + environmentVariables = process.env; + } + + let result = newDict(); + for (let key in environmentVariables) { + if (Object.hasOwn(environmentVariables, key)) { + result = insert(result, key, environmentVariables[key]); + } + } + + return result; +} diff --git a/examples/http_interactions/build/packages/exception/LICENCE b/examples/http_interactions/build/packages/exception/LICENCE new file mode 100644 index 0000000..db9a728 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2023 onwards, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/exception/README.md b/examples/http_interactions/build/packages/exception/README.md new file mode 100644 index 0000000..a0caa34 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/README.md @@ -0,0 +1,12 @@ +# exception + +[![Package Version](https://img.shields.io/hexpm/v/exception)](https://hex.pm/packages/exception) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/exception/) + +A tiny package for dealing with exceptions + +```sh +gleam add exception +``` + +Documentation can be found at . diff --git a/examples/http_interactions/build/packages/exception/gleam.toml b/examples/http_interactions/build/packages/exception/gleam.toml new file mode 100644 index 0000000..d62bc09 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/gleam.toml @@ -0,0 +1,17 @@ +name = "exception" +version = "2.1.0" +gleam = ">= 1.11.0" +description = "A tiny package for dealing with exceptions" +licences = ["Apache-2.0"] + +repository = { type = "github", user = "lpil", repo = "exception" } +links = [ + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = "~> 0.30 or ~> 1.0" + +[dev-dependencies] +gleeunit = "~> 1.0" +simplifile = "~> 2.0" diff --git a/examples/http_interactions/build/packages/exception/src/exception.app.src b/examples/http_interactions/build/packages/exception/src/exception.app.src new file mode 100644 index 0000000..cf37143 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/src/exception.app.src @@ -0,0 +1,9 @@ +{application, exception, [ + {vsn, "2.1.0"}, + {applications, [gleam_stdlib]}, + {description, "A tiny package for dealing with exceptions"}, + {modules, [exception, + exception@@main, + exception_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/exception/src/exception.erl b/examples/http_interactions/build/packages/exception/src/exception.erl new file mode 100644 index 0000000..04dd0f6 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/src/exception.erl @@ -0,0 +1,67 @@ +-module(exception). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). +-define(FILEPATH, "src/exception.gleam"). +-export([rescue/1, defer/2, on_crash/2]). +-export_type([exception/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type exception() :: {errored, gleam@dynamic:dynamic_()} | + {thrown, gleam@dynamic:dynamic_()} | + {exited, gleam@dynamic:dynamic_()}. + +-file("src/exception.gleam", 29). +?DOC( + " This function will catch any crash and convert it into a result rather than\n" + " crashing the process.\n" + "\n" + " You should ideally never use this function! Exceptions are not flow control\n" + " in Gleam, a result type should be used instead. This function is only if you\n" + " need to perform some cleanup when a crash occurs, and then you should favour\n" + " `defer` if possible.\n" +). +-spec rescue(fun(() -> EHB)) -> {ok, EHB} | {error, exception()}. +rescue(Body) -> + exception_ffi:rescue(Body). + +-file("src/exception.gleam", 51). +?DOC( + " This function will run a cleanup function after the given body function, even\n" + " if the body function crashes.\n" + "\n" + " You should ideally never use this function! Exceptions are not flow control\n" + " in Gleam, a result type should be used instead. This function is only if you\n" + " need to perform some cleanup when a crash occurs.\n" + "\n" + " # Examples\n" + " \n" + " ```gleam\n" + " pub fn run_with_lock(f: fn() -> a) -> a {\n" + " let lock = acquire()\n" + " use <- defer(fn() { release(lock) })\n" + " f()\n" + " }\n" + " ```\n" +). +-spec defer(fun(() -> any()), fun(() -> EHF)) -> EHF. +defer(Cleanup, Body) -> + exception_ffi:defer(Cleanup, Body). + +-file("src/exception.gleam", 62). +?DOC( + " This function will run a cleanup function after the given body function,\n" + " but only if the body function crashes.\n" + "\n" + " You should ideally never use this function! Exceptions are not flow control\n" + " in Gleam, a result type should be used instead. This function is only if you\n" + " need to perform some cleanup when a crash occurs.\n" +). +-spec on_crash(fun(() -> any()), fun(() -> EHH)) -> EHH. +on_crash(Cleanup, Body) -> + exception_ffi:on_crash(Cleanup, Body). diff --git a/examples/http_interactions/build/packages/exception/src/exception.gleam b/examples/http_interactions/build/packages/exception/src/exception.gleam new file mode 100644 index 0000000..2b8939e --- /dev/null +++ b/examples/http_interactions/build/packages/exception/src/exception.gleam @@ -0,0 +1,62 @@ +import gleam/dynamic.{type Dynamic} + +pub type Exception { + /// An error was raised. + /// On Erlang this would be caused by calling the `erlang:error/1` function, + /// or some other runtime error. + /// On JavaScript this would be caused by throwing an `Error` object. + Errored(Dynamic) + /// A value was thrown. + /// On Erlang this would be caused by calling the `erlang:throw/1` function. + /// On JavaScript this would be caused by throwing any non-`Error` value. + Thrown(Dynamic) + /// A process exited. + /// On Erlang this would be caused by calling the `erlang:exit/1` function. + /// On JavaScript this variant is not used. + Exited(Dynamic) +} + +/// This function will catch any crash and convert it into a result rather than +/// crashing the process. +/// +/// You should ideally never use this function! Exceptions are not flow control +/// in Gleam, a result type should be used instead. This function is only if you +/// need to perform some cleanup when a crash occurs, and then you should favour +/// `defer` if possible. +/// +@external(erlang, "exception_ffi", "rescue") +@external(javascript, "./exception_ffi.mjs", "rescue") +pub fn rescue(body: fn() -> a) -> Result(a, Exception) + +/// This function will run a cleanup function after the given body function, even +/// if the body function crashes. +/// +/// You should ideally never use this function! Exceptions are not flow control +/// in Gleam, a result type should be used instead. This function is only if you +/// need to perform some cleanup when a crash occurs. +/// +/// # Examples +/// +/// ```gleam +/// pub fn run_with_lock(f: fn() -> a) -> a { +/// let lock = acquire() +/// use <- defer(fn() { release(lock) }) +/// f() +/// } +/// ``` +/// +/// +@external(erlang, "exception_ffi", "defer") +@external(javascript, "./exception_ffi.mjs", "defer") +pub fn defer(cleanup: fn() -> b, body: fn() -> a) -> a + +/// This function will run a cleanup function after the given body function, +/// but only if the body function crashes. +/// +/// You should ideally never use this function! Exceptions are not flow control +/// in Gleam, a result type should be used instead. This function is only if you +/// need to perform some cleanup when a crash occurs. +/// +@external(erlang, "exception_ffi", "on_crash") +@external(javascript, "./exception_ffi.mjs", "on_crash") +pub fn on_crash(cleanup: fn() -> b, body: fn() -> a) -> a diff --git a/examples/http_interactions/build/packages/exception/src/exception_ffi.erl b/examples/http_interactions/build/packages/exception/src/exception_ffi.erl new file mode 100644 index 0000000..943b035 --- /dev/null +++ b/examples/http_interactions/build/packages/exception/src/exception_ffi.erl @@ -0,0 +1,23 @@ +-module(exception_ffi). + +-export([rescue/1, defer/2, on_crash/2]). + +rescue(F) -> + try {ok, F()} + catch + error:Term -> {error, {errored, Term}}; + throw:Term -> {error, {thrown, Term}}; + exit:Term -> {error, {exited, Term}} + end. + +defer(Cleanup, Body) -> + try Body() + after Cleanup() + end. + +on_crash(Cleanup, Body) -> + try Body() + catch A:B:C -> + Cleanup(), + erlang:raise(A,B,C) + end. diff --git a/examples/http_interactions/build/packages/exception/src/exception_ffi.mjs b/examples/http_interactions/build/packages/exception/src/exception_ffi.mjs new file mode 100644 index 0000000..3259f4d --- /dev/null +++ b/examples/http_interactions/build/packages/exception/src/exception_ffi.mjs @@ -0,0 +1,31 @@ +import { Ok, Error as GError } from "./gleam.mjs"; +import { Errored, Thrown } from "./exception.mjs"; + +export function on_crash(cleanup, body) { + try { + return body(); + } catch (e) { + cleanup(); + throw e; + } +} + +export function rescue(f) { + try { + return new Ok(f()); + } catch (e) { + if (e instanceof Error) { + return new GError(new Errored(e)); + } else { + return new GError(new Thrown(e)); + } + } +} + +export function defer(cleanup, body) { + try { + return body(); + } finally { + cleanup(); + } +} diff --git a/examples/http_interactions/build/packages/filepath/README.md b/examples/http_interactions/build/packages/filepath/README.md new file mode 100644 index 0000000..6ac366f --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/README.md @@ -0,0 +1,23 @@ +# filepath + +Work with file paths in Gleam! + +[![Package Version](https://img.shields.io/hexpm/v/filepath)](https://hex.pm/packages/filepath) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/filepath/) + +This package works on both Unix and Windows systems, and can run on both Erlang +or JavaScript runtimes. + +```sh +gleam add filepath +``` +```gleam +import filepath + +pub fn main() { + let path = filepath.join("/home/lucy", "pokemon-cards") + // -> "/home/lucy/pokemon-cards" +} +``` + +Documentation can be found here: . diff --git a/examples/http_interactions/build/packages/filepath/gleam.toml b/examples/http_interactions/build/packages/filepath/gleam.toml new file mode 100644 index 0000000..4b44f89 --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/gleam.toml @@ -0,0 +1,15 @@ +name = "filepath" +version = "1.1.2" +description = "Work with file paths in Gleam!" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "filepath" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.43.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/examples/http_interactions/build/packages/filepath/src/filepath.app.src b/examples/http_interactions/build/packages/filepath/src/filepath.app.src new file mode 100644 index 0000000..082fe32 --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/src/filepath.app.src @@ -0,0 +1,9 @@ +{application, filepath, [ + {vsn, "1.1.2"}, + {applications, [gleam_stdlib]}, + {description, "Work with file paths in Gleam!"}, + {modules, [filepath, + filepath@@main, + filepath_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/filepath/src/filepath.erl b/examples/http_interactions/build/packages/filepath/src/filepath.erl new file mode 100644 index 0000000..e774982 --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/src/filepath.erl @@ -0,0 +1,425 @@ +-module(filepath). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([join/2, split_unix/1, directory_name/1, is_absolute/1, split_windows/1, split/1, base_name/1, extension/1, strip_extension/1, expand/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Work with file paths in Gleam!\n" + "\n" + " This library expects paths to be valid unicode. If you need to work with\n" + " non-unicode paths you will need to convert them to unicode before using\n" + " this library.\n" +). + +-file("src/filepath.gleam", 49). +-spec relative(binary()) -> binary(). +relative(Path) -> + case Path of + <<"/"/utf8, Path@1/binary>> -> + relative(Path@1); + + _ -> + Path + end. + +-file("src/filepath.gleam", 56). +-spec remove_trailing_slash(binary()) -> binary(). +remove_trailing_slash(Path) -> + case gleam@string:ends_with(Path, <<"/"/utf8>>) of + true -> + gleam@string:drop_end(Path, 1); + + false -> + Path + end. + +-file("src/filepath.gleam", 35). +?DOC( + " Join two paths together.\n" + "\n" + " This function does not expand `..` or `.` segments, use the `expand`\n" + " function to do this.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " join(\"/usr/local\", \"bin\")\n" + " // -> \"/usr/local/bin\"\n" + " ```\n" +). +-spec join(binary(), binary()) -> binary(). +join(Left, Right) -> + _pipe@2 = case {Left, Right} of + {_, <<"/"/utf8>>} -> + Left; + + {<<""/utf8>>, _} -> + relative(Right); + + {<<"/"/utf8>>, <<"/"/utf8, _/binary>>} -> + Right; + + {<<"/"/utf8>>, _} -> + <>; + + {_, _} -> + _pipe = remove_trailing_slash(Left), + _pipe@1 = gleam@string:append(_pipe, <<"/"/utf8>>), + gleam@string:append(_pipe@1, relative(Right)) + end, + remove_trailing_slash(_pipe@2). + +-file("src/filepath.gleam", 97). +?DOC( + " Split a path into its segments, using `/` as the path separator.\n" + "\n" + " Typically you would want to use `split` instead of this function, but if you\n" + " want non-Windows path behaviour on a Windows system then you can use this\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " split(\"/usr/local/bin\", \"bin\")\n" + " // -> [\"/\", \"usr\", \"local\", \"bin\"]\n" + " ```\n" +). +-spec split_unix(binary()) -> list(binary()). +split_unix(Path) -> + _pipe = case gleam@string:split(Path, <<"/"/utf8>>) of + [<<""/utf8>>] -> + []; + + [<<""/utf8>> | Rest] -> + [<<"/"/utf8>> | Rest]; + + Rest@1 -> + Rest@1 + end, + gleam@list:filter(_pipe, fun(X) -> X /= <<""/utf8>> end). + +-file("src/filepath.gleam", 267). +-spec get_directory_name(list(binary()), binary(), binary()) -> binary(). +get_directory_name(Path, Acc, Segment) -> + case Path of + [<<"/"/utf8>> | Rest] -> + get_directory_name( + Rest, + <>, + <<"/"/utf8>> + ); + + [First | Rest@1] -> + get_directory_name(Rest@1, Acc, <>); + + [] -> + Acc + end. + +-file("src/filepath.gleam", 259). +?DOC( + " Get the directory name of a path, that is the path without the file name.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " directory_name(\"/usr/local/bin\")\n" + " // -> \"/usr/local\"\n" + " ```\n" +). +-spec directory_name(binary()) -> binary(). +directory_name(Path) -> + Path@1 = remove_trailing_slash(Path), + case Path@1 of + <<"/"/utf8, Rest/binary>> -> + get_directory_name( + gleam@string:to_graphemes(Rest), + <<"/"/utf8>>, + <<""/utf8>> + ); + + _ -> + get_directory_name( + gleam@string:to_graphemes(Path@1), + <<""/utf8>>, + <<""/utf8>> + ) + end. + +-file("src/filepath.gleam", 294). +?DOC( + " Check if a path is absolute.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " is_absolute(\"/usr/local/bin\")\n" + " // -> True\n" + " ```\n" + "\n" + " ```gleam\n" + " is_absolute(\"usr/local/bin\")\n" + " // -> False\n" + " ```\n" +). +-spec is_absolute(binary()) -> boolean(). +is_absolute(Path) -> + gleam@string:starts_with(Path, <<"/"/utf8>>). + +-file("src/filepath.gleam", 339). +-spec expand_segments(list(binary()), list(binary())) -> {ok, binary()} | + {error, nil}. +expand_segments(Path, Base) -> + case {Base, Path} of + {[<<""/utf8>>], [<<".."/utf8>> | _]} -> + {error, nil}; + + {[], [<<".."/utf8>> | _]} -> + {error, nil}; + + {[_ | Base@1], [<<".."/utf8>> | Path@1]} -> + expand_segments(Path@1, Base@1); + + {_, [<<"."/utf8>> | Path@2]} -> + expand_segments(Path@2, Base); + + {_, [S | Path@3]} -> + expand_segments(Path@3, [S | Base]); + + {_, []} -> + {ok, gleam@string:join(lists:reverse(Base), <<"/"/utf8>>)} + end. + +-file("src/filepath.gleam", 364). +-spec root_slash_to_empty(list(binary())) -> list(binary()). +root_slash_to_empty(Segments) -> + case Segments of + [<<"/"/utf8>> | Rest] -> + [<<""/utf8>> | Rest]; + + _ -> + Segments + end. + +-file("src/filepath.gleam", 153). +-spec pop_windows_drive_specifier(binary()) -> {gleam@option:option(binary()), + binary()}. +pop_windows_drive_specifier(Path) -> + Start = gleam@string:slice(Path, 0, 3), + Codepoints = gleam@string:to_utf_codepoints(Start), + case gleam@list:map(Codepoints, fun gleam@string:utf_codepoint_to_int/1) of + [Drive, Colon, Slash] when (((Slash =:= 47) orelse (Slash =:= 92)) andalso (Colon =:= 58)) andalso (((Drive >= 65) andalso (Drive =< 90)) orelse ((Drive >= 97) andalso (Drive =< 122))) -> + Drive_letter = gleam@string:slice(Path, 0, 1), + Drive@1 = <<(gleam@string:lowercase(Drive_letter))/binary, + ":/"/utf8>>, + Path@1 = gleam@string:drop_start(Path, 3), + {{some, Drive@1}, Path@1}; + + _ -> + {none, Path} + end. + +-file("src/filepath.gleam", 120). +?DOC( + " Split a path into its segments, using `/` and `\\` as the path separators. If\n" + " there is a drive letter at the start of the path then it is lowercased.\n" + "\n" + " Typically you would want to use `split` instead of this function, but if you\n" + " want Windows path behaviour on a non-Windows system then you can use this\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " split(\"/usr/local/bin\", \"bin\")\n" + " // -> [\"/\", \"usr\", \"local\", \"bin\"]\n" + " ```\n" +). +-spec split_windows(binary()) -> list(binary()). +split_windows(Path) -> + {Drive, Path@1} = pop_windows_drive_specifier(Path), + Segments = begin + _pipe = gleam@string:split(Path@1, <<"/"/utf8>>), + gleam@list:flat_map( + _pipe, + fun(_capture) -> gleam@string:split(_capture, <<"\\"/utf8>>) end + ) + end, + Segments@1 = case Drive of + {some, Drive@1} -> + [Drive@1 | Segments]; + + none -> + Segments + end, + case Segments@1 of + [<<""/utf8>>] -> + []; + + [<<""/utf8>> | Rest] -> + [<<"/"/utf8>> | Rest]; + + Rest@1 -> + Rest@1 + end. + +-file("src/filepath.gleam", 77). +?DOC( + " Split a path into its segments.\n" + "\n" + " When running on Windows both `/` and `\\` are treated as path separators, and \n" + " if the path starts with a drive letter then the drive letter then it is\n" + " lowercased.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " split(\"/usr/local/bin\", \"bin\")\n" + " // -> [\"/\", \"usr\", \"local\", \"bin\"]\n" + " ```\n" +). +-spec split(binary()) -> list(binary()). +split(Path) -> + case filepath_ffi:is_windows() of + true -> + split_windows(Path); + + false -> + split_unix(Path) + end. + +-file("src/filepath.gleam", 240). +?DOC( + " Get the base name of a path, that is the name of the file without the\n" + " containing directory.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " base_name(\"/usr/local/bin\")\n" + " // -> \"bin\"\n" + " ```\n" +). +-spec base_name(binary()) -> binary(). +base_name(Path) -> + gleam@bool:guard(Path =:= <<"/"/utf8>>, <<""/utf8>>, fun() -> _pipe = Path, + _pipe@1 = split(_pipe), + _pipe@2 = gleam@list:last(_pipe@1), + gleam@result:unwrap(_pipe@2, <<""/utf8>>) end). + +-file("src/filepath.gleam", 190). +?DOC( + " Get the file extension of a path.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " extension(\"src/main.gleam\")\n" + " // -> Ok(\"gleam\")\n" + " ```\n" + "\n" + " ```gleam\n" + " extension(\"package.tar.gz\")\n" + " // -> Ok(\"gz\")\n" + " ```\n" +). +-spec extension(binary()) -> {ok, binary()} | {error, nil}. +extension(Path) -> + File = base_name(Path), + case gleam@string:split(File, <<"."/utf8>>) of + [<<""/utf8>>, _] -> + {error, nil}; + + [_, Extension] -> + {ok, Extension}; + + [_ | Rest] -> + gleam@list:last(Rest); + + _ -> + {error, nil} + end. + +-file("src/filepath.gleam", 219). +?DOC( + " Remove the extension from a file, if it has any.\n" + " \n" + " ## Examples\n" + " \n" + " ```gleam\n" + " strip_extension(\"src/main.gleam\")\n" + " // -> \"src/main\"\n" + " ```\n" + " \n" + " ```gleam\n" + " strip_extension(\"package.tar.gz\")\n" + " // -> \"package.tar\"\n" + " ```\n" + " \n" + " ```gleam\n" + " strip_extension(\"src/gleam\")\n" + " // -> \"src/gleam\"\n" + " ```\n" +). +-spec strip_extension(binary()) -> binary(). +strip_extension(Path) -> + case extension(Path) of + {ok, Extension} -> + gleam@string:drop_end(Path, gleam@string:length(Extension) + 1); + + {error, nil} -> + Path + end. + +-file("src/filepath.gleam", 324). +?DOC( + " Expand `..` and `.` segments in a path.\n" + "\n" + " If the path has a `..` segment that would go up past the root of the path\n" + " then an error is returned. This may be useful to example to ensure that a\n" + " path specified by a user does not go outside of a directory.\n" + "\n" + " If the path is absolute then the result will always be absolute.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " expand(\"/usr/local/../bin\")\n" + " // -> Ok(\"/usr/bin\")\n" + " ```\n" + "\n" + " ```gleam\n" + " expand(\"/tmp/../..\")\n" + " // -> Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " expand(\"src/../..\")\n" + " // -> Error(\"..\")\n" + " ```\n" +). +-spec expand(binary()) -> {ok, binary()} | {error, nil}. +expand(Path) -> + Is_absolute = is_absolute(Path), + Result = begin + _pipe = Path, + _pipe@1 = split(_pipe), + _pipe@2 = root_slash_to_empty(_pipe@1), + _pipe@3 = expand_segments(_pipe@2, []), + gleam@result:map(_pipe@3, fun remove_trailing_slash/1) + end, + case Is_absolute andalso (Result =:= {ok, <<""/utf8>>}) of + true -> + {ok, <<"/"/utf8>>}; + + false -> + Result + end. diff --git a/examples/http_interactions/build/packages/filepath/src/filepath.gleam b/examples/http_interactions/build/packages/filepath/src/filepath.gleam new file mode 100644 index 0000000..6220708 --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/src/filepath.gleam @@ -0,0 +1,369 @@ +//// Work with file paths in Gleam! +//// +//// This library expects paths to be valid unicode. If you need to work with +//// non-unicode paths you will need to convert them to unicode before using +//// this library. + +// References: +// https://github.com/erlang/otp/blob/master/lib/stdlib/src/filename.erl +// https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/path.ex +// https://github.com/elixir-lang/elixir/blob/main/lib/elixir/test/elixir/path_test.exs +// https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/path/match.go + +import gleam/bool +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string + +@external(erlang, "filepath_ffi", "is_windows") +@external(javascript, "./filepath_ffi.mjs", "is_windows") +fn is_windows() -> Bool + +/// Join two paths together. +/// +/// This function does not expand `..` or `.` segments, use the `expand` +/// function to do this. +/// +/// ## Examples +/// +/// ```gleam +/// join("/usr/local", "bin") +/// // -> "/usr/local/bin" +/// ``` +/// +pub fn join(left: String, right: String) -> String { + case left, right { + _, "/" -> left + "", _ -> relative(right) + "/", "/" <> _ -> right + "/", _ -> left <> right + _, _ -> + remove_trailing_slash(left) + |> string.append("/") + |> string.append(relative(right)) + } + |> remove_trailing_slash +} + +fn relative(path: String) -> String { + case path { + "/" <> path -> relative(path) + _ -> path + } +} + +fn remove_trailing_slash(path: String) -> String { + case string.ends_with(path, "/") { + True -> string.drop_end(path, 1) + False -> path + } +} + +// TODO: Windows support +/// Split a path into its segments. +/// +/// When running on Windows both `/` and `\` are treated as path separators, and +/// if the path starts with a drive letter then the drive letter then it is +/// lowercased. +/// +/// ## Examples +/// +/// ```gleam +/// split("/usr/local/bin", "bin") +/// // -> ["/", "usr", "local", "bin"] +/// ``` +/// +pub fn split(path: String) -> List(String) { + case is_windows() { + True -> split_windows(path) + False -> split_unix(path) + } +} + +/// Split a path into its segments, using `/` as the path separator. +/// +/// Typically you would want to use `split` instead of this function, but if you +/// want non-Windows path behaviour on a Windows system then you can use this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// split("/usr/local/bin", "bin") +/// // -> ["/", "usr", "local", "bin"] +/// ``` +/// +pub fn split_unix(path: String) -> List(String) { + case string.split(path, "/") { + [""] -> [] + ["", ..rest] -> ["/", ..rest] + rest -> rest + } + |> list.filter(fn(x) { x != "" }) +} + +/// Split a path into its segments, using `/` and `\` as the path separators. If +/// there is a drive letter at the start of the path then it is lowercased. +/// +/// Typically you would want to use `split` instead of this function, but if you +/// want Windows path behaviour on a non-Windows system then you can use this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// split("/usr/local/bin", "bin") +/// // -> ["/", "usr", "local", "bin"] +/// ``` +/// +pub fn split_windows(path: String) -> List(String) { + let #(drive, path) = pop_windows_drive_specifier(path) + + let segments = + string.split(path, "/") + |> list.flat_map(string.split(_, "\\")) + + let segments = case drive { + Some(drive) -> [drive, ..segments] + None -> segments + } + + case segments { + [""] -> [] + ["", ..rest] -> ["/", ..rest] + rest -> rest + } +} + +const codepoint_slash = 47 + +const codepoint_backslash = 92 + +const codepoint_colon = 58 + +const codepoint_a = 65 + +const codepoint_z = 90 + +const codepoint_a_up = 97 + +const codepoint_z_up = 122 + +fn pop_windows_drive_specifier(path: String) -> #(Option(String), String) { + let start = string.slice(from: path, at_index: 0, length: 3) + let codepoints = string.to_utf_codepoints(start) + case list.map(codepoints, string.utf_codepoint_to_int) { + [drive, colon, slash] + if { slash == codepoint_slash || slash == codepoint_backslash } + && colon == codepoint_colon + && { + drive >= codepoint_a + && drive <= codepoint_z + || drive >= codepoint_a_up + && drive <= codepoint_z_up + } + -> { + let drive_letter = string.slice(from: path, at_index: 0, length: 1) + let drive = string.lowercase(drive_letter) <> ":/" + let path = string.drop_start(path, 3) + #(Some(drive), path) + } + _ -> #(None, path) + } +} + +/// Get the file extension of a path. +/// +/// ## Examples +/// +/// ```gleam +/// extension("src/main.gleam") +/// // -> Ok("gleam") +/// ``` +/// +/// ```gleam +/// extension("package.tar.gz") +/// // -> Ok("gz") +/// ``` +/// +pub fn extension(path: String) -> Result(String, Nil) { + let file = base_name(path) + case string.split(file, ".") { + ["", _] -> Error(Nil) + [_, extension] -> Ok(extension) + [_, ..rest] -> list.last(rest) + _ -> Error(Nil) + } +} + +/// Remove the extension from a file, if it has any. +/// +/// ## Examples +/// +/// ```gleam +/// strip_extension("src/main.gleam") +/// // -> "src/main" +/// ``` +/// +/// ```gleam +/// strip_extension("package.tar.gz") +/// // -> "package.tar" +/// ``` +/// +/// ```gleam +/// strip_extension("src/gleam") +/// // -> "src/gleam" +/// ``` +/// +pub fn strip_extension(path: String) -> String { + case extension(path) { + Ok(extension) -> + // Since the extension string doesn't have a leading `.` + // we drop a grapheme more to remove that as well. + string.drop_end(path, string.length(extension) + 1) + Error(Nil) -> path + } +} + +// TODO: windows support +/// Get the base name of a path, that is the name of the file without the +/// containing directory. +/// +/// ## Examples +/// +/// ```gleam +/// base_name("/usr/local/bin") +/// // -> "bin" +/// ``` +/// +pub fn base_name(path: String) -> String { + use <- bool.guard(when: path == "/", return: "") + + path + |> split + |> list.last + |> result.unwrap("") +} + +// TODO: windows support +/// Get the directory name of a path, that is the path without the file name. +/// +/// ## Examples +/// +/// ```gleam +/// directory_name("/usr/local/bin") +/// // -> "/usr/local" +/// ``` +/// +pub fn directory_name(path: String) -> String { + let path = remove_trailing_slash(path) + case path { + "/" <> rest -> get_directory_name(string.to_graphemes(rest), "/", "") + _ -> get_directory_name(string.to_graphemes(path), "", "") + } +} + +fn get_directory_name( + path: List(String), + acc: String, + segment: String, +) -> String { + case path { + ["/", ..rest] -> get_directory_name(rest, acc <> segment, "/") + [first, ..rest] -> get_directory_name(rest, acc, segment <> first) + [] -> acc + } +} + +// TODO: windows support +/// Check if a path is absolute. +/// +/// ## Examples +/// +/// ```gleam +/// is_absolute("/usr/local/bin") +/// // -> True +/// ``` +/// +/// ```gleam +/// is_absolute("usr/local/bin") +/// // -> False +/// ``` +/// +pub fn is_absolute(path: String) -> Bool { + string.starts_with(path, "/") +} + +//TODO: windows support +/// Expand `..` and `.` segments in a path. +/// +/// If the path has a `..` segment that would go up past the root of the path +/// then an error is returned. This may be useful to example to ensure that a +/// path specified by a user does not go outside of a directory. +/// +/// If the path is absolute then the result will always be absolute. +/// +/// ## Examples +/// +/// ```gleam +/// expand("/usr/local/../bin") +/// // -> Ok("/usr/bin") +/// ``` +/// +/// ```gleam +/// expand("/tmp/../..") +/// // -> Error(Nil) +/// ``` +/// +/// ```gleam +/// expand("src/../..") +/// // -> Error("..") +/// ``` +/// +pub fn expand(path: String) -> Result(String, Nil) { + let is_absolute = is_absolute(path) + let result = + path + |> split + |> root_slash_to_empty + |> expand_segments([]) + |> result.map(remove_trailing_slash) + + case is_absolute && result == Ok("") { + True -> Ok("/") + False -> result + } +} + +fn expand_segments( + path: List(String), + base: List(String), +) -> Result(String, Nil) { + case base, path { + // Going up past the root (empty string in this representation) + [""], ["..", ..] -> Error(Nil) + + // Going up past the top of a relative path + [], ["..", ..] -> Error(Nil) + + // Going up successfully + [_, ..base], ["..", ..path] -> expand_segments(path, base) + + // Discarding `.` + _, [".", ..path] -> expand_segments(path, base) + + // Adding a segment + _, [s, ..path] -> expand_segments(path, [s, ..base]) + + // Done! + _, [] -> Ok(string.join(list.reverse(base), "/")) + } +} + +fn root_slash_to_empty(segments: List(String)) -> List(String) { + case segments { + ["/", ..rest] -> ["", ..rest] + _ -> segments + } +} diff --git a/examples/http_interactions/build/packages/filepath/src/filepath_ffi.erl b/examples/http_interactions/build/packages/filepath/src/filepath_ffi.erl new file mode 100644 index 0000000..2dde1aa --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/src/filepath_ffi.erl @@ -0,0 +1,9 @@ +-module(filepath_ffi). + +-export([is_windows/0]). + +is_windows() -> + case os:type() of + {win32, _} -> true; + _ -> false + end. diff --git a/examples/http_interactions/build/packages/filepath/src/filepath_ffi.mjs b/examples/http_interactions/build/packages/filepath/src/filepath_ffi.mjs new file mode 100644 index 0000000..8919959 --- /dev/null +++ b/examples/http_interactions/build/packages/filepath/src/filepath_ffi.mjs @@ -0,0 +1,6 @@ +export function is_windows() { + return ( + globalThis?.process?.platform === "win32" || + globalThis?.Deno?.build?.os === "windows" + ); +} diff --git a/examples/http_interactions/build/packages/gleam.lock b/examples/http_interactions/build/packages/gleam.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_crypto/LICENSE b/examples/http_interactions/build/packages/gleam_crypto/LICENSE new file mode 100644 index 0000000..59e1345 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright {{copyright_year}}, {{author_name}} <{{author_email}}>. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_crypto/README.md b/examples/http_interactions/build/packages/gleam_crypto/README.md new file mode 100644 index 0000000..56109a6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/README.md @@ -0,0 +1,16 @@ +# crypto + +GitHub release +Discord chat +![CI](https://github.com/gleam-lang/crypto/workflows/test/badge.svg?branch=main) + +A Gleam cryptography library supporting Erlang and JavaScript. + +On JavaScript this package uses Node crypto APIs that are available in server +runtimes such as NodeJS, Bun, and Deno, but are not available in web browsers. + +## Installation + +``` +gleam add gleam_crypto@1 +``` diff --git a/examples/http_interactions/build/packages/gleam_crypto/gleam.toml b/examples/http_interactions/build/packages/gleam_crypto/gleam.toml new file mode 100644 index 0000000..f0c7db1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/gleam.toml @@ -0,0 +1,21 @@ +name = "gleam_crypto" +version = "1.6.0" +gleam = ">= 1.9.0" + +licences = ["Apache-2.0"] +description = "A Gleam cryptography library supporting Erlang and JavaScript" + +repository = { type = "github", user = "gleam-lang", repo = "crypto" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[erlang] +extra_applications = ["crypto"] + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.9.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_crypto/src/gleam/crypto.gleam b/examples/http_interactions/build/packages/gleam_crypto/src/gleam/crypto.gleam new file mode 100644 index 0000000..d05ba98 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/src/gleam/crypto.gleam @@ -0,0 +1,197 @@ +//// Set of cryptographic functions. + +import gleam/bit_array +import gleam/int +import gleam/result +import gleam/string + +/// Generates a specified number of bytes randomly uniform 0..255, and returns +/// the result in a binary. +/// +/// On Erlang this uses a cryptographically secure prng seeded and periodically +/// mixed with / operating system provided entropy. By default this is the +/// RAND_bytes method from OpenSSL. +/// +/// +/// On JavaScript the WebCrypto API is used. +/// +@external(erlang, "crypto", "strong_rand_bytes") +@external(javascript, "../gleam_crypto_ffi.mjs", "strongRandomBytes") +pub fn strong_random_bytes(a: Int) -> BitArray + +pub type HashAlgorithm { + Sha224 + Sha256 + Sha384 + Sha512 + /// The MD5 hash algorithm is considered weak and should not be used for + /// security purposes. It may still be useful for non-security purposes or for + /// compatibility with existing systems. + Md5 + /// The SHA1 hash algorithm is considered weak and should not be used for + /// security purposes. It may still be useful for non-security purposes or for + /// compatibility with existing systems. + Sha1 +} + +pub type Hasher + +/// Computes a digest of the input bit string. +/// +/// ## Examples +/// +/// ```gleam +/// let digest = hash(Sha256, <<"a":utf8>>) +/// ``` +/// If you wish to to hash content in multiple chunks rather than all at once +/// see the `new_hasher` function. +/// +pub fn hash(algorithm: HashAlgorithm, data: BitArray) -> BitArray { + new_hasher(algorithm) + |> hash_chunk(data) + |> digest +} + +/// Initializes the state for a streaming hash digest calculation. +/// Then you can add data into the digest algorithm using `hash_update` function. +/// Finally you use `hash_final` to retrieve the digest. +/// +/// It is useful for hashing streams of data or +/// large amount of it without the need to load it all to the memory. +/// +/// ## Examples +/// +/// ```gleam +/// let hash = +/// new_hasher(Sha512) +/// |> hash_chunk(<<"data to hash":utf8>>) +/// |> digest +/// ``` +/// +@external(erlang, "gleam_crypto_ffi", "hash_init") +@external(javascript, "../gleam_crypto_ffi.mjs", "hashInit") +pub fn new_hasher(algorithm: HashAlgorithm) -> Hasher + +/// Adds data to a streaming digest calculation. +/// +/// See `new_hasher` for more information and examples. +/// +@external(erlang, "crypto", "hash_update") +@external(javascript, "../gleam_crypto_ffi.mjs", "hashUpdate") +pub fn hash_chunk(hasher: Hasher, chunk: BitArray) -> Hasher + +/// Finalizes a streaming hash calculation. +/// +/// See `new_hasher` for more information and examples. +/// +@external(erlang, "crypto", "hash_final") +@external(javascript, "../gleam_crypto_ffi.mjs", "digest") +pub fn digest(hasher: Hasher) -> BitArray + +/// Calculates the HMAC (hash-based message authentication code) for a bit +/// string. +/// +/// Based on the Erlang [`crypto:mac`](https://www.erlang.org/doc/man/crypto.html#mac-4) +/// function, or the [`node:crypto.createHmac`](https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options) +/// function on JavaScript. +/// +@external(erlang, "gleam_crypto_ffi", "hmac") +@external(javascript, "../gleam_crypto_ffi.mjs", "hmac") +pub fn hmac(data: BitArray, algorithm: HashAlgorithm, key: BitArray) -> BitArray + +/// Compares the two binaries in constant-time to avoid timing attacks. +/// +/// For more details see: http://codahale.com/a-lesson-in-timing-attacks/ +/// +pub fn secure_compare(left: BitArray, right: BitArray) -> Bool { + case bit_array.byte_size(left) == bit_array.byte_size(right) { + True -> do_secure_compare(left, right, 0) + False -> False + } +} + +fn do_secure_compare(left: BitArray, right: BitArray, accumulator: Int) -> Bool { + case left, right { + <>, <> -> { + let accumulator = + int.bitwise_or(accumulator, int.bitwise_exclusive_or(x, y)) + do_secure_compare(left, right, accumulator) + } + _, _ -> left == right && accumulator == 0 + } +} + +// Based off of https://github.com/elixir-plug/plug_crypto/blob/v1.2.1/lib/plug/crypto/message_verifier.ex#L1 +// +/// Sign a message which can later be verified using the `verify_signed_message` +/// function to detect if the message has been tampered with. +/// +/// A web application could use this verifier to sign HTTP cookies. The data can +/// be read by the user, but cannot be tampered with. +/// +pub fn sign_message( + message: BitArray, + secret: BitArray, + digest_type: HashAlgorithm, +) -> String { + let input = signing_input(digest_type, message) + let signature = hmac(<>, digest_type, secret) + + string.concat([input, ".", bit_array.base64_url_encode(signature, False)]) +} + +fn signing_input(digest_type: HashAlgorithm, message: BitArray) -> String { + let protected = case digest_type { + Sha224 -> "HS224" + Sha256 -> "HS256" + Sha384 -> "HS384" + Sha512 -> "HS512" + Sha1 -> "HS1" + Md5 -> "HMD5" + } + string.concat([ + bit_array.base64_url_encode(<>, False), + ".", + bit_array.base64_url_encode(message, False), + ]) +} + +// Based off of https://github.com/elixir-plug/plug_crypto/blob/v1.2.1/lib/plug/crypto/message_verifier.ex#L1 +// +/// Verify a message created by the `sign_message` function. +/// +pub fn verify_signed_message( + message: String, + secret: BitArray, +) -> Result(BitArray, Nil) { + use #(protected, payload, signature) <- result.try( + case string.split(message, on: ".") { + [a, b, c] -> Ok(#(a, b, c)) + _ -> Error(Nil) + }, + ) + let text = string.concat([protected, ".", payload]) + use payload <- result.try(bit_array.base64_url_decode(payload)) + use signature <- result.try(bit_array.base64_url_decode(signature)) + use protected <- result.try(bit_array.base64_url_decode(protected)) + use digest_type <- result.try(case protected { + // <<"HS224":utf8>> + <<72, 83, 50, 50, 52>> -> Ok(Sha224) + // <<"HS256":utf8>> + <<72, 83, 50, 53, 54>> -> Ok(Sha256) + // <<"HS384":utf8>> + <<72, 83, 51, 56, 52>> -> Ok(Sha384) + // <<"HS512":utf8>> + <<72, 83, 53, 49, 50>> -> Ok(Sha512) + // <<"HS1":utf8>> + <<72, 83, 49>> -> Ok(Sha1) + // <<"HMD5":utf8>> + <<72, 77, 68, 53>> -> Ok(Md5) + _ -> Error(Nil) + }) + let challenge = hmac(<>, digest_type, secret) + case secure_compare(challenge, signature) { + True -> Ok(payload) + False -> Error(Nil) + } +} diff --git a/examples/http_interactions/build/packages/gleam_crypto/src/gleam@crypto.erl b/examples/http_interactions/build/packages/gleam_crypto/src/gleam@crypto.erl new file mode 100644 index 0000000..0ef18ad --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/src/gleam@crypto.erl @@ -0,0 +1,251 @@ +-module(gleam@crypto). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/crypto.gleam"). +-export([strong_random_bytes/1, new_hasher/1, hash_chunk/2, digest/1, hash/2, hmac/3, secure_compare/2, sign_message/3, verify_signed_message/2]). +-export_type([hash_algorithm/0, hasher/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(" Set of cryptographic functions.\n"). + +-type hash_algorithm() :: sha224 | sha256 | sha384 | sha512 | md5 | sha1. + +-type hasher() :: any(). + +-file("src/gleam/crypto.gleam", 20). +?DOC( + " Generates a specified number of bytes randomly uniform 0..255, and returns\n" + " the result in a binary.\n" + "\n" + " On Erlang this uses a cryptographically secure prng seeded and periodically\n" + " mixed with / operating system provided entropy. By default this is the\n" + " RAND_bytes method from OpenSSL.\n" + " \n" + "\n" + " On JavaScript the WebCrypto API is used.\n" +). +-spec strong_random_bytes(integer()) -> bitstring(). +strong_random_bytes(A) -> + crypto:strong_rand_bytes(A). + +-file("src/gleam/crypto.gleam", 73). +?DOC( + " Initializes the state for a streaming hash digest calculation.\n" + " Then you can add data into the digest algorithm using `hash_update` function.\n" + " Finally you use `hash_final` to retrieve the digest.\n" + "\n" + " It is useful for hashing streams of data or\n" + " large amount of it without the need to load it all to the memory.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let hash =\n" + " new_hasher(Sha512)\n" + " |> hash_chunk(<<\"data to hash\":utf8>>)\n" + " |> digest\n" + " ```\n" +). +-spec new_hasher(hash_algorithm()) -> hasher(). +new_hasher(Algorithm) -> + gleam_crypto_ffi:hash_init(Algorithm). + +-file("src/gleam/crypto.gleam", 81). +?DOC( + " Adds data to a streaming digest calculation.\n" + "\n" + " See `new_hasher` for more information and examples.\n" +). +-spec hash_chunk(hasher(), bitstring()) -> hasher(). +hash_chunk(Hasher, Chunk) -> + crypto:hash_update(Hasher, Chunk). + +-file("src/gleam/crypto.gleam", 89). +?DOC( + " Finalizes a streaming hash calculation.\n" + "\n" + " See `new_hasher` for more information and examples.\n" +). +-spec digest(hasher()) -> bitstring(). +digest(Hasher) -> + crypto:hash_final(Hasher). + +-file("src/gleam/crypto.gleam", 49). +?DOC( + " Computes a digest of the input bit string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let digest = hash(Sha256, <<\"a\":utf8>>)\n" + " ```\n" + " If you wish to to hash content in multiple chunks rather than all at once\n" + " see the `new_hasher` function.\n" +). +-spec hash(hash_algorithm(), bitstring()) -> bitstring(). +hash(Algorithm, Data) -> + _pipe = gleam_crypto_ffi:hash_init(Algorithm), + _pipe@1 = crypto:hash_update(_pipe, Data), + crypto:hash_final(_pipe@1). + +-file("src/gleam/crypto.gleam", 100). +?DOC( + " Calculates the HMAC (hash-based message authentication code) for a bit\n" + " string.\n" + "\n" + " Based on the Erlang [`crypto:mac`](https://www.erlang.org/doc/man/crypto.html#mac-4)\n" + " function, or the [`node:crypto.createHmac`](https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options)\n" + " function on JavaScript.\n" +). +-spec hmac(bitstring(), hash_algorithm(), bitstring()) -> bitstring(). +hmac(Data, Algorithm, Key) -> + gleam_crypto_ffi:hmac(Data, Algorithm, Key). + +-file("src/gleam/crypto.gleam", 113). +-spec do_secure_compare(bitstring(), bitstring(), integer()) -> boolean(). +do_secure_compare(Left, Right, Accumulator) -> + case {Left, Right} of + {<>, <>} -> + Accumulator@1 = erlang:'bor'(Accumulator, erlang:'bxor'(X, Y)), + do_secure_compare(Left@1, Right@1, Accumulator@1); + + {_, _} -> + (Left =:= Right) andalso (Accumulator =:= 0) + end. + +-file("src/gleam/crypto.gleam", 106). +?DOC( + " Compares the two binaries in constant-time to avoid timing attacks.\n" + "\n" + " For more details see: http://codahale.com/a-lesson-in-timing-attacks/\n" +). +-spec secure_compare(bitstring(), bitstring()) -> boolean(). +secure_compare(Left, Right) -> + case erlang:byte_size(Left) =:= erlang:byte_size(Right) of + true -> + do_secure_compare(Left, Right, 0); + + false -> + false + end. + +-file("src/gleam/crypto.gleam", 143). +-spec signing_input(hash_algorithm(), bitstring()) -> binary(). +signing_input(Digest_type, Message) -> + Protected = case Digest_type of + sha224 -> + <<"HS224"/utf8>>; + + sha256 -> + <<"HS256"/utf8>>; + + sha384 -> + <<"HS384"/utf8>>; + + sha512 -> + <<"HS512"/utf8>>; + + sha1 -> + <<"HS1"/utf8>>; + + md5 -> + <<"HMD5"/utf8>> + end, + erlang:list_to_binary( + [gleam@bit_array:base64_url_encode(<>, false), + <<"."/utf8>>, + gleam@bit_array:base64_url_encode(Message, false)] + ). + +-file("src/gleam/crypto.gleam", 132). +?DOC( + " Sign a message which can later be verified using the `verify_signed_message`\n" + " function to detect if the message has been tampered with.\n" + "\n" + " A web application could use this verifier to sign HTTP cookies. The data can\n" + " be read by the user, but cannot be tampered with.\n" +). +-spec sign_message(bitstring(), bitstring(), hash_algorithm()) -> binary(). +sign_message(Message, Secret, Digest_type) -> + Input = signing_input(Digest_type, Message), + Signature = gleam_crypto_ffi:hmac(<>, Digest_type, Secret), + erlang:list_to_binary( + [Input, + <<"."/utf8>>, + gleam@bit_array:base64_url_encode(Signature, false)] + ). + +-file("src/gleam/crypto.gleam", 163). +?DOC(" Verify a message created by the `sign_message` function.\n"). +-spec verify_signed_message(binary(), bitstring()) -> {ok, bitstring()} | + {error, nil}. +verify_signed_message(Message, Secret) -> + gleam@result:'try'(case gleam@string:split(Message, <<"."/utf8>>) of + [A, B, C] -> + {ok, {A, B, C}}; + + _ -> + {error, nil} + end, fun(_use0) -> + {Protected, Payload, Signature} = _use0, + Text = erlang:list_to_binary([Protected, <<"."/utf8>>, Payload]), + gleam@result:'try'( + gleam@bit_array:base64_url_decode(Payload), + fun(Payload@1) -> + gleam@result:'try'( + gleam@bit_array:base64_url_decode(Signature), + fun(Signature@1) -> + gleam@result:'try'( + gleam@bit_array:base64_url_decode(Protected), + fun(Protected@1) -> + gleam@result:'try'(case Protected@1 of + <<72, 83, 50, 50, 52>> -> + {ok, sha224}; + + <<72, 83, 50, 53, 54>> -> + {ok, sha256}; + + <<72, 83, 51, 56, 52>> -> + {ok, sha384}; + + <<72, 83, 53, 49, 50>> -> + {ok, sha512}; + + <<72, 83, 49>> -> + {ok, sha1}; + + <<72, 77, 68, 53>> -> + {ok, md5}; + + _ -> + {error, nil} + end, fun(Digest_type) -> + Challenge = gleam_crypto_ffi:hmac( + <>, + Digest_type, + Secret + ), + case secure_compare( + Challenge, + Signature@1 + ) of + true -> + {ok, Payload@1}; + + false -> + {error, nil} + end + end) + end + ) + end + ) + end + ) + end). diff --git a/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto.app.src b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto.app.src new file mode 100644 index 0000000..dd98785 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto.app.src @@ -0,0 +1,10 @@ +{application, gleam_crypto, [ + {vsn, "1.6.0"}, + {applications, [crypto, + gleam_stdlib]}, + {description, "A Gleam cryptography library supporting Erlang and JavaScript"}, + {modules, [gleam@crypto, + gleam_crypto@@main, + gleam_crypto_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.erl b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.erl new file mode 100644 index 0000000..e72d4e2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.erl @@ -0,0 +1,14 @@ +-module(gleam_crypto_ffi). +-export([hmac/3, hash_init/1]). + +convert_algorithm(Algorithm) -> + case Algorithm of + sha1 -> sha; + _ -> Algorithm + end. + +hmac(Data, Algorithm, Key) -> + crypto:mac(hmac, convert_algorithm(Algorithm), Key, Data). + +hash_init(Algorithm) -> + crypto:hash_init(convert_algorithm(Algorithm)). diff --git a/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.mjs b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.mjs new file mode 100644 index 0000000..2dbeaf4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_crypto/src/gleam_crypto_ffi.mjs @@ -0,0 +1,55 @@ +import { BitArray } from "./gleam.mjs"; +import { Sha1, Sha224, Sha256, Sha384, Sha512, Md5 } from "./gleam/crypto.mjs"; +import * as crypto from "node:crypto"; + +function webCrypto() { + if (!globalThis.crypto?.getRandomValues) { + throw new Error("WebCrypto API not supported on this JavaScript runtime"); + } + return globalThis.crypto; +} + +function algorithmName(algorithm) { + if (algorithm instanceof Sha1) { + return "sha1"; + } else if (algorithm instanceof Sha224) { + return "sha224"; + } else if (algorithm instanceof Sha256) { + return "sha256"; + } else if (algorithm instanceof Sha384) { + return "sha384"; + } else if (algorithm instanceof Sha512) { + return "sha512"; + } else if (algorithm instanceof Md5) { + return "md5"; + } else { + throw new Error("Unsupported algorithm"); + } +} + +export function strongRandomBytes(n) { + const array = new Uint8Array(n); + webCrypto().getRandomValues(array); + return new BitArray(array); +} + +export function hmac(data, algorithm, key) { + const hmac = crypto.createHmac(algorithmName(algorithm), key.rawBuffer); + hmac.update(data.rawBuffer); + const array = new Uint8Array(hmac.digest()); + return new BitArray(array); +} + +export function hashInit(algorithm) { + return crypto.createHash(algorithmName(algorithm)); +} + +export function hashUpdate(hasher, hashChunk) { + hasher.update(hashChunk.rawBuffer); + return hasher; +} + +export function digest(hasher) { + const array = new Uint8Array(hasher.digest()); + return new BitArray(array); +} diff --git a/examples/http_interactions/build/packages/gleam_erlang/LICENSE b/examples/http_interactions/build/packages/gleam_erlang/LICENSE new file mode 100644 index 0000000..59e1345 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright {{copyright_year}}, {{author_name}} <{{author_email}}>. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_erlang/README.md b/examples/http_interactions/build/packages/gleam_erlang/README.md new file mode 100644 index 0000000..02fa9f9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/README.md @@ -0,0 +1,25 @@ +# Gleam Erlang 🐙 + +Types and functions for programs running on Erlang! + +[![Package Version](https://img.shields.io/hexpm/v/gleam_erlang)](https://hex.pm/packages/gleam_erlang) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_erlang/) + + +```sh +gleam add gleam_erlang@1 +``` +```gleam +import gleam/io +import gleam/erlang/process + +pub fn main() { + process.spawn(fn() { + io.println("Hello from another process running concurrently!") + }) +} +``` + +Documentation can be found at . + +This library requires OTP 27.0 or higher. diff --git a/examples/http_interactions/build/packages/gleam_erlang/gleam.toml b/examples/http_interactions/build/packages/gleam_erlang/gleam.toml new file mode 100644 index 0000000..f1c49a1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/gleam.toml @@ -0,0 +1,18 @@ +name = "gleam_erlang" + +version = "1.3.0" +licences = ["Apache-2.0"] +description = "Types and functions for programs running on Erlang!" + +repository = { type = "github", user = "gleam-lang", repo = "erlang" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] +gleam = ">= 1.11.0" + +[dependencies] +gleam_stdlib = ">= 0.53.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.3.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl new file mode 100644 index 0000000..61b6542 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl @@ -0,0 +1 @@ +-record(failover, {previous :: gleam@erlang@node:node_()}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl new file mode 100644 index 0000000..0c80158 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl @@ -0,0 +1 @@ +-record(takeover, {previous :: gleam@erlang@node:node_()}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl new file mode 100644 index 0000000..807c997 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl @@ -0,0 +1 @@ +-record(abnormal, {reason :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl new file mode 100644 index 0000000..b82b49f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl @@ -0,0 +1 @@ +-record(cancelled, {time_remaining :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl new file mode 100644 index 0000000..c476308 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl @@ -0,0 +1,4 @@ +-record(exit_message, { + pid :: gleam@erlang@process:pid_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl new file mode 100644 index 0000000..d4801f5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl @@ -0,0 +1 @@ +-record(named_subject, {name :: gleam@erlang@process:name(any())}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl new file mode 100644 index 0000000..d40ace9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl @@ -0,0 +1,5 @@ +-record(port_down, { + monitor :: gleam@erlang@process:monitor(), + port :: gleam@erlang@port:port_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl new file mode 100644 index 0000000..7b013a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl @@ -0,0 +1,5 @@ +-record(process_down, { + monitor :: gleam@erlang@process:monitor(), + pid :: gleam@erlang@process:pid_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl new file mode 100644 index 0000000..8c4b577 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl @@ -0,0 +1,4 @@ +-record(subject, { + owner :: gleam@erlang@process:pid_(), + tag :: gleam@dynamic:dynamic_() +}). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/application.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/application.gleam new file mode 100644 index 0000000..e5c6a40 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/application.gleam @@ -0,0 +1,37 @@ +//// An Erlang application is a collection of code that can be loaded into the +//// Erlang virtual machine and even started and stopped if they define a +//// start module and supervision tree. Each Gleam package is an Erlang +//// application. + +import gleam/erlang/node.{type Node} + +/// The Erlang/OTP application `start` callback takes a start-type as an +/// argument, indicating the context in which the application is being started. +pub type StartType { + /// A normal application start. + Normal + /// The application is distributed and started at the current node because of + /// a takeover from Node, either because Erlang's `application:takeover/2` + /// function has been called, or because the current node has higher priority + /// than the previous node. + Takeover(previous: Node) + /// The application is distributed and started at the current node because of + /// a failover from the previous node. + Failover(previous: Node) +} + +/// Returns the path of an application's `priv` directory, where extra non-Gleam +/// or Erlang files are typically kept. Each Gleam package is an Erlang +/// application. +/// +/// Returns an error if no application was found with the given name. +/// +/// # Example +/// +/// ```gleam +/// application.priv_directory("my_app") +/// // -> Ok("/some/location/my_app/priv") +/// ``` +/// +@external(erlang, "gleam_erlang_ffi", "priv_directory") +pub fn priv_directory(name: String) -> Result(String, Nil) diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam new file mode 100644 index 0000000..3664f03 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam @@ -0,0 +1,81 @@ +import gleam/dynamic +import gleam/dynamic/decode + +/// Atom is a special string-like data-type that is most commonly used for +/// interfacing with code written in other BEAM languages such as Erlang and +/// Elixir. It is preferable to define your own custom types to use instead of +/// atoms where possible. +/// +/// Atoms are not used much in typical Gleam code! +/// +/// ## Creating atoms +/// +/// We can create atoms with the the [`create`](#create) +/// function, though we must be careful when doing so as atoms are never +/// garbage collected. If we create too many atoms (for example, if we convert +/// user input into atoms) we may hit the max limit of atoms and cause the +/// virtual machine to crash. +/// +pub type Atom + +/// Finds an existing atom for the given string. +/// +/// If no atom is found in the virtual machine's atom table for the string then +/// an error is returned. +/// +@external(erlang, "gleam_erlang_ffi", "atom_from_string") +pub fn get(a: String) -> Result(Atom, Nil) + +/// Creates an atom from a string, inserting a new value into the virtual +/// machine's atom table if an atom does not already exist for the given +/// string. +/// +/// We must be careful when using this function as there is a limit to the +/// number of atom that can fit in the virtual machine's atom table. Never +/// convert user input into atoms as filling the atom table will cause the +/// virtual machine to crash! +/// +@external(erlang, "erlang", "binary_to_atom") +pub fn create(a: String) -> Atom + +/// Returns a `String` corresponding to the text representation of the given +/// `Atom`. +/// +/// ## Examples +/// ```gleam +/// let ok_atom = create("ok") +/// to_string(ok_atom) +/// // -> "ok" +/// ``` +/// +@external(erlang, "erlang", "atom_to_binary") +pub fn to_string(a: Atom) -> String + +/// Convert an atom to a dynamic value, throwing away the type information. +/// +/// This may be useful for testing decoders. +/// +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn to_dynamic(a: Atom) -> dynamic.Dynamic + +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn cast_from_dynamic(a: dynamic.Dynamic) -> Atom + +/// A dynamic decoder for atoms. +/// +/// You almost certainly should not use this to work with externally defined +/// functions. They return known types, so you should define the external +/// functions with the correct types, defining wrapper functions in Erlang if +/// the external types cannot be mapped directly onto Gleam types. +/// +pub fn decoder() -> decode.Decoder(Atom) { + decode.new_primitive_decoder("Atom", fn(data) { + case is_atom(data) { + True -> Ok(cast_from_dynamic(data)) + False -> Error(create("nil")) + } + }) +} + +@external(erlang, "erlang", "is_atom") +fn is_atom(data: dynamic.Dynamic) -> Bool diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam new file mode 100644 index 0000000..f7e94da --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam @@ -0,0 +1,24 @@ +//// A charlist is a list of integers where all the integers are valid code +//// points. +//// +//// In practice, you will not come across them often, except perhaps when +//// interfacing with Erlang, in particular when using older libraries that do +//// not accept binaries as arguments. + +/// A list of characters represented as ints. Commonly used with Erlang +/// functions that do not accept binary strings such as Gleam's core string +/// type. +/// +pub type Charlist + +/// Convert a charlist to a string using Erlang's +/// `unicode:characters_to_binary`. +/// +@external(erlang, "unicode", "characters_to_binary") +pub fn to_string(a: Charlist) -> String + +/// Convert a string to a charlist using Erlang's +/// `unicode:characters_to_list`. +/// +@external(erlang, "unicode", "characters_to_list") +pub fn from_string(a: String) -> Charlist diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/node.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/node.gleam new file mode 100644 index 0000000..294d851 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/node.gleam @@ -0,0 +1,63 @@ +//// Multiple Erlang VM instances can form a cluster to make a distributed +//// Erlang system, talking directly to each other using messages rather than +//// other communication protocols like HTTP. In a distributed Erlang system +//// each virtual machine is called a _node_. This module provides Node related +//// types and functions to be used as a foundation by other packages providing +//// more specialised functionality. +//// +//// For more information on distributed Erlang systems see the Erlang +//// documentation: . + +import gleam/erlang/atom.{type Atom} + +pub type Node + +/// Return the current node. +/// +@external(erlang, "erlang", "node") +pub fn self() -> Node + +/// Return a list of all visible nodes in the cluster, not including the current +/// node. +/// +/// The current node can be included by calling `self()` and prepending the +/// result. +/// +/// ```gleam +/// let all_nodes = [node.self(), ..node.visible()] +/// ``` +/// +@external(erlang, "erlang", "nodes") +pub fn visible() -> List(Node) + +pub type ConnectError { + /// Was unable to connect to the node. + FailedToConnect + /// The local node is not alive, so it is not possible to connect to the other + /// node. + LocalNodeIsNotAlive +} + +// TODO: test unknown node +// TODO: test successfully connecting +/// Establish a connection to a node, so the nodes can send messages to each +/// other and any other connected nodes. +/// +/// Returns `Error(FailedToConnect)` if the node is not reachable. +/// +/// Returns `Error(LocalNodeIsNotAlive)` if the local node is not alive, meaning +/// it is not running in distributed mode. +/// +@external(erlang, "gleam_erlang_ffi", "connect_node") +pub fn connect(node: Atom) -> Result(Node, ConnectError) + +/// Get the atom name of a node. +/// +/// ## Examples +/// +/// ```gleam +/// assert name(my_node) == atom.create("app1@localhost") +/// ``` +/// +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn name(node: Node) -> Atom diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/port.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/port.gleam new file mode 100644 index 0000000..4e1b4d8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/port.gleam @@ -0,0 +1,9 @@ +/// Ports are how code running on the Erlang virtual machine interacts with +/// the outside world. Bytes of data can be sent to and read from ports, +/// providing a form of message passing to an external program or resource. +/// +/// For more information on ports see the [Erlang ports documentation][1]. +/// +/// [1]: https://erlang.org/doc/reference_manual/ports.html +/// +pub type Port diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/process.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/process.gleam new file mode 100644 index 0000000..35df254 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/process.gleam @@ -0,0 +1,876 @@ +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/erlang/atom.{type Atom} +import gleam/erlang/port.{type Port} +import gleam/erlang/reference.{type Reference} +import gleam/string + +/// A `Pid` (or Process identifier) is a reference to an Erlang process. Each +/// process has a `Pid` and it is one of the lowest level building blocks of +/// inter-process communication in the Erlang and Gleam OTP frameworks. +/// +pub type Pid + +/// Get the `Pid` for the current process. +/// +@external(erlang, "erlang", "self") +pub fn self() -> Pid + +/// Create a new Erlang process that runs concurrently to the creator. In other +/// languages this might be called a fibre, a green thread, or a coroutine. +/// +/// The child process is linked to the creator process. When a process +/// terminates an exit signal is sent to all other processes that are linked to +/// it, causing the process to either terminate or have to handle the signal. +/// If you want an unlinked process use the `spawn_unlinked` function. +/// +/// More can be read about processes and links in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/reference_manual/processes.html +/// +/// This function starts processes via the Erlang `proc_lib` module, and as +/// such they benefit from the functionality described in the +/// [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html). +/// +@external(erlang, "proc_lib", "spawn_link") +pub fn spawn(running: fn() -> anything) -> Pid + +/// Create a new Erlang process that runs concurrently to the creator. In other +/// languages this might be called a fibre, a green thread, or a coroutine. +/// +/// Typically you want to create a linked process using the `spawn` function, +/// but creating an unlinked process may be occasionally useful. +/// +/// More can be read about processes and links in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/reference_manual/processes.html +/// +/// This function starts processes via the Erlang `proc_lib` module, and as +/// such they benefit from the functionality described in the +/// [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html). +/// +@external(erlang, "proc_lib", "spawn") +pub fn spawn_unlinked(a: fn() -> anything) -> Pid + +/// A `Subject` is a value that processes can use to send and receive messages +/// to and from each other in a well typed way. +/// +/// Each subject is "owned" by the process that created it. Any process can use +/// the `send` function to send a message of the correct type to the process +/// that owns the subject, and the owner can use the `receive` function or the +/// `Selector` type to receive these messages. +/// +/// The `Subject` type is similar to the "channel" types found in other +/// languages and the "topic" concept found in some pub-sub systems. +/// +/// # Examples +/// +/// ```gleam +/// let subject = new_subject() +/// +/// // Send a message with the subject +/// send(subject, "Hello, Joe!") +/// +/// // Receive the message +/// receive(subject, within: 10) +/// ``` +/// +pub opaque type Subject(message) { + Subject(owner: Pid, tag: Dynamic) + NamedSubject(name: Name(message)) +} + +/// Create a subject for the given process with the give tag. This is unsafe! +/// There's nothing here that verifies that the message the subject receives is +/// expected and that the tag is not already in use. +/// +/// You should almost certainly not use this function. +/// +@internal +pub fn unsafely_create_subject(owner: Pid, tag: Dynamic) -> Subject(message) { + Subject(owner, tag) +} + +/// A name is an identity that a process can adopt, after which they will receive +/// messages sent to that name. This has two main advantages: +/// +/// - Structuring OTP programs becomes easier as a name can be passed down the +/// program from the top level, while without names subjects and pids would +/// need to be passed up from the started process and then back down to the +/// code that works with that process. +/// - A new process can adopt the name of one that previously failed, allowing +/// it to transparently take-over and handle messages that are sent to that +/// name. +/// +/// Names are globally unique as each process can have at most 1 name, and each +/// name can be registered by at most 1 process. Create all the names your +/// program needs at the start of your program and pass them down. Names are +/// Erlang atoms internally, so never create them dynamically. Generating too +/// many atoms will result in the atom table getting filled and causing the entire +/// virtual machine to crash. +/// +/// The most commonly used name functions are `new_name`, `register`, and +/// `named_subject`. +/// +pub type Name(message) + +/// Generate a new name that a process can register itself with using the +/// `register` function, and other processes can send messages to using +/// `named_subject`. +/// +/// The string argument is a prefix for the Erlang name. A unique suffix is +/// added to the prefix to make the name, removing the possibility of name +/// collisions. +/// +/// ## Safe use +/// +/// Use this function to create all the names your program needs when it +/// starts. **Never call this function dynamically** such as within a loop or +/// within a process within a supervision tree. +/// +/// Each time this function is called a new atom will be generated. Generating +/// too many atoms will result in the atom table getting filled and causing the +/// entire virtual machine to crash. +/// +@external(erlang, "gleam_erlang_ffi", "new_name") +pub fn new_name(prefix prefix: String) -> Name(message) + +/// Create a subject for a name, which can be used to send and receive messages. +/// +/// All subjects created for the same name behave identically and can be used +/// interchangably. +/// +pub fn named_subject(name: Name(message)) -> Subject(message) { + NamedSubject(name) +} + +/// Get the name of a subject, returning an error if it doesn't have one. +/// +pub fn subject_name(subject: Subject(message)) -> Result(Name(message), Nil) { + case subject { + NamedSubject(name:) -> Ok(name) + Subject(..) -> Error(Nil) + } +} + +/// Create a new `Subject` owned by the current process. +/// +pub fn new_subject() -> Subject(message) { + Subject(owner: self(), tag: reference_to_dynamic(reference.new())) +} + +/// Get the owner process for a subject, which is the process that will +/// receive any messages sent using the subject. +/// +/// If the subject was created from a name and no process is currently +/// registered with that name then this function will return an error. +/// +pub fn subject_owner(subject: Subject(message)) -> Result(Pid, Nil) { + case subject { + NamedSubject(name) -> named(name) + Subject(pid, _) -> Ok(pid) + } +} + +type DoNotLeak + +@external(erlang, "erlang", "send") +fn raw_send(a: Pid, b: message) -> DoNotLeak + +/// Send a message to a process using a `Subject`. The message must be of the +/// type that the `Subject` accepts. +/// +/// This function does not wait for the `Subject` owner process to call the +/// `receive` function, instead it returns once the message has been placed in +/// the process' mailbox. +/// +/// # Named Subjects +/// +/// If this function is called on a named subject for which a process has not been +/// registered, it will simply drop the message as there's no mailbox to send it to. +/// +/// # Panics +/// +/// This function will panic when sending to a named subject if no process is +/// currently registed under that name. +/// +/// # Ordering +/// +/// If process P1 sends two messages to process P2 it is guaranteed that process +/// P1 will receive the messages in the order they were sent. +/// +/// If you wish to receive the messages in a different order you can send them +/// on two different subjects and the receiver function can call the `receive` +/// function for each subject in the desired order, or you can write some Erlang +/// code to perform a selective receive. +/// +/// # Examples +/// +/// ```gleam +/// let subject = new_subject() +/// send(subject, "Hello, Joe!") +/// ``` +/// +pub fn send(subject: Subject(message), message: message) -> Nil { + case subject { + Subject(pid, tag) -> { + raw_send(pid, #(tag, message)) + } + NamedSubject(name) -> { + let assert Ok(pid) = named(name) as "Sending to unregistered name" + raw_send(pid, #(name, message)) + } + } + Nil +} + +/// Receive a message that has been sent to current process using the `Subject`. +/// +/// If there is not an existing message for the `Subject` in the process' +/// mailbox or one does not arrive `within` the permitted timeout then the +/// `Error(Nil)` is returned. +/// +/// Only the process that is owner of the `Subject` can receive a message using +/// it. If a process that does not own the `Subject` attempts to receive with it +/// then it will not receive a message. +/// +/// To wait for messages from multiple `Subject`s at the same time see the +/// `Selector` type. +/// +/// The `within` parameter specifies the timeout duration in milliseconds. +/// +/// ## Panics +/// +/// This function will panic if a process tries to receive with a non-named +/// subject that it does not own. +/// +pub fn receive( + from subject: Subject(message), + within timeout: Int, +) -> Result(message, Nil) { + case subject { + NamedSubject(..) -> perform_receive(subject, timeout) + Subject(owner:, ..) -> + case owner == self() { + True -> perform_receive(subject, timeout) + False -> + panic as "Cannot receive with a subject owned by another process" + } + } +} + +@external(erlang, "gleam_erlang_ffi", "receive") +fn perform_receive( + subject: Subject(message), + timeout: Int, +) -> Result(message, Nil) + +/// Receive a message that has been sent to current process using the `Subject`. +/// +/// Same as `receive` but waits forever and returns the message as is. +@external(erlang, "gleam_erlang_ffi", "receive") +pub fn receive_forever(from subject: Subject(message)) -> message + +/// A type that enables a process to wait for messages from multiple `Subject`s +/// at the same time, returning whichever message arrives first. +/// +/// Used with the `new_selector`, `selector_receive`, and `select*` functions. +/// +/// # Examples +/// +/// ```gleam +/// let int_subject = new_subject() +/// let string_subject = new_subject() +/// send(int_subject, 1) +/// +/// let selector = +/// new_selector() +/// |> select(string_subject) +/// |> select_map(int_subject, int.to_string) +/// +/// selector_receive(selector, 10) +/// // -> Ok("1") +/// ``` +/// +pub type Selector(payload) + +/// Create a new `Selector` which can be used to receive messages on multiple +/// `Subject`s at once. +/// +@external(erlang, "gleam_erlang_ffi", "new_selector") +pub fn new_selector() -> Selector(payload) + +/// Receive a message that has been sent to current process using any of the +/// `Subject`s that have been added to the `Selector` with the `select*` +/// functions. +/// +/// If there is not an existing message for the `Selector` in the process' +/// mailbox or one does not arrive `within` the permitted timeout then the +/// `Error(Nil)` is returned. +/// +/// Only the process that is owner of the `Subject`s can receive a message using +/// them. If a process that does not own the a `Subject` attempts to receive +/// with it then it will not receive a message. +/// +/// To wait forever for the next message rather than for a limited amount of +/// time see the `selector_receive_forever` function. +/// +/// The `within` parameter specifies the timeout duration in milliseconds. +/// +@external(erlang, "gleam_erlang_ffi", "select") +pub fn selector_receive( + from from: Selector(payload), + within within: Int, +) -> Result(payload, Nil) + +/// Similar to the `select` function but will wait forever for a message to +/// arrive rather than timing out after a specified amount of time. +/// +@external(erlang, "gleam_erlang_ffi", "select") +pub fn selector_receive_forever(from from: Selector(payload)) -> payload + +/// Add a transformation function to a selector. When a message is received +/// using this selector the transformation function is applied to the message. +/// +/// This function can be used to change the type of messages received and may +/// be useful when combined with the `merge_selector` function. +/// +@external(erlang, "gleam_erlang_ffi", "map_selector") +pub fn map_selector(a: Selector(a), b: fn(a) -> b) -> Selector(b) + +/// Merge one selector into another, producing a selector that contains the +/// message handlers of both. +/// +/// If a subject is handled by both selectors the handler function of the +/// second selector is used. +/// +@external(erlang, "gleam_erlang_ffi", "merge_selector") +pub fn merge_selector(a: Selector(a), b: Selector(a)) -> Selector(a) + +pub type ExitMessage { + ExitMessage(pid: Pid, reason: ExitReason) +} + +pub type ExitReason { + Normal + Killed + Abnormal(reason: Dynamic) +} + +/// Add a handler for trapped exit messages. In order for these messages to be +/// sent to the process when a linked process exits the process must call the +/// `trap_exit` beforehand. +/// +pub fn select_trapped_exits( + selector: Selector(a), + handler: fn(ExitMessage) -> a, +) -> Selector(a) { + let tag = atom.create("EXIT") + let handler = fn(message: #(Atom, Pid, Dynamic)) -> a { + handler(ExitMessage(message.1, cast_exit_reason(message.2))) + } + insert_selector_handler(selector, #(tag, 3), handler) +} + +/// Discard all messages in the current process' mailbox. +/// +/// Warning: This function may cause other processes to crash if they sent a +/// message to the current process and are waiting for a response, so use with +/// caution. +/// +/// This function may be useful in tests. +/// +@external(erlang, "gleam_erlang_ffi", "flush_messages") +pub fn flush_messages() -> Nil + +/// Add a new `Subject` to the `Selector` so that its messages can be selected +/// from the receiver process inbox. +/// +/// See `select_map` to add subjects of a different message type. +/// +/// See `deselect` to remove a subject from a selector. +/// +pub fn select( + selector: Selector(payload), + for subject: Subject(payload), +) -> Selector(payload) { + select_map(selector, subject, fn(x) { x }) +} + +/// Add a new `Subject` to the `Selector` so that its messages can be selected +/// from the receiver process inbox. +/// +/// The `mapping` function provided with the `Subject` can be used to convert +/// the type of messages received using this `Subject`. This is useful for when +/// you wish to add multiple `Subject`s to a `Selector` when they have differing +/// message types. If you do not wish to transform the incoming messages in any +/// way then the `identity` function can be given. +/// +/// See `deselect` to remove a subject from a selector. +/// +pub fn select_map( + selector: Selector(payload), + for subject: Subject(message), + mapping transform: fn(message) -> payload, +) -> Selector(payload) { + let handler = fn(message: #(Reference, message)) { transform(message.1) } + case subject { + NamedSubject(name) -> insert_selector_handler(selector, #(name, 2), handler) + Subject(_, tag:) -> insert_selector_handler(selector, #(tag, 2), handler) + } +} + +/// Remove a new `Subject` from the `Selector` so that its messages will not be +/// selected from the receiver process inbox. +/// +pub fn deselect( + selector: Selector(payload), + for subject: Subject(message), +) -> Selector(payload) { + case subject { + NamedSubject(name) -> remove_selector_handler(selector, #(name, 2)) + Subject(_, tag:) -> remove_selector_handler(selector, #(tag, 2)) + } +} + +/// Add a handler to a selector for tuple messages with a given tag in the +/// first position followed by a given number of fields. +/// +/// Typically you want to use the `select` function with a `Subject` instead, +/// but this function may be useful if you need to receive messages sent from +/// other BEAM languages that do not use the `Subject` type. +/// +/// This will not select messages sent via a subject even if the message has +/// the same tag in the first position. This is because when a message is sent +/// via a subject a new tag is used that is unique and specific to that subject. +/// +pub fn select_record( + selector: Selector(payload), + tag tag: tag, + fields arity: Int, + mapping transform: fn(Dynamic) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, #(tag, arity + 1), transform) +} + +type AnythingSelectorTag { + Anything +} + +/// Add a catch-all handler to a selector that will be used when no other +/// handler in a selector is suitable for a given message. +/// +/// This may be useful for when you want to ensure that any message in the inbox +/// is handled, or when you need to handle messages from other BEAM languages +/// which do not use subjects or record format messages. +/// +pub fn select_other( + selector: Selector(payload), + mapping handler: fn(Dynamic) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, Anything, handler) +} + +@external(erlang, "gleam_erlang_ffi", "insert_selector_handler") +fn insert_selector_handler( + a: Selector(payload), + for for: tag, + mapping mapping: fn(message) -> payload, +) -> Selector(payload) + +@external(erlang, "gleam_erlang_ffi", "remove_selector_handler") +fn remove_selector_handler( + a: Selector(payload), + for for: tag, +) -> Selector(payload) + +/// Suspends the process calling this function for the specified number of +/// milliseconds. +/// +@external(erlang, "gleam_erlang_ffi", "sleep") +pub fn sleep(a: Int) -> Nil + +/// Suspends the process forever! This may be useful for suspending the main +/// process in a Gleam program when it has no more work to do but we want other +/// processes to continue to work. +/// +@external(erlang, "gleam_erlang_ffi", "sleep_forever") +pub fn sleep_forever() -> Nil + +/// Check to see whether the process for a given `Pid` is alive. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#is_process_alive-1 +/// +@external(erlang, "erlang", "is_process_alive") +pub fn is_alive(a: Pid) -> Bool + +type ProcessMonitorFlag { + Process +} + +@external(erlang, "erlang", "monitor") +fn erlang_monitor_process(a: ProcessMonitorFlag, b: Pid) -> Monitor + +pub type Monitor + +/// A message received when a monitored process exits. +/// +pub type Down { + ProcessDown(monitor: Monitor, pid: Pid, reason: ExitReason) + PortDown(monitor: Monitor, port: Port, reason: ExitReason) +} + +/// Start monitoring a process so that when the monitored process exits a +/// message is sent to the monitoring process. +/// +/// The message is only sent once, when the target process exits. If the +/// process was not alive when this function is called the message will never +/// be received. +/// +/// The down message can be received with a selector and the +/// `select_monitors` function. +/// +/// The process can be demonitored with the `demonitor_process` function. +/// +pub fn monitor(pid: Pid) -> Monitor { + erlang_monitor_process(Process, pid) +} + +/// Select for a message sent for a given monitor. +/// +/// Each monitor handler added to a selector has a select performance cost, +/// so prefer [`select_monitors`](#select_monitors) if you are select +/// for multiple monitors. +/// +/// The handler can be removed from the selector later using +/// [`deselect_specific_monitor`](#deselect_specific_monitor). +/// +pub fn select_specific_monitor( + selector: Selector(payload), + monitor: Monitor, + mapping: fn(Down) -> payload, +) { + insert_selector_handler(selector, monitor, mapping) +} + +/// Select for any messages sent for any monitors set up by the select process. +/// +/// If you want to select for a specific message then use +/// [`select_specific_monitor`](#select_specific_monitor), but this +/// function is preferred if you need to select for multiple monitors. +/// +pub fn select_monitors( + selector: Selector(payload), + mapping: fn(Down) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, #(atom.create("DOWN"), 5), fn(message) { + mapping(cast_down_message(message)) + }) +} + +@external(erlang, "gleam_erlang_ffi", "cast_down_message") +fn cast_down_message(message: Dynamic) -> Down + +@external(erlang, "gleam_erlang_ffi", "cast_exit_reason") +fn cast_exit_reason(message: Dynamic) -> ExitReason + +/// Remove the monitor for a process so that when the monitor process exits a +/// `Down` message is not sent to the monitoring process. +/// +/// If the message has already been sent it is removed from the monitoring +/// process' mailbox. +/// +pub fn demonitor_process(monitor monitor: Monitor) -> Nil { + erlang_demonitor_process(monitor) + Nil +} + +@external(erlang, "gleam_erlang_ffi", "demonitor") +fn erlang_demonitor_process(monitor: Monitor) -> DoNotLeak + +/// Remove a `Monitor` from a `Selector` prevoiusly added by +/// [`select_specific_monitor`](#select_specific_monitor). If +/// the `Monitor` is not in the `Selector` it will be returned +/// unchanged. +/// +pub fn deselect_specific_monitor( + selector: Selector(payload), + monitor: Monitor, +) -> Selector(payload) { + remove_selector_handler(selector, monitor) +} + +fn perform_call( + subject: Subject(message), + make_request: fn(Subject(reply)) -> message, + run_selector: fn(Selector(reply)) -> Result(reply, Nil), +) -> reply { + let reply_subject = new_subject() + let assert Ok(callee) = subject_owner(subject) + as "Callee subject had no owner" + + // Monitor the callee process so we can tell if it goes down (meaning we + // won't get a reply) + let monitor = monitor(callee) + + // Send the request to the process over the channel + send(subject, make_request(reply_subject)) + + // Await a reply or handle failure modes (timeout, process down, etc) + let reply = + new_selector() + |> select(reply_subject) + |> select_specific_monitor(monitor, fn(down) { + panic as { "callee exited: " <> string.inspect(down) } + }) + |> run_selector + + let assert Ok(reply) = reply as "callee did not send reply before timeout" + + // Demonitor the process and close the channels as we're done + demonitor_process(monitor) + + reply +} + +// This function is based off of Erlang's gen:do_call/4. +/// Send a message to a process and wait a given number of milliseconds for a +/// reply. +/// +/// ## Panics +/// +/// This function will panic under the following circumstances: +/// - The callee process exited prior to sending a reply. +/// - The callee process did not send a reply within the permitted amount of +/// time. +/// - The subject is a named subject but no process is registered with that +/// name. +/// +/// ## Examples +/// +/// ```gleam +/// pub type Message { +/// // This message variant is to be used with `call`. +/// // The `reply` field contains a subject that the reply message will be +/// // sent over. +/// SayHello(reply_to: Subject(String), name: String) +/// } +/// +/// // Typically we make public functions that hide the details of a process' +/// // message-based API. +/// pub fn say_hello(subject: Subject(Message), name: String) -> String { +/// // The `SayHello` message constructor is given _partially applied_ with +/// // all the arguments except the reply subject, which will be supplied by +/// // the `call` function itself before sending the message. +/// process.call(subject, 100, SayHello(_, name)) +/// } +/// +/// // This is the message handling logic used by the process that owns the +/// // subject, and so receives the messages. In a real project it would be +/// // within a process or some higher level abstraction like an actor, but for +/// // this demonstration that has been omitted. +/// pub fn handle_message(message: Message) -> Nil { +/// case message { +/// SayHello(reply:, name:) -> { +/// let data = "Hello, " <> name <> "!" +/// // The reply subject is used to send the response back. +/// // If the receiver process does not sent a reply in time then the +/// // caller will crash. +/// process.send(reply, data) +/// } +/// } +/// } +/// +/// // Here is what it looks like using the functional API to call the process. +/// pub fn run(subject: Subject(Message)) { +/// say_hello(subject, "Lucy") +/// // -> "Hello, Lucy!" +/// say_hello(subject, "Nubi") +/// // -> "Hello, Nubi!" +/// } +/// ``` +/// +pub fn call( + subject: Subject(message), + waiting timeout: Int, + sending make_request: fn(Subject(reply)) -> message, +) -> reply { + perform_call(subject, make_request, selector_receive(_, timeout)) +} + +/// Send a message to a process and wait for a reply. +/// +/// # Panics +/// +/// This function will panic under the following circumstances: +/// - The callee process exited prior to sending a reply. +/// - The subject is a named subject but no process is registered with that +/// name. +/// +pub fn call_forever( + subject: Subject(message), + make_request: fn(Subject(reply)) -> message, +) -> reply { + perform_call(subject, make_request, fn(s) { Ok(selector_receive_forever(s)) }) +} + +/// Creates a link between the calling process and another process. +/// +/// When a process crashes any linked processes will also crash. This is useful +/// to ensure that groups of processes that depend on each other all either +/// succeed or fail together. +/// +/// Returns `True` if the link was created successfully, returns `False` if the +/// process was not alive and as such could not be linked. +/// +@external(erlang, "gleam_erlang_ffi", "link") +pub fn link(pid pid: Pid) -> Bool + +@external(erlang, "erlang", "unlink") +fn erlang_unlink(pid pid: Pid) -> Bool + +/// Removes any existing link between the caller process and the target process. +/// +pub fn unlink(pid: Pid) -> Nil { + erlang_unlink(pid) + Nil +} + +pub type Timer + +@external(erlang, "erlang", "send_after") +fn pid_send_after(a: Int, b: Pid, c: #(Dynamic, msg)) -> Timer + +@external(erlang, "erlang", "send_after") +fn name_send_after(a: Int, b: Name(msg), c: #(Name(msg), msg)) -> Timer + +/// Send a message over a channel after a specified number of milliseconds. +/// +pub fn send_after(subject: Subject(msg), delay: Int, message: msg) -> Timer { + case subject { + NamedSubject(name) -> name_send_after(delay, name, #(name, message)) + Subject(owner, tag) -> pid_send_after(delay, owner, #(tag, message)) + } +} + +@external(erlang, "gleam_erlang_ffi", "identity") +fn reference_to_dynamic(reference: Reference) -> Dynamic + +@external(erlang, "erlang", "cancel_timer") +fn erlang_cancel_timer(a: Timer) -> Dynamic + +/// Values returned when a timer is cancelled. +/// +pub type Cancelled { + /// The timer could not be found. It likely has already triggered. + /// + TimerNotFound + + /// The timer was found and cancelled before it triggered. + /// + /// The amount of remaining time before the timer was due to be triggered is + /// returned in milliseconds. + /// + Cancelled(time_remaining: Int) +} + +/// Cancel a given timer, causing it not to trigger if it has not done already. +/// +pub fn cancel_timer(timer: Timer) -> Cancelled { + case decode.run(erlang_cancel_timer(timer), decode.int) { + Ok(i) -> Cancelled(i) + Error(_) -> TimerNotFound + } +} + +type KillFlag { + Kill +} + +@external(erlang, "erlang", "exit") +fn erlang_kill(to to: Pid, because because: KillFlag) -> Bool + +// Go, my pretties. Kill! Kill! +// - Bart Simpson +// +/// Send an untrappable `kill` exit signal to the target process. +/// +/// See the documentation for the Erlang [`erlang:exit`][1] function for more +/// information. +/// +/// [1]: https://erlang.org/doc/man/erlang.html#exit-1 +/// +pub fn kill(pid: Pid) -> Nil { + erlang_kill(pid, Kill) + Nil +} + +@external(erlang, "erlang", "exit") +fn erlang_send_exit(to to: Pid, because because: whatever) -> Bool + +// TODO: test +/// Sends an exit signal to a process, indicating that the process is to shut +/// down. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#exit-2 +/// +pub fn send_exit(to pid: Pid) -> Nil { + erlang_send_exit(pid, Normal) + Nil +} + +/// Sends an exit signal to a process, indicating that the process is to shut +/// down due to an abnormal reason such as a failure. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#exit-2 +/// +pub fn send_abnormal_exit(pid: Pid, reason: anything) -> Nil { + erlang_send_exit(pid, reason) + Nil +} + +/// Set whether the current process is to trap exit signals or not. +/// +/// When not trapping exits if a linked process crashes the exit signal +/// propagates to the process which will also crash. +/// This is the normal behaviour before this function is called. +/// +/// When trapping exits (after this function is called) if a linked process +/// crashes an exit message is sent to the process instead. These messages can +/// be handled with the `select_trapped_exits` function. +/// +@external(erlang, "gleam_erlang_ffi", "trap_exits") +pub fn trap_exits(a: Bool) -> Nil + +/// Register a process under a given name, allowing it to be looked up using +/// the `named` function. +/// +/// This function will return an error under the following conditions: +/// - The process for the pid no longer exists. +/// - The name has already been registered. +/// - The process already has a name. +/// +@external(erlang, "gleam_erlang_ffi", "register_process") +pub fn register(pid: Pid, name: Name(message)) -> Result(Nil, Nil) + +/// Un-register a process name, after which the process can no longer be looked +/// up by that name, and both the name and the process can be re-used in other +/// registrations. +/// +/// It is possible to un-register process that are not from your application, +/// including those from Erlang/OTP itself. This is not recommended and will +/// likely result in undesirable behaviour and crashes. +/// +@external(erlang, "gleam_erlang_ffi", "unregister_process") +pub fn unregister(name: Name(message)) -> Result(Nil, Nil) + +/// Look up a process by registered name, returning the pid if it exists. +/// +@external(erlang, "gleam_erlang_ffi", "process_named") +pub fn named(name: Name(a)) -> Result(Pid, Nil) diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam new file mode 100644 index 0000000..8a69d64 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam @@ -0,0 +1,15 @@ +/// A unique reference value. +/// +/// It holds no particular meaning or value, but unique values are often useful +/// in programs are used heavily within both Gleam and Erlang's OTP frameworks. +/// +/// More can be read about references in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/efficiency_guide/advanced.html#unique_references +/// +pub type Reference + +/// Create a new unique reference. +/// +@external(erlang, "erlang", "make_ref") +pub fn new() -> Reference diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@application.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@application.erl new file mode 100644 index 0000000..f571565 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@application.erl @@ -0,0 +1,43 @@ +-module(gleam@erlang@application). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/application.gleam"). +-export([priv_directory/1]). +-export_type([start_type/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " An Erlang application is a collection of code that can be loaded into the\n" + " Erlang virtual machine and even started and stopped if they define a\n" + " start module and supervision tree. Each Gleam package is an Erlang\n" + " application.\n" +). + +-type start_type() :: normal | + {takeover, gleam@erlang@node:node_()} | + {failover, gleam@erlang@node:node_()}. + +-file("src/gleam/erlang/application.gleam", 37). +?DOC( + " Returns the path of an application's `priv` directory, where extra non-Gleam\n" + " or Erlang files are typically kept. Each Gleam package is an Erlang\n" + " application.\n" + "\n" + " Returns an error if no application was found with the given name.\n" + "\n" + " # Example\n" + "\n" + " ```gleam\n" + " application.priv_directory(\"my_app\")\n" + " // -> Ok(\"/some/location/my_app/priv\")\n" + " ```\n" +). +-spec priv_directory(binary()) -> {ok, binary()} | {error, nil}. +priv_directory(Name) -> + gleam_erlang_ffi:priv_directory(Name). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@atom.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@atom.erl new file mode 100644 index 0000000..40c4fa2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@atom.erl @@ -0,0 +1,94 @@ +-module(gleam@erlang@atom). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/atom.gleam"). +-export([get/1, create/1, to_string/1, to_dynamic/1, cast_from_dynamic/1, decoder/0]). +-export_type([atom_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type atom_() :: any(). + +-file("src/gleam/erlang/atom.gleam", 27). +?DOC( + " Finds an existing atom for the given string.\n" + "\n" + " If no atom is found in the virtual machine's atom table for the string then\n" + " an error is returned.\n" +). +-spec get(binary()) -> {ok, atom_()} | {error, nil}. +get(A) -> + gleam_erlang_ffi:atom_from_string(A). + +-file("src/gleam/erlang/atom.gleam", 39). +?DOC( + " Creates an atom from a string, inserting a new value into the virtual\n" + " machine's atom table if an atom does not already exist for the given\n" + " string.\n" + "\n" + " We must be careful when using this function as there is a limit to the\n" + " number of atom that can fit in the virtual machine's atom table. Never\n" + " convert user input into atoms as filling the atom table will cause the\n" + " virtual machine to crash!\n" +). +-spec create(binary()) -> atom_(). +create(A) -> + erlang:binary_to_atom(A). + +-file("src/gleam/erlang/atom.gleam", 52). +?DOC( + " Returns a `String` corresponding to the text representation of the given\n" + " `Atom`.\n" + "\n" + " ## Examples\n" + " ```gleam\n" + " let ok_atom = create(\"ok\")\n" + " to_string(ok_atom)\n" + " // -> \"ok\"\n" + " ```\n" +). +-spec to_string(atom_()) -> binary(). +to_string(A) -> + erlang:atom_to_binary(A). + +-file("src/gleam/erlang/atom.gleam", 59). +?DOC( + " Convert an atom to a dynamic value, throwing away the type information. \n" + "\n" + " This may be useful for testing decoders.\n" +). +-spec to_dynamic(atom_()) -> gleam@dynamic:dynamic_(). +to_dynamic(A) -> + gleam_erlang_ffi:identity(A). + +-file("src/gleam/erlang/atom.gleam", 62). +-spec cast_from_dynamic(gleam@dynamic:dynamic_()) -> atom_(). +cast_from_dynamic(A) -> + gleam_erlang_ffi:identity(A). + +-file("src/gleam/erlang/atom.gleam", 71). +?DOC( + " A dynamic decoder for atoms.\n" + "\n" + " You almost certainly should not use this to work with externally defined\n" + " functions. They return known types, so you should define the external\n" + " functions with the correct types, defining wrapper functions in Erlang if\n" + " the external types cannot be mapped directly onto Gleam types.\n" +). +-spec decoder() -> gleam@dynamic@decode:decoder(atom_()). +decoder() -> + gleam@dynamic@decode:new_primitive_decoder( + <<"Atom"/utf8>>, + fun(Data) -> case erlang:is_atom(Data) of + true -> + {ok, gleam_erlang_ffi:identity(Data)}; + + false -> + {error, erlang:binary_to_atom(<<"nil"/utf8>>)} + end end + ). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl new file mode 100644 index 0000000..15b7bcf --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl @@ -0,0 +1,42 @@ +-module(gleam@erlang@charlist). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/charlist.gleam"). +-export([to_string/1, from_string/1]). +-export_type([charlist/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A charlist is a list of integers where all the integers are valid code\n" + " points.\n" + "\n" + " In practice, you will not come across them often, except perhaps when\n" + " interfacing with Erlang, in particular when using older libraries that do\n" + " not accept binaries as arguments.\n" +). + +-type charlist() :: any(). + +-file("src/gleam/erlang/charlist.gleam", 18). +?DOC( + " Convert a charlist to a string using Erlang's\n" + " `unicode:characters_to_binary`.\n" +). +-spec to_string(charlist()) -> binary(). +to_string(A) -> + unicode:characters_to_binary(A). + +-file("src/gleam/erlang/charlist.gleam", 24). +?DOC( + " Convert a string to a charlist using Erlang's\n" + " `unicode:characters_to_list`.\n" +). +-spec from_string(binary()) -> charlist(). +from_string(A) -> + unicode:characters_to_list(A). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@node.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@node.erl new file mode 100644 index 0000000..97e3ce5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@node.erl @@ -0,0 +1,80 @@ +-module(gleam@erlang@node). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/node.gleam"). +-export([self/0, visible/0, connect/1, name/1]). +-export_type([node_/0, connect_error/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Multiple Erlang VM instances can form a cluster to make a distributed\n" + " Erlang system, talking directly to each other using messages rather than\n" + " other communication protocols like HTTP. In a distributed Erlang system\n" + " each virtual machine is called a _node_. This module provides Node related\n" + " types and functions to be used as a foundation by other packages providing\n" + " more specialised functionality.\n" + "\n" + " For more information on distributed Erlang systems see the Erlang\n" + " documentation: .\n" +). + +-type node_() :: any(). + +-type connect_error() :: failed_to_connect | local_node_is_not_alive. + +-file("src/gleam/erlang/node.gleam", 18). +?DOC(" Return the current node.\n"). +-spec self() -> node_(). +self() -> + erlang:node(). + +-file("src/gleam/erlang/node.gleam", 31). +?DOC( + " Return a list of all visible nodes in the cluster, not including the current\n" + " node.\n" + "\n" + " The current node can be included by calling `self()` and prepending the\n" + " result.\n" + "\n" + " ```gleam\n" + " let all_nodes = [node.self(), ..node.visible()]\n" + " ```\n" +). +-spec visible() -> list(node_()). +visible() -> + erlang:nodes(). + +-file("src/gleam/erlang/node.gleam", 52). +?DOC( + " Establish a connection to a node, so the nodes can send messages to each\n" + " other and any other connected nodes.\n" + "\n" + " Returns `Error(FailedToConnect)` if the node is not reachable.\n" + "\n" + " Returns `Error(LocalNodeIsNotAlive)` if the local node is not alive, meaning\n" + " it is not running in distributed mode.\n" +). +-spec connect(gleam@erlang@atom:atom_()) -> {ok, node_()} | + {error, connect_error()}. +connect(Node) -> + gleam_erlang_ffi:connect_node(Node). + +-file("src/gleam/erlang/node.gleam", 63). +?DOC( + " Get the atom name of a node.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert name(my_node) == atom.create(\"app1@localhost\")\n" + " ```\n" +). +-spec name(node_()) -> gleam@erlang@atom:atom_(). +name(Node) -> + gleam_erlang_ffi:identity(Node). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@port.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@port.erl new file mode 100644 index 0000000..65b8ca2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@port.erl @@ -0,0 +1,8 @@ +-module(gleam@erlang@port). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/port.gleam"). +-export_type([port_/0]). + +-type port_() :: any(). + + diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@process.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@process.erl new file mode 100644 index 0000000..53b69bf --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@process.erl @@ -0,0 +1,868 @@ +-module(gleam@erlang@process). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/process.gleam"). +-export([self/0, spawn/1, spawn_unlinked/1, unsafely_create_subject/2, new_name/1, named_subject/1, subject_name/1, 'receive'/2, receive_forever/1, new_selector/0, selector_receive/2, selector_receive_forever/1, map_selector/2, merge_selector/2, flush_messages/0, select_map/3, select/2, select_record/4, select_other/2, deselect/2, sleep/1, sleep_forever/0, is_alive/1, monitor/1, select_specific_monitor/3, select_monitors/2, select_trapped_exits/2, demonitor_process/1, deselect_specific_monitor/2, link/1, unlink/1, send_after/3, new_subject/0, cancel_timer/1, kill/1, send_exit/1, send_abnormal_exit/2, trap_exits/1, register/2, unregister/1, named/1, subject_owner/1, send/2, call/3, call_forever/2]). +-export_type([pid_/0, subject/1, name/1, do_not_leak/0, selector/1, exit_message/0, exit_reason/0, anything_selector_tag/0, process_monitor_flag/0, monitor/0, down/0, timer/0, cancelled/0, kill_flag/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type pid_() :: any(). + +-opaque subject(DVB) :: {subject, pid_(), gleam@dynamic:dynamic_()} | + {named_subject, name(DVB)}. + +-type name(DVC) :: any() | {gleam_phantom, DVC}. + +-type do_not_leak() :: any(). + +-type selector(DVD) :: any() | {gleam_phantom, DVD}. + +-type exit_message() :: {exit_message, pid_(), exit_reason()}. + +-type exit_reason() :: normal | killed | {abnormal, gleam@dynamic:dynamic_()}. + +-type anything_selector_tag() :: anything. + +-type process_monitor_flag() :: process. + +-type monitor() :: any(). + +-type down() :: {process_down, monitor(), pid_(), exit_reason()} | + {port_down, monitor(), gleam@erlang@port:port_(), exit_reason()}. + +-type timer() :: any(). + +-type cancelled() :: timer_not_found | {cancelled, integer()}. + +-type kill_flag() :: kill. + +-file("src/gleam/erlang/process.gleam", 17). +?DOC(" Get the `Pid` for the current process.\n"). +-spec self() -> pid_(). +self() -> + erlang:self(). + +-file("src/gleam/erlang/process.gleam", 36). +?DOC( + " Create a new Erlang process that runs concurrently to the creator. In other\n" + " languages this might be called a fibre, a green thread, or a coroutine.\n" + "\n" + " The child process is linked to the creator process. When a process\n" + " terminates an exit signal is sent to all other processes that are linked to\n" + " it, causing the process to either terminate or have to handle the signal.\n" + " If you want an unlinked process use the `spawn_unlinked` function.\n" + "\n" + " More can be read about processes and links in the [Erlang documentation][1].\n" + "\n" + " [1]: https://www.erlang.org/doc/reference_manual/processes.html\n" + "\n" + " This function starts processes via the Erlang `proc_lib` module, and as\n" + " such they benefit from the functionality described in the\n" + " [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html).\n" +). +-spec spawn(fun(() -> any())) -> pid_(). +spawn(Running) -> + proc_lib:spawn_link(Running). + +-file("src/gleam/erlang/process.gleam", 53). +?DOC( + " Create a new Erlang process that runs concurrently to the creator. In other\n" + " languages this might be called a fibre, a green thread, or a coroutine.\n" + "\n" + " Typically you want to create a linked process using the `spawn` function,\n" + " but creating an unlinked process may be occasionally useful.\n" + "\n" + " More can be read about processes and links in the [Erlang documentation][1].\n" + "\n" + " [1]: https://www.erlang.org/doc/reference_manual/processes.html\n" + "\n" + " This function starts processes via the Erlang `proc_lib` module, and as\n" + " such they benefit from the functionality described in the\n" + " [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html).\n" +). +-spec spawn_unlinked(fun(() -> any())) -> pid_(). +spawn_unlinked(A) -> + proc_lib:spawn(A). + +-file("src/gleam/erlang/process.gleam", 90). +?DOC(false). +-spec unsafely_create_subject(pid_(), gleam@dynamic:dynamic_()) -> subject(any()). +unsafely_create_subject(Owner, Tag) -> + {subject, Owner, Tag}. + +-file("src/gleam/erlang/process.gleam", 136). +?DOC( + " Generate a new name that a process can register itself with using the\n" + " `register` function, and other processes can send messages to using\n" + " `named_subject`.\n" + "\n" + " The string argument is a prefix for the Erlang name. A unique suffix is\n" + " added to the prefix to make the name, removing the possibility of name\n" + " collisions.\n" + "\n" + " ## Safe use\n" + "\n" + " Use this function to create all the names your program needs when it\n" + " starts. **Never call this function dynamically** such as within a loop or\n" + " within a process within a supervision tree.\n" + "\n" + " Each time this function is called a new atom will be generated. Generating\n" + " too many atoms will result in the atom table getting filled and causing the\n" + " entire virtual machine to crash.\n" +). +-spec new_name(binary()) -> name(any()). +new_name(Prefix) -> + gleam_erlang_ffi:new_name(Prefix). + +-file("src/gleam/erlang/process.gleam", 143). +?DOC( + " Create a subject for a name, which can be used to send and receive messages.\n" + "\n" + " All subjects created for the same name behave identically and can be used\n" + " interchangably.\n" +). +-spec named_subject(name(DVK)) -> subject(DVK). +named_subject(Name) -> + {named_subject, Name}. + +-file("src/gleam/erlang/process.gleam", 149). +?DOC(" Get the name of a subject, returning an error if it doesn't have one.\n"). +-spec subject_name(subject(DVN)) -> {ok, name(DVN)} | {error, nil}. +subject_name(Subject) -> + case Subject of + {named_subject, Name} -> + {ok, Name}; + + {subject, _, _} -> + {error, nil} + end. + +-file("src/gleam/erlang/process.gleam", 247). +?DOC( + " Receive a message that has been sent to current process using the `Subject`.\n" + "\n" + " If there is not an existing message for the `Subject` in the process'\n" + " mailbox or one does not arrive `within` the permitted timeout then the\n" + " `Error(Nil)` is returned.\n" + "\n" + " Only the process that is owner of the `Subject` can receive a message using\n" + " it. If a process that does not own the `Subject` attempts to receive with it\n" + " then it will not receive a message.\n" + "\n" + " To wait for messages from multiple `Subject`s at the same time see the\n" + " `Selector` type.\n" + "\n" + " The `within` parameter specifies the timeout duration in milliseconds.\n" + "\n" + " ## Panics\n" + "\n" + " This function will panic if a process tries to receive with a non-named\n" + " subject that it does not own.\n" +). +-spec 'receive'(subject(DWB), integer()) -> {ok, DWB} | {error, nil}. +'receive'(Subject, Timeout) -> + case Subject of + {named_subject, _} -> + gleam_erlang_ffi:'receive'(Subject, Timeout); + + {subject, Owner, _} -> + case Owner =:= erlang:self() of + true -> + gleam_erlang_ffi:'receive'(Subject, Timeout); + + false -> + erlang:error(#{gleam_error => panic, + message => <<"Cannot receive with a subject owned by another process"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"receive"/utf8>>, + line => 257}) + end + end. + +-file("src/gleam/erlang/process.gleam", 272). +?DOC( + " Receive a message that has been sent to current process using the `Subject`.\n" + "\n" + " Same as `receive` but waits forever and returns the message as is.\n" +). +-spec receive_forever(subject(DWJ)) -> DWJ. +receive_forever(Subject) -> + gleam_erlang_ffi:'receive'(Subject). + +-file("src/gleam/erlang/process.gleam", 301). +?DOC( + " Create a new `Selector` which can be used to receive messages on multiple\n" + " `Subject`s at once.\n" +). +-spec new_selector() -> selector(any()). +new_selector() -> + gleam_erlang_ffi:new_selector(). + +-file("src/gleam/erlang/process.gleam", 321). +?DOC( + " Receive a message that has been sent to current process using any of the\n" + " `Subject`s that have been added to the `Selector` with the `select*`\n" + " functions.\n" + "\n" + " If there is not an existing message for the `Selector` in the process'\n" + " mailbox or one does not arrive `within` the permitted timeout then the\n" + " `Error(Nil)` is returned.\n" + "\n" + " Only the process that is owner of the `Subject`s can receive a message using\n" + " them. If a process that does not own the a `Subject` attempts to receive\n" + " with it then it will not receive a message.\n" + "\n" + " To wait forever for the next message rather than for a limited amount of\n" + " time see the `selector_receive_forever` function.\n" + "\n" + " The `within` parameter specifies the timeout duration in milliseconds.\n" +). +-spec selector_receive(selector(DWN), integer()) -> {ok, DWN} | {error, nil}. +selector_receive(From, Within) -> + gleam_erlang_ffi:select(From, Within). + +-file("src/gleam/erlang/process.gleam", 330). +?DOC( + " Similar to the `select` function but will wait forever for a message to\n" + " arrive rather than timing out after a specified amount of time.\n" +). +-spec selector_receive_forever(selector(DWR)) -> DWR. +selector_receive_forever(From) -> + gleam_erlang_ffi:select(From). + +-file("src/gleam/erlang/process.gleam", 339). +?DOC( + " Add a transformation function to a selector. When a message is received\n" + " using this selector the transformation function is applied to the message.\n" + "\n" + " This function can be used to change the type of messages received and may\n" + " be useful when combined with the `merge_selector` function.\n" +). +-spec map_selector(selector(DWT), fun((DWT) -> DWV)) -> selector(DWV). +map_selector(A, B) -> + gleam_erlang_ffi:map_selector(A, B). + +-file("src/gleam/erlang/process.gleam", 348). +?DOC( + " Merge one selector into another, producing a selector that contains the\n" + " message handlers of both.\n" + "\n" + " If a subject is handled by both selectors the handler function of the\n" + " second selector is used.\n" +). +-spec merge_selector(selector(DWX), selector(DWX)) -> selector(DWX). +merge_selector(A, B) -> + gleam_erlang_ffi:merge_selector(A, B). + +-file("src/gleam/erlang/process.gleam", 384). +?DOC( + " Discard all messages in the current process' mailbox.\n" + "\n" + " Warning: This function may cause other processes to crash if they sent a\n" + " message to the current process and are waiting for a response, so use with\n" + " caution.\n" + "\n" + " This function may be useful in tests.\n" +). +-spec flush_messages() -> nil. +flush_messages() -> + gleam_erlang_ffi:flush_messages(). + +-file("src/gleam/erlang/process.gleam", 411). +?DOC( + " Add a new `Subject` to the `Selector` so that its messages can be selected\n" + " from the receiver process inbox.\n" + "\n" + " The `mapping` function provided with the `Subject` can be used to convert\n" + " the type of messages received using this `Subject`. This is useful for when\n" + " you wish to add multiple `Subject`s to a `Selector` when they have differing\n" + " message types. If you do not wish to transform the incoming messages in any\n" + " way then the `identity` function can be given.\n" + "\n" + " See `deselect` to remove a subject from a selector.\n" +). +-spec select_map(selector(DXI), subject(DXK), fun((DXK) -> DXI)) -> selector(DXI). +select_map(Selector, Subject, Transform) -> + Handler = fun(Message) -> Transform(erlang:element(2, Message)) end, + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Name, 2}, + Handler + ); + + {subject, _, Tag} -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Tag, 2}, + Handler + ) + end. + +-file("src/gleam/erlang/process.gleam", 393). +?DOC( + " Add a new `Subject` to the `Selector` so that its messages can be selected\n" + " from the receiver process inbox.\n" + "\n" + " See `select_map` to add subjects of a different message type.\n" + "\n" + " See `deselect` to remove a subject from a selector.\n" +). +-spec select(selector(DXE), subject(DXE)) -> selector(DXE). +select(Selector, Subject) -> + select_map(Selector, Subject, fun(X) -> X end). + +-file("src/gleam/erlang/process.gleam", 447). +?DOC( + " Add a handler to a selector for tuple messages with a given tag in the\n" + " first position followed by a given number of fields.\n" + "\n" + " Typically you want to use the `select` function with a `Subject` instead,\n" + " but this function may be useful if you need to receive messages sent from\n" + " other BEAM languages that do not use the `Subject` type.\n" + "\n" + " This will not select messages sent via a subject even if the message has\n" + " the same tag in the first position. This is because when a message is sent\n" + " via a subject a new tag is used that is unique and specific to that subject.\n" +). +-spec select_record( + selector(DXS), + any(), + integer(), + fun((gleam@dynamic:dynamic_()) -> DXS) +) -> selector(DXS). +select_record(Selector, Tag, Arity, Transform) -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Tag, Arity + 1}, + Transform + ). + +-file("src/gleam/erlang/process.gleam", 467). +?DOC( + " Add a catch-all handler to a selector that will be used when no other\n" + " handler in a selector is suitable for a given message.\n" + "\n" + " This may be useful for when you want to ensure that any message in the inbox\n" + " is handled, or when you need to handle messages from other BEAM languages\n" + " which do not use subjects or record format messages.\n" +). +-spec select_other(selector(DXW), fun((gleam@dynamic:dynamic_()) -> DXW)) -> selector(DXW). +select_other(Selector, Handler) -> + gleam_erlang_ffi:insert_selector_handler(Selector, anything, Handler). + +-file("src/gleam/erlang/process.gleam", 426). +?DOC( + " Remove a new `Subject` from the `Selector` so that its messages will not be\n" + " selected from the receiver process inbox.\n" +). +-spec deselect(selector(DXN), subject(any())) -> selector(DXN). +deselect(Selector, Subject) -> + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:remove_selector_handler(Selector, {Name, 2}); + + {subject, _, Tag} -> + gleam_erlang_ffi:remove_selector_handler(Selector, {Tag, 2}) + end. + +-file("src/gleam/erlang/process.gleam", 491). +?DOC( + " Suspends the process calling this function for the specified number of\n" + " milliseconds.\n" +). +-spec sleep(integer()) -> nil. +sleep(A) -> + gleam_erlang_ffi:sleep(A). + +-file("src/gleam/erlang/process.gleam", 498). +?DOC( + " Suspends the process forever! This may be useful for suspending the main\n" + " process in a Gleam program when it has no more work to do but we want other\n" + " processes to continue to work.\n" +). +-spec sleep_forever() -> nil. +sleep_forever() -> + gleam_erlang_ffi:sleep_forever(). + +-file("src/gleam/erlang/process.gleam", 507). +?DOC( + " Check to see whether the process for a given `Pid` is alive.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#is_process_alive-1\n" +). +-spec is_alive(pid_()) -> boolean(). +is_alive(A) -> + erlang:is_process_alive(A). + +-file("src/gleam/erlang/process.gleam", 537). +?DOC( + " Start monitoring a process so that when the monitored process exits a\n" + " message is sent to the monitoring process.\n" + "\n" + " The message is only sent once, when the target process exits. If the\n" + " process was not alive when this function is called the message will never\n" + " be received.\n" + "\n" + " The down message can be received with a selector and the\n" + " `select_monitors` function.\n" + "\n" + " The process can be demonitored with the `demonitor_process` function.\n" +). +-spec monitor(pid_()) -> monitor(). +monitor(Pid) -> + erlang:monitor(process, Pid). + +-file("src/gleam/erlang/process.gleam", 550). +?DOC( + " Select for a message sent for a given monitor.\n" + "\n" + " Each monitor handler added to a selector has a select performance cost,\n" + " so prefer [`select_monitors`](#select_monitors) if you are select\n" + " for multiple monitors.\n" + "\n" + " The handler can be removed from the selector later using\n" + " [`deselect_specific_monitor`](#deselect_specific_monitor).\n" +). +-spec select_specific_monitor(selector(DYI), monitor(), fun((down()) -> DYI)) -> selector(DYI). +select_specific_monitor(Selector, Monitor, Mapping) -> + gleam_erlang_ffi:insert_selector_handler(Selector, Monitor, Mapping). + +-file("src/gleam/erlang/process.gleam", 564). +?DOC( + " Select for any messages sent for any monitors set up by the select process.\n" + "\n" + " If you want to select for a specific message then use \n" + " [`select_specific_monitor`](#select_specific_monitor), but this\n" + " function is preferred if you need to select for multiple monitors.\n" +). +-spec select_monitors(selector(DYL), fun((down()) -> DYL)) -> selector(DYL). +select_monitors(Selector, Mapping) -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {erlang:binary_to_atom(<<"DOWN"/utf8>>), 5}, + fun(Message) -> Mapping(gleam_erlang_ffi:cast_down_message(Message)) end + ). + +-file("src/gleam/erlang/process.gleam", 364). +?DOC( + " Add a handler for trapped exit messages. In order for these messages to be\n" + " sent to the process when a linked process exits the process must call the\n" + " `trap_exit` beforehand.\n" +). +-spec select_trapped_exits(selector(DXB), fun((exit_message()) -> DXB)) -> selector(DXB). +select_trapped_exits(Selector, Handler) -> + Tag = erlang:binary_to_atom(<<"EXIT"/utf8>>), + Handler@1 = fun(Message) -> + Handler( + {exit_message, + erlang:element(2, Message), + gleam_erlang_ffi:cast_exit_reason(erlang:element(3, Message))} + ) + end, + gleam_erlang_ffi:insert_selector_handler(Selector, {Tag, 3}, Handler@1). + +-file("src/gleam/erlang/process.gleam", 585). +?DOC( + " Remove the monitor for a process so that when the monitor process exits a\n" + " `Down` message is not sent to the monitoring process.\n" + "\n" + " If the message has already been sent it is removed from the monitoring\n" + " process' mailbox.\n" +). +-spec demonitor_process(monitor()) -> nil. +demonitor_process(Monitor) -> + gleam_erlang_ffi:demonitor(Monitor), + nil. + +-file("src/gleam/erlang/process.gleam", 598). +?DOC( + " Remove a `Monitor` from a `Selector` prevoiusly added by\n" + " [`select_specific_monitor`](#select_specific_monitor). If\n" + " the `Monitor` is not in the `Selector` it will be returned\n" + " unchanged.\n" +). +-spec deselect_specific_monitor(selector(DYO), monitor()) -> selector(DYO). +deselect_specific_monitor(Selector, Monitor) -> + gleam_erlang_ffi:remove_selector_handler(Selector, Monitor). + +-file("src/gleam/erlang/process.gleam", 729). +?DOC( + " Creates a link between the calling process and another process.\n" + "\n" + " When a process crashes any linked processes will also crash. This is useful\n" + " to ensure that groups of processes that depend on each other all either\n" + " succeed or fail together.\n" + "\n" + " Returns `True` if the link was created successfully, returns `False` if the\n" + " process was not alive and as such could not be linked.\n" +). +-spec link(pid_()) -> boolean(). +link(Pid) -> + gleam_erlang_ffi:link(Pid). + +-file("src/gleam/erlang/process.gleam", 736). +?DOC(" Removes any existing link between the caller process and the target process.\n"). +-spec unlink(pid_()) -> nil. +unlink(Pid) -> + erlang:unlink(Pid), + nil. + +-file("src/gleam/erlang/process.gleam", 751). +?DOC(" Send a message over a channel after a specified number of milliseconds.\n"). +-spec send_after(subject(DZK), integer(), DZK) -> timer(). +send_after(Subject, Delay, Message) -> + case Subject of + {named_subject, Name} -> + erlang:send_after(Delay, Name, {Name, Message}); + + {subject, Owner, Tag} -> + erlang:send_after(Delay, Owner, {Tag, Message}) + end. + +-file("src/gleam/erlang/process.gleam", 158). +?DOC(" Create a new `Subject` owned by the current process.\n"). +-spec new_subject() -> subject(any()). +new_subject() -> + {subject, erlang:self(), gleam_erlang_ffi:identity(erlang:make_ref())}. + +-file("src/gleam/erlang/process.gleam", 781). +?DOC(" Cancel a given timer, causing it not to trigger if it has not done already.\n"). +-spec cancel_timer(timer()) -> cancelled(). +cancel_timer(Timer) -> + case gleam@dynamic@decode:run( + erlang:cancel_timer(Timer), + {decoder, fun gleam@dynamic@decode:decode_int/1} + ) of + {ok, I} -> + {cancelled, I}; + + {error, _} -> + timer_not_found + end. + +-file("src/gleam/erlang/process.gleam", 805). +?DOC( + " Send an untrappable `kill` exit signal to the target process.\n" + "\n" + " See the documentation for the Erlang [`erlang:exit`][1] function for more\n" + " information.\n" + "\n" + " [1]: https://erlang.org/doc/man/erlang.html#exit-1\n" +). +-spec kill(pid_()) -> nil. +kill(Pid) -> + erlang:exit(Pid, kill), + nil. + +-file("src/gleam/erlang/process.gleam", 821). +?DOC( + " Sends an exit signal to a process, indicating that the process is to shut\n" + " down.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#exit-2\n" +). +-spec send_exit(pid_()) -> nil. +send_exit(Pid) -> + erlang:exit(Pid, normal), + nil. + +-file("src/gleam/erlang/process.gleam", 833). +?DOC( + " Sends an exit signal to a process, indicating that the process is to shut\n" + " down due to an abnormal reason such as a failure.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#exit-2\n" +). +-spec send_abnormal_exit(pid_(), any()) -> nil. +send_abnormal_exit(Pid, Reason) -> + erlang:exit(Pid, Reason), + nil. + +-file("src/gleam/erlang/process.gleam", 849). +?DOC( + " Set whether the current process is to trap exit signals or not.\n" + "\n" + " When not trapping exits if a linked process crashes the exit signal\n" + " propagates to the process which will also crash.\n" + " This is the normal behaviour before this function is called.\n" + "\n" + " When trapping exits (after this function is called) if a linked process\n" + " crashes an exit message is sent to the process instead. These messages can\n" + " be handled with the `select_trapped_exits` function.\n" +). +-spec trap_exits(boolean()) -> nil. +trap_exits(A) -> + gleam_erlang_ffi:trap_exits(A). + +-file("src/gleam/erlang/process.gleam", 860). +?DOC( + " Register a process under a given name, allowing it to be looked up using\n" + " the `named` function.\n" + "\n" + " This function will return an error under the following conditions:\n" + " - The process for the pid no longer exists.\n" + " - The name has already been registered.\n" + " - The process already has a name.\n" +). +-spec register(pid_(), name(any())) -> {ok, nil} | {error, nil}. +register(Pid, Name) -> + gleam_erlang_ffi:register_process(Pid, Name). + +-file("src/gleam/erlang/process.gleam", 871). +?DOC( + " Un-register a process name, after which the process can no longer be looked\n" + " up by that name, and both the name and the process can be re-used in other\n" + " registrations.\n" + "\n" + " It is possible to un-register process that are not from your application,\n" + " including those from Erlang/OTP itself. This is not recommended and will\n" + " likely result in undesirable behaviour and crashes.\n" +). +-spec unregister(name(any())) -> {ok, nil} | {error, nil}. +unregister(Name) -> + gleam_erlang_ffi:unregister_process(Name). + +-file("src/gleam/erlang/process.gleam", 876). +?DOC(" Look up a process by registered name, returning the pid if it exists.\n"). +-spec named(name(any())) -> {ok, pid_()} | {error, nil}. +named(Name) -> + gleam_erlang_ffi:process_named(Name). + +-file("src/gleam/erlang/process.gleam", 168). +?DOC( + " Get the owner process for a subject, which is the process that will\n" + " receive any messages sent using the subject.\n" + "\n" + " If the subject was created from a name and no process is currently\n" + " registered with that name then this function will return an error.\n" +). +-spec subject_owner(subject(any())) -> {ok, pid_()} | {error, nil}. +subject_owner(Subject) -> + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:process_named(Name); + + {subject, Pid, _} -> + {ok, Pid} + end. + +-file("src/gleam/erlang/process.gleam", 214). +?DOC( + " Send a message to a process using a `Subject`. The message must be of the\n" + " type that the `Subject` accepts.\n" + "\n" + " This function does not wait for the `Subject` owner process to call the\n" + " `receive` function, instead it returns once the message has been placed in\n" + " the process' mailbox.\n" + " \n" + " # Named Subjects\n" + " \n" + " If this function is called on a named subject for which a process has not been \n" + " registered, it will simply drop the message as there's no mailbox to send it to.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic when sending to a named subject if no process is\n" + " currently registed under that name.\n" + "\n" + " # Ordering\n" + "\n" + " If process P1 sends two messages to process P2 it is guaranteed that process\n" + " P1 will receive the messages in the order they were sent.\n" + "\n" + " If you wish to receive the messages in a different order you can send them\n" + " on two different subjects and the receiver function can call the `receive`\n" + " function for each subject in the desired order, or you can write some Erlang\n" + " code to perform a selective receive.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let subject = new_subject()\n" + " send(subject, \"Hello, Joe!\")\n" + " ```\n" +). +-spec send(subject(DVZ), DVZ) -> nil. +send(Subject, Message) -> + case Subject of + {subject, Pid, Tag} -> + erlang:send(Pid, {Tag, Message}); + + {named_subject, Name} -> + Pid@2 = case gleam_erlang_ffi:process_named(Name) of + {ok, Pid@1} -> Pid@1; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Sending to unregistered name"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"send"/utf8>>, + line => 220, + value => _assert_fail, + start => 8194, + 'end' => 8226, + pattern_start => 8205, + pattern_end => 8212}) + end, + erlang:send(Pid@2, {Name, Message}) + end, + nil. + +-file("src/gleam/erlang/process.gleam", 605). +-spec perform_call( + subject(DYR), + fun((subject(DYT)) -> DYR), + fun((selector(DYT)) -> {ok, DYT} | {error, nil}) +) -> DYT. +perform_call(Subject, Make_request, Run_selector) -> + Reply_subject = new_subject(), + Callee@1 = case subject_owner(Subject) of + {ok, Callee} -> Callee; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Callee subject had no owner"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 611, + value => _assert_fail, + start => 21173, + 'end' => 21219, + pattern_start => 21184, + pattern_end => 21194}) + end, + Monitor = monitor(Callee@1), + send(Subject, Make_request(Reply_subject)), + Reply = begin + _pipe = gleam_erlang_ffi:new_selector(), + _pipe@1 = select(_pipe, Reply_subject), + _pipe@2 = select_specific_monitor( + _pipe@1, + Monitor, + fun(Down) -> erlang:error(#{gleam_error => panic, + message => (<<"callee exited: "/utf8, + (gleam@string:inspect(Down))/binary>>), + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 626}) end + ), + Run_selector(_pipe@2) + end, + Reply@2 = case Reply of + {ok, Reply@1} -> Reply@1; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"callee did not send reply before timeout"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 630, + value => _assert_fail@1, + start => 21766, + 'end' => 21794, + pattern_start => 21777, + pattern_end => 21786}) + end, + demonitor_process(Monitor), + Reply@2. + +-file("src/gleam/erlang/process.gleam", 695). +?DOC( + " Send a message to a process and wait a given number of milliseconds for a\n" + " reply.\n" + "\n" + " ## Panics\n" + "\n" + " This function will panic under the following circumstances:\n" + " - The callee process exited prior to sending a reply.\n" + " - The callee process did not send a reply within the permitted amount of\n" + " time.\n" + " - The subject is a named subject but no process is registered with that\n" + " name.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " pub type Message {\n" + " // This message variant is to be used with `call`.\n" + " // The `reply` field contains a subject that the reply message will be\n" + " // sent over.\n" + " SayHello(reply_to: Subject(String), name: String)\n" + " }\n" + " \n" + " // Typically we make public functions that hide the details of a process'\n" + " // message-based API.\n" + " pub fn say_hello(subject: Subject(Message), name: String) -> String {\n" + " // The `SayHello` message constructor is given _partially applied_ with\n" + " // all the arguments except the reply subject, which will be supplied by\n" + " // the `call` function itself before sending the message.\n" + " process.call(subject, 100, SayHello(_, name))\n" + " }\n" + "\n" + " // This is the message handling logic used by the process that owns the\n" + " // subject, and so receives the messages. In a real project it would be\n" + " // within a process or some higher level abstraction like an actor, but for\n" + " // this demonstration that has been omitted.\n" + " pub fn handle_message(message: Message) -> Nil {\n" + " case message {\n" + " SayHello(reply:, name:) -> {\n" + " let data = \"Hello, \" <> name <> \"!\"\n" + " // The reply subject is used to send the response back.\n" + " // If the receiver process does not sent a reply in time then the\n" + " // caller will crash.\n" + " process.send(reply, data)\n" + " }\n" + " }\n" + " }\n" + "\n" + " // Here is what it looks like using the functional API to call the process.\n" + " pub fn run(subject: Subject(Message)) {\n" + " say_hello(subject, \"Lucy\")\n" + " // -> \"Hello, Lucy!\"\n" + " say_hello(subject, \"Nubi\")\n" + " // -> \"Hello, Nubi!\"\n" + " }\n" + " ```\n" +). +-spec call(subject(DYY), integer(), fun((subject(DZA)) -> DYY)) -> DZA. +call(Subject, Timeout, Make_request) -> + perform_call( + Subject, + Make_request, + fun(_capture) -> gleam_erlang_ffi:select(_capture, Timeout) end + ). + +-file("src/gleam/erlang/process.gleam", 712). +?DOC( + " Send a message to a process and wait for a reply.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic under the following circumstances:\n" + " - The callee process exited prior to sending a reply.\n" + " - The subject is a named subject but no process is registered with that\n" + " name.\n" +). +-spec call_forever(subject(DZC), fun((subject(DZE)) -> DZC)) -> DZE. +call_forever(Subject, Make_request) -> + perform_call( + Subject, + Make_request, + fun(S) -> {ok, gleam_erlang_ffi:select(S)} end + ). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@reference.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@reference.erl new file mode 100644 index 0000000..a393fa6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam@erlang@reference.erl @@ -0,0 +1,21 @@ +-module(gleam@erlang@reference). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/reference.gleam"). +-export([new/0]). +-export_type([reference_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type reference_() :: any(). + +-file("src/gleam/erlang/reference.gleam", 15). +?DOC(" Create a new unique reference.\n"). +-spec new() -> reference_(). +new() -> + erlang:make_ref(). diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang.app.src b/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang.app.src new file mode 100644 index 0000000..28f3f46 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang.app.src @@ -0,0 +1,16 @@ +{application, gleam_erlang, [ + {vsn, "1.3.0"}, + {applications, [gleam_stdlib]}, + {description, "Types and functions for programs running on Erlang!"}, + {modules, [gleam@erlang@application, + gleam@erlang@atom, + gleam@erlang@charlist, + gleam@erlang@node, + gleam@erlang@port, + gleam@erlang@process, + gleam@erlang@reference, + gleam_erlang@@main, + gleam_erlang_ffi, + gleam_erlang_test_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl b/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl new file mode 100644 index 0000000..b1ad710 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl @@ -0,0 +1,164 @@ +-module(gleam_erlang_ffi). +-export([ + atom_from_string/1, sleep/1, sleep_forever/0, demonitor/1, new_selector/0, + link/1, insert_selector_handler/3, remove_selector_handler/2, select/1, + select/2, trap_exits/1, map_selector/2, merge_selector/2, flush_messages/0, + priv_directory/1, connect_node/1, register_process/2, unregister_process/1, + process_named/1, identity/1, 'receive'/1, 'receive'/2, new_name/1, + cast_down_message/1, cast_exit_reason/1 +]). + +-spec atom_from_string(binary()) -> {ok, atom()} | {error, nil}. +atom_from_string(S) -> + try {ok, binary_to_existing_atom(S)} + catch error:badarg -> {error, nil} + end. + +new_name(Prefix) -> + Suffix = integer_to_binary(erlang:unique_integer([positive])), + Name = <>, + binary_to_atom(Name). + +sleep(Microseconds) -> + timer:sleep(Microseconds), + nil. + +sleep_forever() -> + timer:sleep(infinity), + nil. + +new_selector() -> + {selector, #{}}. + +map_selector({selector, Handlers}, Fn) -> + MappedHandlers = maps:map(fun(_Tag, Handler) -> + fun(Message) -> Fn(Handler(Message)) end + end, Handlers), + {selector, MappedHandlers}. + +merge_selector({selector, HandlersA}, {selector, HandlersB}) -> + {selector, maps:merge(HandlersA, HandlersB)}. + +insert_selector_handler({selector, Handlers}, Tag, Fn) -> + {selector, Handlers#{Tag => Fn}}. + +remove_selector_handler({selector, Handlers}, Tag) -> + {selector, maps:remove(Tag, Handlers)}. + +select(Selector) -> + {ok, Message} = select(Selector, infinity), + Message. + +select({selector, Handlers}, Timeout) -> + AnythingHandler = maps:get(anything, Handlers, undefined), + receive + % Monitored process down messages. + % This is special cased so we can selectively receive based on the + % reference as well as the record tag. + {'DOWN', Ref, _, _, _} = Message when is_map_key(Ref, Handlers) -> + Fn = maps:get(Ref, Handlers), + {ok, Fn(cast_down_message(Message))}; + + Msg when is_map_key({element(1, Msg), tuple_size(Msg)}, Handlers) -> + Fn = maps:get({element(1, Msg), tuple_size(Msg)}, Handlers), + {ok, Fn(Msg)}; + + Msg when AnythingHandler =/= undefined -> + {ok, AnythingHandler(Msg)} + after Timeout -> + {error, nil} + end. + +cast_exit_reason(normal) -> normal; +cast_exit_reason(killed) -> killed; +cast_exit_reason(Other) -> {abnormal, Other}. + +cast_down_message({'DOWN', Ref, process, Pid, Reason}) -> + {process_down, Ref, Pid, cast_exit_reason(Reason)}; +cast_down_message({'DOWN', Ref, port, Pid, Reason}) -> + {port_down, Ref, Pid, cast_exit_reason(Reason)}. + + +'receive'({subject, _Pid, Ref}) -> + receive + {Ref, Message} -> Message + end; +'receive'({named_subject, Name}) -> + receive + {Name, Message} -> Message + end. + +'receive'({subject, _Pid, Ref}, Timeout) -> + receive + {Ref, Message} -> {ok, Message} + after Timeout -> + {error, nil} + end; +'receive'({named_subject, Name}, Timeout) -> + receive + {Name, Message} -> {ok, Message} + after Timeout -> + {error, nil} + end. + +demonitor(Reference) -> + erlang:demonitor(Reference, [flush]). + +link(Pid) -> + try + erlang:link(Pid) + catch + error:_ -> false + end. + +trap_exits(ShouldTrap) -> + erlang:process_flag(trap_exit, ShouldTrap), + nil. + +flush_messages() -> + receive _Message -> flush_messages() + after 0 -> nil + end. + +priv_directory(Name) -> + try erlang:binary_to_existing_atom(Name) of + Atom -> + case code:priv_dir(Atom) of + {error, _} -> {error, nil}; + Path -> {ok, unicode:characters_to_binary(Path)} + end + catch + error:badarg -> {error, nil} + end. + +connect_node(Node) -> + case net_kernel:connect_node(Node) of + true -> {ok, Node}; + false -> {error, failed_to_connect}; + ignored -> {error, local_node_is_not_alive} + end. + +register_process(Pid, Name) -> + try + true = erlang:register(Name, Pid), + {ok, nil} + catch + error:badarg -> {error, nil} + end. + +unregister_process(Name) -> + try + true = erlang:unregister(Name), + {ok, nil} + catch + error:badarg -> {error, nil} + end. + +process_named(Name) -> + case erlang:whereis(Name) of + Pid when is_pid(Pid) -> {ok, Pid}; + _ -> {error, nil} + end. + +identity(X) -> + X. diff --git a/examples/http_interactions/build/packages/gleam_http/LICENSE b/examples/http_interactions/build/packages/gleam_http/LICENSE new file mode 100644 index 0000000..619ec77 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_http/README.md b/examples/http_interactions/build/packages/gleam_http/README.md new file mode 100644 index 0000000..8431da7 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/README.md @@ -0,0 +1,44 @@ +# Gleam HTTP + +Types and functions for HTTP clients and servers! + +## Server adapters + +In the example above the Elli Erlang web server is used to run the Gleam HTTP +service. Here's a full list of the server adapters available, sorted +alphabetically. + +| Adapter | About | +| --- | --- | +| [Mist][mist] | [Mist][mist] is a high performance pure Gleam HTTP 1.1 server | +| [cgi][cgi] | [cgi][cgi] is a adapter for the Common Gateway Interface. | +| [gleam_cowboy][cowboy-adapter] | [Cowboy][cowboy] is an Erlang HTTP2 & HTTP1.1 web server | +| [gleam_elli][elli-adapter] | [Elli][elli] is an Erlang HTTP1.1 web server | +| [gleam_plug][plug-adapter] | [Plug][plug] is an Elixir web application interface | + +[cgi]: https://github.com/lpil/cgi +[cowboy-adapter]: https://github.com/gleam-lang/cowboy +[cowboy]:https://github.com/ninenines/cowboy +[elli-adapter]: https://github.com/gleam-lang/elli +[elli]:https://github.com/elli-lib/elli +[mist]: https://github.com/rawhat/mist +[plug-adapter]: https://github.com/gleam-lang/plug +[plug]:https://github.com/elixir-plug/plug + +## Client adapters + +Client adapters are used to send HTTP requests to services over the network. +Here's a full list of the client adapters available, sorted alphabetically. + +| Adapter | About | +| --- | --- | +| [gleam_fetch][fetch-adapter] | [fetch][fetch] is a HTTP client included with JavaScript | +| [gleam_hackney][hackney-adapter] | [Hackney][hackney] is a simple HTTP client for Erlang | +| [gleam_httpc][httpc-adapter] | [httpc][httpc] is a HTTP client included with Erlang | + +[hackney]: https://github.com/benoitc/hackney +[hackney-adapter]: https://github.com/gleam-lang/hackney +[httpc]: https://erlang.org/doc/man/httpc.html +[httpc-adapter]: https://github.com/gleam-lang/httpc +[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API +[fetch-adapter]: https://github.com/gleam-lang/fetch diff --git a/examples/http_interactions/build/packages/gleam_http/gleam.toml b/examples/http_interactions/build/packages/gleam_http/gleam.toml new file mode 100644 index 0000000..59f7bce --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/gleam.toml @@ -0,0 +1,17 @@ +name = "gleam_http" +version = "4.3.0" +licences = ["Apache-2.0"] +description = "Types and functions for Gleam HTTP clients and servers" +gleam = ">= 1.11.0" + +repository = { type = "github", user = "gleam-lang", repo = "http" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.45.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http@cookie_Attributes.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@cookie_Attributes.hrl new file mode 100644 index 0000000..78a7d02 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@cookie_Attributes.hrl @@ -0,0 +1,8 @@ +-record(attributes, { + max_age :: gleam@option:option(integer()), + domain :: gleam@option:option(binary()), + path :: gleam@option:option(binary()), + secure :: boolean(), + http_only :: boolean(), + same_site :: gleam@option:option(gleam@http@cookie:same_site_policy()) +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http@request_Request.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@request_Request.hrl new file mode 100644 index 0000000..c8bbae6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@request_Request.hrl @@ -0,0 +1,10 @@ +-record(request, { + method :: gleam@http:method(), + headers :: list({binary(), binary()}), + body :: any(), + scheme :: gleam@http:scheme(), + host :: binary(), + port :: gleam@option:option(integer()), + path :: binary(), + 'query' :: gleam@option:option(binary()) +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http@response_Response.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@response_Response.hrl new file mode 100644 index 0000000..ba6f077 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http@response_Response.hrl @@ -0,0 +1,5 @@ +-record(response, { + status :: integer(), + headers :: list({binary(), binary()}), + body :: any() +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForBody.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForBody.hrl new file mode 100644 index 0000000..abd56dd --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForBody.hrl @@ -0,0 +1,5 @@ +-record(more_required_for_body, { + chunk :: bitstring(), + continuation :: fun((bitstring()) -> {ok, gleam@http:multipart_body()} | + {error, nil}) +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForHeaders.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForHeaders.hrl new file mode 100644 index 0000000..43729c1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MoreRequiredForHeaders.hrl @@ -0,0 +1,4 @@ +-record(more_required_for_headers, { + continuation :: fun((bitstring()) -> {ok, gleam@http:multipart_headers()} | + {error, nil}) +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartBody.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartBody.hrl new file mode 100644 index 0000000..4521591 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartBody.hrl @@ -0,0 +1,5 @@ +-record(multipart_body, { + chunk :: bitstring(), + done :: boolean(), + remaining :: bitstring() +}). diff --git a/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartHeaders.hrl b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartHeaders.hrl new file mode 100644 index 0000000..d9fca5c --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/include/gleam@http_MultipartHeaders.hrl @@ -0,0 +1,4 @@ +-record(multipart_headers, { + headers :: list({binary(), binary()}), + remaining :: bitstring() +}). diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam/http.gleam b/examples/http_interactions/build/packages/gleam_http/src/gleam/http.gleam new file mode 100644 index 0000000..40cca32 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam/http.gleam @@ -0,0 +1,656 @@ +//// Functions for working with HTTP data structures in Gleam. +//// +//// This module makes it easy to create and modify Requests and Responses, data types. +//// A general HTTP message type is defined that enables functions to work on both requests and responses. +//// +//// This module does not implement a HTTP client or HTTP server, but it can be used as a base for them. + +import gleam/bit_array +import gleam/bool +import gleam/list +import gleam/result +import gleam/string + +/// HTTP standard method as defined by [RFC 2616](https://tools.ietf.org/html/rfc2616), +/// and PATCH which is defined by [RFC 5789](https://tools.ietf.org/html/rfc5789). +pub type Method { + Get + Post + Head + Put + Delete + Trace + Connect + Options + Patch + + /// Non-standard but valid HTTP methods. + Other(String) +} + +pub fn parse_method(method: String) -> Result(Method, Nil) { + case method { + "CONNECT" -> Ok(Connect) + "DELETE" -> Ok(Delete) + "GET" -> Ok(Get) + "HEAD" -> Ok(Head) + "OPTIONS" -> Ok(Options) + "PATCH" -> Ok(Patch) + "POST" -> Ok(Post) + "PUT" -> Ok(Put) + "TRACE" -> Ok(Trace) + method -> + case is_valid_token(method) { + True -> Ok(Other(method)) + False -> Error(Nil) + } + } +} + +// A token is defined as: +// +// token = 1*tchar +// +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +// / DIGIT / ALPHA +// ; any VCHAR, except delimiters +// +// (From https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens) +// +// Where DIGIT = %x30-39 +// ALPHA = %x41-5A / %x61-7A +// (%xXX is a hexadecimal ASCII value) +// +// (From https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1) +// +fn is_valid_token(token: String) -> Bool { + case token { + // A token must have at least a single valid characther + "" -> False + _ -> is_valid_token_loop(token) + } +} + +fn is_valid_token_loop(token: String) -> Bool { + case token { + "" -> True + // SYMBOLS + "!" <> rest + | "#" <> rest + | "$" <> rest + | "%" <> rest + | "&" <> rest + | "'" <> rest + | "*" <> rest + | "+" <> rest + | "-" <> rest + | "." <> rest + | "^" <> rest + | "_" <> rest + | "`" <> rest + | "|" <> rest + | "~" <> rest + | // DIGITS + "0" <> rest + | "1" <> rest + | "2" <> rest + | "3" <> rest + | "4" <> rest + | "5" <> rest + | "6" <> rest + | "7" <> rest + | "8" <> rest + | "9" <> rest + | // ALPHA + "A" <> rest + | "B" <> rest + | "C" <> rest + | "D" <> rest + | "E" <> rest + | "F" <> rest + | "G" <> rest + | "H" <> rest + | "I" <> rest + | "J" <> rest + | "K" <> rest + | "L" <> rest + | "M" <> rest + | "N" <> rest + | "O" <> rest + | "P" <> rest + | "Q" <> rest + | "R" <> rest + | "S" <> rest + | "T" <> rest + | "U" <> rest + | "V" <> rest + | "W" <> rest + | "X" <> rest + | "Y" <> rest + | "Z" <> rest + | "a" <> rest + | "b" <> rest + | "c" <> rest + | "d" <> rest + | "e" <> rest + | "f" <> rest + | "g" <> rest + | "h" <> rest + | "i" <> rest + | "j" <> rest + | "k" <> rest + | "l" <> rest + | "m" <> rest + | "n" <> rest + | "o" <> rest + | "p" <> rest + | "q" <> rest + | "r" <> rest + | "s" <> rest + | "t" <> rest + | "u" <> rest + | "v" <> rest + | "w" <> rest + | "x" <> rest + | "y" <> rest + | "z" <> rest -> is_valid_token_loop(rest) + _ -> False + } +} + +pub fn method_to_string(method: Method) -> String { + case method { + Connect -> "CONNECT" + Delete -> "DELETE" + Get -> "GET" + Head -> "HEAD" + Options -> "OPTIONS" + Patch -> "PATCH" + Post -> "POST" + Put -> "PUT" + Trace -> "TRACE" + Other(method) -> method + } +} + +/// The two URI schemes for HTTP +/// +pub type Scheme { + Http + Https +} + +/// Convert a scheme into a string. +/// +/// # Examples +/// +/// ```gleam +/// assert "http" == scheme_to_string(Http) +/// assert "https" == scheme_to_string(Https) +/// ``` +/// +pub fn scheme_to_string(scheme: Scheme) -> String { + case scheme { + Http -> "http" + Https -> "https" + } +} + +/// Parse a HTTP scheme from a string +/// +/// # Examples +/// +/// ```gleam +/// assert Ok(Http) == scheme_from_string("http") +/// assert Error(Nil) == scheme_from_string("ftp") +/// ``` +/// +pub fn scheme_from_string(scheme: String) -> Result(Scheme, Nil) { + case string.lowercase(scheme) { + "http" -> Ok(Http) + "https" -> Ok(Https) + _ -> Error(Nil) + } +} + +pub type MultipartHeaders { + /// The headers for the part have been fully parsed. + /// Header keys are all lowercase. + MultipartHeaders( + headers: List(Header), + /// The remaining content that has not yet been parsed. This will contain + /// the body for this part, if any, and can be parsed with the + /// `parse_multipart_body` function. + remaining: BitArray, + ) + /// More input is required to parse the headers for this part. + MoreRequiredForHeaders( + /// Call this function to continue parsing the headers for this part. + continuation: fn(BitArray) -> Result(MultipartHeaders, Nil), + ) +} + +pub type MultipartBody { + /// The body for the part has been fully parsed. + MultipartBody( + // The rest of the body for this part. The full body of the part is this + // concatenated onto the end of each chunk returned by any previous + // `MoreRequiredForBody` returns. + chunk: BitArray, + /// This is `True` if this was the last part in the multipart message, + /// otherwise there are more parts to parse. + done: Bool, + /// The remaining content that has not yet been parsed. This will contain + /// the next part if `done` is `False`, otherwise it will contain the + /// epilogue, if any. + remaining: BitArray, + ) + MoreRequiredForBody( + // The body that has been parsed so far. The full body of the part is this + // concatenated with the chunk returned by each `MoreRequiredForBody` return + // value, and the final `MultipartBody` return value. + chunk: BitArray, + /// Call this function to continue parsing the body for this part. + continuation: fn(BitArray) -> Result(MultipartBody, Nil), + ) +} + +/// Parse the headers for part of a multipart message, as defined in RFC 2045. +/// +/// This function skips any preamble before the boundary. The preamble may be +/// retrieved using `parse_multipart_body`. +/// +/// This function will accept input of any size, it is up to the caller to limit +/// it if needed. +/// +/// To enable streaming parsing of multipart messages, this function will return +/// a continuation if there is not enough data to fully parse the headers. +/// Further information is available in the documentation for `MultipartBody`. +/// +pub fn parse_multipart_headers( + data: BitArray, + boundary: String, +) -> Result(MultipartHeaders, Nil) { + let boundary = bit_array.from_string(boundary) + let boundary_bytes = bit_array.byte_size(boundary) + do_parse_multipart_headers(data, boundary, boundary_bytes) +} + +fn do_parse_multipart_headers( + data: BitArray, + boundary: BitArray, + boundary_bytes: Int, +) -> Result(MultipartHeaders, Nil) { + case data { + // The headers start right away with a boundary. + <<"--", found:size(boundary_bytes)-bytes, rest:bits>> if found == boundary -> + case rest { + // Final boundary `--boundary--`. + <<"--", rest:bits>> -> Ok(MultipartHeaders([], remaining: rest)) + + // Regular boundary `--boundary`. + <<_, _, _:bits>> -> do_parse_headers(rest) + + // Not enough bytes to make a choice, we have to wait for more. + _ -> + more_please_headers(data, fn(data) { + do_parse_multipart_headers(data, boundary, boundary_bytes) + }) + } + + // The headers start with a preamble we need to skip. + _ -> skip_preamble(data, boundary, boundary_bytes) + } +} + +fn skip_preamble( + data: BitArray, + boundary: BitArray, + boundary_bytes: Int, +) -> Result(MultipartHeaders, Nil) { + case data { + // We might have found the preamble end! + <<"\r\n--", rest:bytes>> -> { + case rest { + // It is indeed the end, we go on parsing the headers that come after + // the boundary. + <> if found == boundary -> + do_parse_headers(rest) + + // This was not the end because `\r\n--` is not followed by the expected + // boundary, so we keep ignoring bytes. + <<_found:size(boundary_bytes)-bytes, _:bits>> -> + skip_preamble(rest, boundary, boundary_bytes) + + // There's not enough bytes to make sure the `\r\n--` is not followed + // by the closing boundary so we have to wait for more to come. + _ -> + more_please_headers(data, skip_preamble(_, boundary, boundary_bytes)) + } + } + + // We don't have enough bytes to be sure this is not the end of the preamble + // so we have to wait for more to come. + <<>> | <<"\r">> | <<"\r\n">> | <<"\r\n-">> -> + more_please_headers(data, skip_preamble(_, boundary, boundary_bytes)) + + // The byte is surely part of the preamble and can be skipped. + <<_, data:bytes>> -> skip_preamble(data, boundary, boundary_bytes) + + _ -> panic as "unreachable" + } +} + +fn do_parse_headers(data: BitArray) -> Result(MultipartHeaders, Nil) { + case data { + // We've reached the end, there are no headers. + <<"\r\n\r\n", data:bytes>> -> Ok(MultipartHeaders([], remaining: data)) + + // Skip the line break after the boundary. + <<"\r\n", data:bytes>> -> parse_header_name(data, []) + + <<"\r">> | <<>> -> more_please_headers(data, do_parse_headers) + + _ -> Error(Nil) + } +} + +fn parse_header_name( + data: BitArray, + headers: List(Header), +) -> Result(MultipartHeaders, Nil) { + case data { + // We first have to skip all whitespace preceding the name. + <<" ", rest:bits>> | <<"\t", rest:bits>> -> parse_header_name(rest, headers) + <<_, _:bits>> -> parse_header_name_loop(data, headers, <<>>) + + // We don't have enough bits to make a choice and will have to wait for more + // to come. + _ -> more_please_headers(data, parse_header_name(_, headers)) + } +} + +fn parse_header_name_loop(data: BitArray, headers: List(Header), name: BitArray) { + case data { + // We've found the end of the header, we can now start parsing its value. + <<":", data:bits>> -> + case bit_array.to_string(name) { + Ok(name) -> parse_header_value(data, headers, name) + Error(Nil) -> Error(Nil) + } + + // Otherwise the character belongs to the header. + <> -> + parse_header_name_loop(data, headers, <>) + + // We don't have enough bits to make a choice and have to wait for more to + // come. + _ -> more_please_headers(data, parse_header_name_loop(_, headers, name)) + } +} + +fn parse_header_value(data: BitArray, headers: List(Header), name: String) { + case data { + // We first have to skip all whitespace preceding the value. + <<" ", rest:bits>> | <<"\t", rest:bits>> -> + parse_header_value(rest, headers, name) + + <<_, _:bits>> -> parse_header_value_loop(data, headers, name, <<>>) + + // We don't have enough bits to make a choice and will have to wait for more + // to come. + _ -> more_please_headers(data, parse_header_value(_, headers, name)) + } +} + +fn parse_header_value_loop( + data: BitArray, + headers: List(Header), + name: String, + value: BitArray, +) -> Result(MultipartHeaders, Nil) { + case data { + // We need at least 4 bytes to check for the end of the headers. + <<>> | <<_>> | <<_, _>> | <<_, _, _>> -> + more_please_headers(data, fn(data) { + parse_header_value_loop(data, headers, name, value) + }) + + <<"\r\n\r\n", data:bytes>> -> { + use value <- result.map(bit_array.to_string(value)) + let headers = list.reverse([#(string.lowercase(name), value), ..headers]) + MultipartHeaders(headers:, remaining: data) + } + + <<"\r\n ", data:bytes>> | <<"\r\n\t", data:bytes>> -> + parse_header_value_loop(data, headers, name, value) + + <<"\r\n", data:bytes>> -> { + use value <- result.try(bit_array.to_string(value)) + let headers = [#(string.lowercase(name), value), ..headers] + parse_header_name(data, headers) + } + + <> -> + parse_header_value_loop(rest, headers, name, <>) + + _ -> Error(Nil) + } +} + +/// Parse the body for part of a multipart message, as defined in RFC 2045. The +/// body is everything until the next boundary. This function is generally to be +/// called after calling `parse_multipart_headers` for a given part. +/// +/// This function will accept input of any size, it is up to the caller to limit +/// it if needed. +/// +/// To enable streaming parsing of multipart messages, this function will return +/// a continuation if there is not enough data to fully parse the body, along +/// with the data that has been parsed so far. Further information is available +/// in the documentation for `MultipartBody`. +/// +pub fn parse_multipart_body( + data: BitArray, + boundary: String, +) -> Result(MultipartBody, Nil) { + let boundary = bit_array.from_string(boundary) + let boundary_bytes = bit_array.byte_size(boundary) + do_parse_multipart_body(data, boundary, boundary_bytes) +} + +fn do_parse_multipart_body( + data: BitArray, + boundary: BitArray, + boundary_bytes: Int, +) -> Result(MultipartBody, Nil) { + case data { + <<"--", found:size(boundary_bytes)-bytes, _:bytes>> if found == boundary -> + Ok(MultipartBody(<<>>, done: False, remaining: data)) + _ -> parse_body_loop(data, boundary, boundary_bytes, <<>>) + } +} + +fn parse_body_loop( + data: BitArray, + boundary: BitArray, + boundary_bytes: Int, + body: BitArray, +) -> Result(MultipartBody, Nil) { + case data { + <<>> | <<"\r">> -> + more_please_body(body, data, fn(data) { + parse_body_loop(data, boundary, boundary_bytes, <<>>) + }) + + <<"\r\n", rest:bits>> -> + case rest { + // This string match is written as `"\r", "\n"` (instead of just + // `"\r\n"`) to work around this compiler bug: + // https://github.com/gleam-lang/gleam/issues/4993 + // + // Even after that is fixed want to keep it like this to make sure the + // http library will work with older gleam versions on the js target. + // So please don't change it! + // + // vvvvvvvvvv This one right here! + <<"--", found:size(boundary_bytes)-bytes, "\r", "\n", _:bits>> + if found == boundary + -> Ok(MultipartBody(body, done: False, remaining: rest)) + + // Same goes for this match as well! + // vvvvvvvvvv This one right here! + <<"--", found:size(boundary_bytes)-bytes, "-", "-", rest:bits>> + if found == boundary + -> Ok(MultipartBody(body, done: True, remaining: rest)) + + <<_, _, _:size(boundary_bytes)-bytes, _, _, _:bits>> -> + parse_body_loop(rest, boundary, boundary_bytes, <>) + + _ -> + more_please_body(body, data, fn(data) { + parse_body_loop(data, boundary, boundary_bytes, <<>>) + }) + } + + <> -> + parse_body_loop(data, boundary, boundary_bytes, <>) + + _ -> panic as "unreachable" + } +} + +fn more_please_headers( + existing: BitArray, + continuation: fn(BitArray) -> Result(MultipartHeaders, Nil), +) -> Result(MultipartHeaders, Nil) { + Ok( + MoreRequiredForHeaders(fn(more) { + use <- bool.guard(more == <<>>, return: Error(Nil)) + continuation(<>) + }), + ) +} + +fn more_please_body( + chunk: BitArray, + existing: BitArray, + continuation: fn(BitArray) -> Result(MultipartBody, Nil), +) -> Result(MultipartBody, Nil) { + Ok( + MoreRequiredForBody(chunk, fn(more) { + use <- bool.guard(more == <<>>, return: Error(Nil)) + continuation(<>) + }), + ) +} + +pub type ContentDisposition { + ContentDisposition(String, parameters: List(#(String, String))) +} + +pub fn parse_content_disposition( + header: String, +) -> Result(ContentDisposition, Nil) { + parse_content_disposition_type(header, "") +} + +fn parse_content_disposition_type( + header: String, + name: String, +) -> Result(ContentDisposition, Nil) { + case string.pop_grapheme(header) { + Error(Nil) -> Ok(ContentDisposition(name, [])) + + Ok(#(" ", rest)) | Ok(#("\t", rest)) | Ok(#(";", rest)) -> { + let result = parse_rfc_2045_parameters(rest, []) + use parameters <- result.map(result) + ContentDisposition(name, parameters) + } + + Ok(#(grapheme, rest)) -> + parse_content_disposition_type(rest, name <> string.lowercase(grapheme)) + } +} + +fn parse_rfc_2045_parameters( + header: String, + parameters: List(#(String, String)), +) -> Result(List(#(String, String)), Nil) { + case string.pop_grapheme(header) { + Error(Nil) -> Ok(list.reverse(parameters)) + + Ok(#(";", rest)) | Ok(#(" ", rest)) | Ok(#("\t", rest)) -> + parse_rfc_2045_parameters(rest, parameters) + + Ok(#(grapheme, rest)) -> { + let acc = string.lowercase(grapheme) + use #(parameter, rest) <- result.try(parse_rfc_2045_parameter(rest, acc)) + parse_rfc_2045_parameters(rest, [parameter, ..parameters]) + } + } +} + +fn parse_rfc_2045_parameter( + header: String, + name: String, +) -> Result(#(#(String, String), String), Nil) { + use #(grapheme, rest) <- result.try(string.pop_grapheme(header)) + case grapheme { + "=" -> parse_rfc_2045_parameter_value(rest, name) + _ -> parse_rfc_2045_parameter(rest, name <> string.lowercase(grapheme)) + } +} + +fn parse_rfc_2045_parameter_value( + header: String, + name: String, +) -> Result(#(#(String, String), String), Nil) { + case string.pop_grapheme(header) { + Error(Nil) -> Error(Nil) + Ok(#("\"", rest)) -> parse_rfc_2045_parameter_quoted_value(rest, name, "") + Ok(#(grapheme, rest)) -> + Ok(parse_rfc_2045_parameter_unquoted_value(rest, name, grapheme)) + } +} + +fn parse_rfc_2045_parameter_quoted_value( + header: String, + name: String, + value: String, +) -> Result(#(#(String, String), String), Nil) { + case string.pop_grapheme(header) { + Error(Nil) -> Error(Nil) + Ok(#("\"", rest)) -> Ok(#(#(name, value), rest)) + Ok(#("\\", rest)) -> { + use #(grapheme, rest) <- result.try(string.pop_grapheme(rest)) + parse_rfc_2045_parameter_quoted_value(rest, name, value <> grapheme) + } + Ok(#(grapheme, rest)) -> + parse_rfc_2045_parameter_quoted_value(rest, name, value <> grapheme) + } +} + +fn parse_rfc_2045_parameter_unquoted_value( + header: String, + name: String, + value: String, +) -> #(#(String, String), String) { + case string.pop_grapheme(header) { + Error(Nil) -> #(#(name, value), header) + + Ok(#(";", rest)) | Ok(#(" ", rest)) | Ok(#("\t", rest)) -> #( + #(name, value), + rest, + ) + + Ok(#(grapheme, rest)) -> + parse_rfc_2045_parameter_unquoted_value(rest, name, value <> grapheme) + } +} + +/// A HTTP header is a key-value pair. Header keys must be all lowercase +/// characters. +pub type Header = + #(String, String) diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam/http/cookie.gleam b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/cookie.gleam new file mode 100644 index 0000000..1f692e5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/cookie.gleam @@ -0,0 +1,130 @@ +import gleam/http.{type Scheme} +import gleam/int +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string + +/// Policy options for the SameSite cookie attribute +/// +/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite +pub type SameSitePolicy { + Lax + Strict + None +} + +fn same_site_to_string(policy: SameSitePolicy) -> String { + case policy { + Lax -> "Lax" + Strict -> "Strict" + None -> "None" + } +} + +/// Attributes of a cookie when sent to a client in the `set-cookie` header. +pub type Attributes { + Attributes( + max_age: Option(Int), + domain: Option(String), + path: Option(String), + secure: Bool, + http_only: Bool, + same_site: Option(SameSitePolicy), + ) +} + +/// Helper to create sensible default attributes for a set cookie. +/// +/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Attributes +pub fn defaults(scheme: Scheme) -> Attributes { + Attributes( + max_age: option.None, + domain: option.None, + path: option.Some("/"), + secure: scheme == http.Https, + http_only: True, + same_site: option.Some(Lax), + ) +} + +const epoch = "Expires=Thu, 01 Jan 1970 00:00:00 GMT" + +fn cookie_attributes_to_list(attributes: Attributes) -> List(String) { + let Attributes(max_age:, domain:, path:, secure:, http_only:, same_site:) = + attributes + + [ + // Expires is a deprecated attribute for cookies, it has been replaced with MaxAge + // MaxAge is widely supported and so Expires values are not set. + // Only when deleting cookies is the exception made to use the old format, + // to ensure complete clearup of cookies if required by an application. + case max_age { + option.Some(0) -> option.Some(epoch) + _ -> option.None + }, + option.map(max_age, fn(max_age) { "Max-Age=" <> int.to_string(max_age) }), + option.map(domain, fn(domain) { "Domain=" <> domain }), + option.map(path, fn(path) { "Path=" <> path }), + case secure { + True -> option.Some("Secure") + False -> option.None + }, + case http_only { + True -> option.Some("HttpOnly") + False -> option.None + }, + option.map(same_site, fn(same_site) { + "SameSite=" <> same_site_to_string(same_site) + }), + ] + |> list.filter_map(option.to_result(_, Nil)) +} + +pub fn set_header(name: String, value: String, attributes: Attributes) -> String { + [name <> "=" <> value, ..cookie_attributes_to_list(attributes)] + |> string.join("; ") +} + +/// Parse a list of cookies from a header string. Any malformed cookies will be +/// discarded. +/// +/// ## Backwards compatibility +/// +/// RFC 6265 states that cookies in the cookie header should be separated by a +/// `;`, however this function will also accept a `,` separator to remain +/// compatible with the now-deprecated RFC 2965, and any older software +/// following that specification. +/// +pub fn parse(cookie_string: String) -> List(#(String, String)) { + cookie_string + |> string.split(";") + |> list.flat_map(string.split(_, ",")) + |> list.filter_map(fn(pair) { + case string.split_once(string.trim(pair), "=") { + Ok(#("", _)) -> Error(Nil) + Ok(#(key, value)) -> { + let key = string.trim(key) + use _ <- result.try(check_token(key)) + let value = string.trim(value) + use _ <- result.try(check_token(value)) + Ok(#(key, value)) + } + Error(Nil) -> Error(Nil) + } + }) +} + +fn check_token(token: String) -> Result(Nil, Nil) { + let contains_invalid_charachter = + string.contains(token, " ") + || string.contains(token, "\t") + || string.contains(token, "\r") + || string.contains(token, "\n") + || string.contains(token, "\f") + + case contains_invalid_charachter { + True -> Error(Nil) + False -> Ok(Nil) + } +} diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam/http/request.gleam b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/request.gleam new file mode 100644 index 0000000..1a66f2f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/request.gleam @@ -0,0 +1,287 @@ +import gleam/http.{type Header, type Method, type Scheme, Get} +import gleam/http/cookie +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string +import gleam/uri.{type Uri, Uri} + +/// A HTTP request. +/// +/// The body of the request is parameterised. The HTTP server or client you are +/// using will have a particular set of types it supports for the body. +/// +pub type Request(body) { + Request( + method: Method, + /// The request headers. The keys must always be lowercase. + headers: List(Header), + body: body, + scheme: Scheme, + host: String, + port: Option(Int), + path: String, + query: Option(String), + ) +} + +/// Return the uri that a request was sent to. +/// +pub fn to_uri(request: Request(body)) -> Uri { + Uri( + scheme: option.Some(http.scheme_to_string(request.scheme)), + userinfo: option.None, + host: option.Some(request.host), + port: request.port, + path: request.path, + query: request.query, + fragment: option.None, + ) +} + +/// Construct a request from a URI. +/// +pub fn from_uri(uri: Uri) -> Result(Request(String), Nil) { + use scheme <- result.try( + uri.scheme + |> option.unwrap("") + |> http.scheme_from_string, + ) + use host <- result.try( + uri.host + |> option.to_result(Nil), + ) + let req = + Request( + method: Get, + headers: [], + body: "", + scheme:, + host:, + port: uri.port, + path: uri.path, + query: uri.query, + ) + Ok(req) +} + +/// Get the value for a given header. +/// +/// If the request does not have that header then `Error(Nil)` is returned. +/// +/// Header keys are always lowercase in `gleam_http`. To use any uppercase +/// letter is invalid. +/// +pub fn get_header(request: Request(body), key: String) -> Result(String, Nil) { + list.key_find(request.headers, string.lowercase(key)) +} + +/// Set the header with the given value under the given header key. +/// +/// If already present, it is replaced. +/// +/// Header keys are always lowercase in `gleam_http`. To use any uppercase +/// letter is invalid. +/// +pub fn set_header( + request: Request(body), + key: String, + value: String, +) -> Request(body) { + let headers = list.key_set(request.headers, string.lowercase(key), value) + Request(..request, headers:) +} + +/// Prepend the header with the given value under the given header key. +/// +/// Similar to `set_header` except if the header already exists it prepends +/// another header with the same key. +/// +/// Header keys are always lowercase in `gleam_http`. To use any uppercase +/// letter is invalid. +/// +pub fn prepend_header( + request: Request(body), + key: String, + value: String, +) -> Request(body) { + let headers = [#(string.lowercase(key), value), ..request.headers] + Request(..request, headers:) +} + +/// Set the body of the request, overwriting any existing body. +/// +pub fn set_body(req: Request(old_body), body: new_body) -> Request(new_body) { + Request(..req, body:) +} + +/// Update the body of a request using a given function. +/// +pub fn map( + request: Request(old_body), + transform: fn(old_body) -> new_body, +) -> Request(new_body) { + request.body + |> transform + |> set_body(request, _) +} + +/// Return the non-empty segments of a request path. +/// +/// # Examples +/// +/// ```gleam +/// > new() +/// > |> set_path("/one/two/three") +/// > |> path_segments +/// ["one", "two", "three"] +/// ``` +/// +pub fn path_segments(request: Request(body)) -> List(String) { + request.path + |> uri.path_segments +} + +/// Decode the query of a request. +pub fn get_query(request: Request(body)) -> Result(List(#(String, String)), Nil) { + case request.query { + option.Some(query_string) -> uri.parse_query(query_string) + option.None -> Ok([]) + } +} + +/// Set the query of the request. +/// Query params will be percent encoded before being added to the Request. +/// +pub fn set_query( + req: Request(body), + query: List(#(String, String)), +) -> Request(body) { + let query = + list.map(query, fn(pair) { + let #(key, value) = pair + uri.percent_encode(key) <> "=" <> uri.percent_encode(value) + }) + |> string.join(with: "&") + |> option.Some + + Request(..req, query:) +} + +/// Set the method of the request. +/// +pub fn set_method(req: Request(body), method: Method) -> Request(body) { + Request(..req, method:) +} + +/// A request with commonly used default values. This request can be used as +/// an initial value and then update to create the desired request. +/// +pub fn new() -> Request(String) { + Request( + method: Get, + headers: [], + body: "", + scheme: http.Https, + host: "localhost", + port: option.None, + path: "", + query: option.None, + ) +} + +/// Construct a request from a URL string +/// +pub fn to(url: String) -> Result(Request(String), Nil) { + url + |> uri.parse + |> result.try(from_uri) +} + +/// Set the scheme (protocol) of the request. +/// +pub fn set_scheme(req: Request(body), scheme: Scheme) -> Request(body) { + Request(..req, scheme:) +} + +/// Set the host of the request. +/// +pub fn set_host(req: Request(body), host: String) -> Request(body) { + Request(..req, host:) +} + +/// Set the port of the request. +/// +pub fn set_port(req: Request(body), port: Int) -> Request(body) { + Request(..req, port: option.Some(port)) +} + +/// Set the path of the request. +/// +pub fn set_path(req: Request(body), path: String) -> Request(body) { + Request(..req, path:) +} + +/// Set a cookie on a request, replacing any previous cookie with that name. +/// +/// All cookies should be stored in a single header named `cookie`. +/// There should be at most one header with the name `cookie`, otherwise this +/// function cannot guarentee that previous cookies with the same name are +/// replaced. +/// +pub fn set_cookie( + req: Request(body), + name: String, + value: String, +) -> Request(body) { + // Get the cookies + let #(cookies, headers) = + list.key_pop(req.headers, "cookie") |> result.unwrap(#("", req.headers)) + + // Set the new cookie, replacing any previous one with the same name + let cookies = + cookie.parse(cookies) + |> list.key_set(name, value) + |> list.map(fn(pair) { pair.0 <> "=" <> pair.1 }) + |> string.join("; ") + + Request(..req, headers: [#("cookie", cookies), ..headers]) +} + +/// Fetch the cookies sent in a request. +/// +/// Note badly formed cookie pairs will be ignored. +/// RFC6265 specifies that invalid cookie names/attributes should be ignored. +pub fn get_cookies(req: Request(body)) -> List(#(String, String)) { + let Request(headers:, ..) = req + + list.flat_map(headers, fn(header) { + case header { + #("cookie", value) -> cookie.parse(value) + _ -> [] + } + }) +} + +/// Remove a cookie from a request +/// +/// Remove a cookie from the request. If no cookie is found return the request +/// unchanged. This will not remove the cookie from the client. +pub fn remove_cookie(req: Request(body), name: String) -> Request(body) { + case list.key_pop(req.headers, "cookie") { + Ok(#(cookies_string, headers)) -> { + let new_cookies_string = + cookie.parse(cookies_string) + |> list.filter_map(fn(cookie) { + case cookie { + #(cookie_name, _) if cookie_name == name -> Error(Nil) + #(name, value) -> Ok(name <> "=" <> value) + } + }) + |> string.join("; ") + + Request(..req, headers: [#("cookie", new_cookies_string), ..headers]) + } + Error(_) -> req + } +} diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam/http/response.gleam b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/response.gleam new file mode 100644 index 0000000..801943c --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/response.gleam @@ -0,0 +1,155 @@ +import gleam/http.{type Header} +import gleam/http/cookie +import gleam/list +import gleam/option +import gleam/result +import gleam/string + +/// A HTTP response. +/// +/// The body of the request is parameterised. The HTTP server or client you are +/// using will have a particular set of types it supports for the body. +/// +pub type Response(body) { + Response( + status: Int, + /// The request headers. The keys must always be lowercase. + headers: List(Header), + body: body, + ) +} + +/// Update the body of a response using a given result returning function. +/// +/// If the given function returns an `Ok` value the body is set, if it returns +/// an `Error` value then the error is returned. +/// +pub fn try_map( + response: Response(old_body), + transform: fn(old_body) -> Result(new_body, error), +) -> Result(Response(new_body), error) { + use body <- result.try(transform(response.body)) + Ok(set_body(response, body)) +} + +/// Construct an empty Response. +/// +/// The body type of the returned response is `String` and could be set with a +/// call to `set_body`. +/// +pub fn new(status: Int) -> Response(String) { + Response(status:, headers: [], body: "") +} + +/// Get the value for a given header. +/// +/// If the response does not have that header then `Error(Nil)` is returned. +/// +pub fn get_header(response: Response(body), key: String) -> Result(String, Nil) { + list.key_find(response.headers, string.lowercase(key)) +} + +/// Set the header with the given value under the given header key. +/// +/// If the response already has that key, it is replaced. +/// +/// Header keys are always lowercase in `gleam_http`. To use any uppercase +/// letter is invalid. +/// +pub fn set_header( + response: Response(body), + key: String, + value: String, +) -> Response(body) { + let headers = list.key_set(response.headers, string.lowercase(key), value) + Response(..response, headers:) +} + +/// Prepend the header with the given value under the given header key. +/// +/// Similar to `set_header` except if the header already exists it prepends +/// another header with the same key. +/// +/// Header keys are always lowercase in `gleam_http`. To use any uppercase +/// letter is invalid. +/// +pub fn prepend_header( + response: Response(body), + key: String, + value: String, +) -> Response(body) { + let headers = [#(string.lowercase(key), value), ..response.headers] + Response(..response, headers:) +} + +/// Set the body of the response, overwriting any existing body. +/// +pub fn set_body( + response: Response(old_body), + body: new_body, +) -> Response(new_body) { + Response(..response, body:) +} + +/// Update the body of a response using a given function. +/// +pub fn map( + response: Response(old_body), + transform: fn(old_body) -> new_body, +) -> Response(new_body) { + response.body + |> transform + |> set_body(response, _) +} + +/// Create a response that redirects to the given uri. +/// +pub fn redirect(uri: String) -> Response(String) { + Response( + status: 303, + headers: [#("location", uri)], + body: string.append("You are being redirected to ", uri), + ) +} + +/// Fetch the cookies sent in a response. +/// +/// Badly formed cookies will be discarded. +/// +pub fn get_cookies(resp: Response(body)) -> List(#(String, String)) { + let Response(headers:, ..) = resp + + list.flat_map(headers, fn(header) { + case header { + #("set-cookie", value) -> cookie.parse(value) + _ -> [] + } + }) +} + +/// Set a cookie value for a client +/// +pub fn set_cookie( + response: Response(body), + name: String, + value: String, + attributes: cookie.Attributes, +) -> Response(body) { + prepend_header( + response, + "set-cookie", + cookie.set_header(name, value, attributes), + ) +} + +/// Expire a cookie value for a client +/// +/// Note: The attributes value should be the same as when the response cookie was set. +pub fn expire_cookie( + response: Response(body), + name: String, + attributes: cookie.Attributes, +) -> Response(body) { + let attrs = cookie.Attributes(..attributes, max_age: option.Some(0)) + set_cookie(response, name, "", attrs) +} diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam/http/service.gleam b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/service.gleam new file mode 100644 index 0000000..52898c1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam/http/service.gleam @@ -0,0 +1,68 @@ +import gleam/http.{Delete, Patch, Post, Put} +import gleam/http/request.{type Request} +import gleam/http/response.{type Response} +import gleam/list +import gleam/result + +@deprecated("Use middleware packages such as Wisp or Glen instead") +pub type Service(in, out) = + fn(Request(in)) -> Response(out) + +@deprecated("Use middleware packages such as Wisp or Glen instead") +pub type Middleware(before_req, before_resp, after_req, after_resp) = + fn(fn(Request(before_req)) -> Response(before_resp)) -> + fn(Request(after_req)) -> Response(after_resp) + +@deprecated("Use middleware packages such as Wisp or Glen instead") +pub fn map_response_body( + service: fn(c) -> Response(a), + with mapper: fn(a) -> b, +) -> fn(c) -> Response(b) { + fn(req) { + req + |> service + |> response.map(mapper) + } +} + +@deprecated("Use middleware packages such as Wisp or Glen instead") +pub fn prepend_response_header( + service: fn(a) -> Response(b), + key: String, + value: String, +) -> fn(a) -> Response(b) { + fn(req) { + req + |> service + |> response.prepend_header(key, value) + } +} + +fn ensure_post(req: Request(a)) -> Result(Request(a), Nil) { + case req.method { + Post -> Ok(req) + _ -> Error(Nil) + } +} + +fn get_override_method(request: Request(t)) -> Result(http.Method, Nil) { + use query_params <- result.try(request.get_query(request)) + use method <- result.try(list.key_find(query_params, "_method")) + use method <- result.try(http.parse_method(method)) + case method { + Put | Patch | Delete -> Ok(method) + _ -> Error(Nil) + } +} + +@deprecated("Use middleware packages such as Wisp or Glen instead") +pub fn method_override(service: fn(Request(a)) -> b) -> fn(Request(a)) -> b { + fn(request) { + request + |> ensure_post + |> result.try(get_override_method) + |> result.map(request.set_method(request, _)) + |> result.unwrap(request) + |> service + } +} diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam@http.erl b/examples/http_interactions/build/packages/gleam_http/src/gleam@http.erl new file mode 100644 index 0000000..013ca2d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam@http.erl @@ -0,0 +1,1038 @@ +-module(gleam@http). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/http.gleam"). +-export([parse_method/1, method_to_string/1, scheme_to_string/1, scheme_from_string/1, parse_multipart_body/2, parse_content_disposition/1, parse_multipart_headers/2]). +-export_type([method/0, scheme/0, multipart_headers/0, multipart_body/0, content_disposition/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Functions for working with HTTP data structures in Gleam.\n" + "\n" + " This module makes it easy to create and modify Requests and Responses, data types.\n" + " A general HTTP message type is defined that enables functions to work on both requests and responses.\n" + "\n" + " This module does not implement a HTTP client or HTTP server, but it can be used as a base for them.\n" +). + +-type method() :: get | + post | + head | + put | + delete | + trace | + connect | + options | + patch | + {other, binary()}. + +-type scheme() :: http | https. + +-type multipart_headers() :: {multipart_headers, + list({binary(), binary()}), + bitstring()} | + {more_required_for_headers, + fun((bitstring()) -> {ok, multipart_headers()} | {error, nil})}. + +-type multipart_body() :: {multipart_body, bitstring(), boolean(), bitstring()} | + {more_required_for_body, + bitstring(), + fun((bitstring()) -> {ok, multipart_body()} | {error, nil})}. + +-type content_disposition() :: {content_disposition, + binary(), + list({binary(), binary()})}. + +-file("src/gleam/http.gleam", 75). +-spec is_valid_token_loop(binary()) -> boolean(). +is_valid_token_loop(Token) -> + case Token of + <<""/utf8>> -> + true; + + <<"!"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"#"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"$"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"%"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"&"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"'"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"*"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"+"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"-"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"."/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"^"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"_"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"`"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"|"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"~"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"0"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"1"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"2"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"3"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"4"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"5"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"6"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"7"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"8"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"9"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"A"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"B"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"C"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"D"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"E"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"F"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"G"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"H"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"I"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"J"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"K"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"L"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"M"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"N"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"O"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"P"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"Q"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"R"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"S"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"T"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"U"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"V"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"W"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"X"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"Y"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"Z"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"a"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"b"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"c"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"d"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"e"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"f"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"g"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"h"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"i"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"j"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"k"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"l"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"m"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"n"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"o"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"p"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"q"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"r"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"s"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"t"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"u"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"v"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"w"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"x"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"y"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + <<"z"/utf8, Rest/binary>> -> + is_valid_token_loop(Rest); + + _ -> + false + end. + +-file("src/gleam/http.gleam", 67). +-spec is_valid_token(binary()) -> boolean(). +is_valid_token(Token) -> + case Token of + <<""/utf8>> -> + false; + + _ -> + is_valid_token_loop(Token) + end. + +-file("src/gleam/http.gleam", 31). +-spec parse_method(binary()) -> {ok, method()} | {error, nil}. +parse_method(Method) -> + case Method of + <<"CONNECT"/utf8>> -> + {ok, connect}; + + <<"DELETE"/utf8>> -> + {ok, delete}; + + <<"GET"/utf8>> -> + {ok, get}; + + <<"HEAD"/utf8>> -> + {ok, head}; + + <<"OPTIONS"/utf8>> -> + {ok, options}; + + <<"PATCH"/utf8>> -> + {ok, patch}; + + <<"POST"/utf8>> -> + {ok, post}; + + <<"PUT"/utf8>> -> + {ok, put}; + + <<"TRACE"/utf8>> -> + {ok, trace}; + + Method@1 -> + case is_valid_token(Method@1) of + true -> + {ok, {other, Method@1}}; + + false -> + {error, nil} + end + end. + +-file("src/gleam/http.gleam", 162). +-spec method_to_string(method()) -> binary(). +method_to_string(Method) -> + case Method of + connect -> + <<"CONNECT"/utf8>>; + + delete -> + <<"DELETE"/utf8>>; + + get -> + <<"GET"/utf8>>; + + head -> + <<"HEAD"/utf8>>; + + options -> + <<"OPTIONS"/utf8>>; + + patch -> + <<"PATCH"/utf8>>; + + post -> + <<"POST"/utf8>>; + + put -> + <<"PUT"/utf8>>; + + trace -> + <<"TRACE"/utf8>>; + + {other, Method@1} -> + Method@1 + end. + +-file("src/gleam/http.gleam", 193). +?DOC( + " Convert a scheme into a string.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " assert \"http\" == scheme_to_string(Http)\n" + " assert \"https\" == scheme_to_string(Https)\n" + " ```\n" +). +-spec scheme_to_string(scheme()) -> binary(). +scheme_to_string(Scheme) -> + case Scheme of + http -> + <<"http"/utf8>>; + + https -> + <<"https"/utf8>> + end. + +-file("src/gleam/http.gleam", 209). +?DOC( + " Parse a HTTP scheme from a string\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " assert Ok(Http) == scheme_from_string(\"http\")\n" + " assert Error(Nil) == scheme_from_string(\"ftp\")\n" + " ```\n" +). +-spec scheme_from_string(binary()) -> {ok, scheme()} | {error, nil}. +scheme_from_string(Scheme) -> + case string:lowercase(Scheme) of + <<"http"/utf8>> -> + {ok, http}; + + <<"https"/utf8>> -> + {ok, https}; + + _ -> + {error, nil} + end. + +-file("src/gleam/http.gleam", 524). +-spec more_please_headers( + bitstring(), + fun((bitstring()) -> {ok, multipart_headers()} | {error, nil}) +) -> {ok, multipart_headers()} | {error, nil}. +more_please_headers(Existing, Continuation) -> + {ok, + {more_required_for_headers, + fun(More) -> + gleam@bool:guard( + More =:= <<>>, + {error, nil}, + fun() -> + Continuation(<>) + end + ) + end}}. + +-file("src/gleam/http.gleam", 536). +-spec more_please_body( + bitstring(), + bitstring(), + fun((bitstring()) -> {ok, multipart_body()} | {error, nil}) +) -> {ok, multipart_body()} | {error, nil}. +more_please_body(Chunk, Existing, Continuation) -> + {ok, + {more_required_for_body, + Chunk, + fun(More) -> + gleam@bool:guard( + More =:= <<>>, + {error, nil}, + fun() -> + Continuation(<>) + end + ) + end}}. + +-file("src/gleam/http.gleam", 475). +-spec parse_body_loop(bitstring(), bitstring(), integer(), bitstring()) -> {ok, + multipart_body()} | + {error, nil}. +parse_body_loop(Data, Boundary, Boundary_bytes, Body) -> + case Data of + <<>> -> + more_please_body( + Body, + Data, + fun(Data@1) -> + parse_body_loop(Data@1, Boundary, Boundary_bytes, <<>>) + end + ); + + <<"\r"/utf8>> -> + more_please_body( + Body, + Data, + fun(Data@1) -> + parse_body_loop(Data@1, Boundary, Boundary_bytes, <<>>) + end + ); + + <<"\r\n"/utf8, Rest/bitstring>> -> + case Rest of + <<"--"/utf8, + Found:Boundary_bytes/binary, + "\r"/utf8, + "\n"/utf8, + _/bitstring>> when Found =:= Boundary -> + {ok, {multipart_body, Body, false, Rest}}; + + <<"--"/utf8, + Found@1:Boundary_bytes/binary, + "-"/utf8, + "-"/utf8, + Rest@1/bitstring>> when Found@1 =:= Boundary -> + {ok, {multipart_body, Body, true, Rest@1}}; + + <<_, _, _:Boundary_bytes/binary, _, _, _/bitstring>> -> + parse_body_loop( + Rest, + Boundary, + Boundary_bytes, + <> + ); + + _ -> + more_please_body( + Body, + Data, + fun(Data@2) -> + parse_body_loop( + Data@2, + Boundary, + Boundary_bytes, + <<>> + ) + end + ) + end; + + <> -> + parse_body_loop( + Data@3, + Boundary, + Boundary_bytes, + <> + ); + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"unreachable"/utf8>>, + file => <>, + module => <<"gleam/http"/utf8>>, + function => <<"parse_body_loop"/utf8>>, + line => 520}) + end. + +-file("src/gleam/http.gleam", 463). +-spec do_parse_multipart_body(bitstring(), bitstring(), integer()) -> {ok, + multipart_body()} | + {error, nil}. +do_parse_multipart_body(Data, Boundary, Boundary_bytes) -> + case Data of + <<"--"/utf8, Found:Boundary_bytes/binary, _/binary>> when Found =:= Boundary -> + {ok, {multipart_body, <<>>, false, Data}}; + + _ -> + parse_body_loop(Data, Boundary, Boundary_bytes, <<>>) + end. + +-file("src/gleam/http.gleam", 454). +?DOC( + " Parse the body for part of a multipart message, as defined in RFC 2045. The\n" + " body is everything until the next boundary. This function is generally to be\n" + " called after calling `parse_multipart_headers` for a given part.\n" + "\n" + " This function will accept input of any size, it is up to the caller to limit\n" + " it if needed.\n" + "\n" + " To enable streaming parsing of multipart messages, this function will return\n" + " a continuation if there is not enough data to fully parse the body, along\n" + " with the data that has been parsed so far. Further information is available\n" + " in the documentation for `MultipartBody`.\n" +). +-spec parse_multipart_body(bitstring(), binary()) -> {ok, multipart_body()} | + {error, nil}. +parse_multipart_body(Data, Boundary) -> + Boundary@1 = gleam_stdlib:identity(Boundary), + Boundary_bytes = erlang:byte_size(Boundary@1), + do_parse_multipart_body(Data, Boundary@1, Boundary_bytes). + +-file("src/gleam/http.gleam", 618). +-spec parse_rfc_2045_parameter_quoted_value(binary(), binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter_quoted_value(Header, Name, Value) -> + case gleam_stdlib:string_pop_grapheme(Header) of + {error, nil} -> + {error, nil}; + + {ok, {<<"\""/utf8>>, Rest}} -> + {ok, {{Name, Value}, Rest}}; + + {ok, {<<"\\"/utf8>>, Rest@1}} -> + gleam@result:'try'( + gleam_stdlib:string_pop_grapheme(Rest@1), + fun(_use0) -> + {Grapheme, Rest@2} = _use0, + parse_rfc_2045_parameter_quoted_value( + Rest@2, + Name, + <> + ) + end + ); + + {ok, {Grapheme@1, Rest@3}} -> + parse_rfc_2045_parameter_quoted_value( + Rest@3, + Name, + <> + ) + end. + +-file("src/gleam/http.gleam", 635). +-spec parse_rfc_2045_parameter_unquoted_value(binary(), binary(), binary()) -> {{binary(), + binary()}, + binary()}. +parse_rfc_2045_parameter_unquoted_value(Header, Name, Value) -> + case gleam_stdlib:string_pop_grapheme(Header) of + {error, nil} -> + {{Name, Value}, Header}; + + {ok, {<<";"/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {<<" "/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {<<"\t"/utf8>>, Rest}} -> + {{Name, Value}, Rest}; + + {ok, {Grapheme, Rest@1}} -> + parse_rfc_2045_parameter_unquoted_value( + Rest@1, + Name, + <> + ) + end. + +-file("src/gleam/http.gleam", 606). +-spec parse_rfc_2045_parameter_value(binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter_value(Header, Name) -> + case gleam_stdlib:string_pop_grapheme(Header) of + {error, nil} -> + {error, nil}; + + {ok, {<<"\""/utf8>>, Rest}} -> + parse_rfc_2045_parameter_quoted_value(Rest, Name, <<""/utf8>>); + + {ok, {Grapheme, Rest@1}} -> + {ok, + parse_rfc_2045_parameter_unquoted_value(Rest@1, Name, Grapheme)} + end. + +-file("src/gleam/http.gleam", 595). +-spec parse_rfc_2045_parameter(binary(), binary()) -> {ok, + {{binary(), binary()}, binary()}} | + {error, nil}. +parse_rfc_2045_parameter(Header, Name) -> + gleam@result:'try'( + gleam_stdlib:string_pop_grapheme(Header), + fun(_use0) -> + {Grapheme, Rest} = _use0, + case Grapheme of + <<"="/utf8>> -> + parse_rfc_2045_parameter_value(Rest, Name); + + _ -> + parse_rfc_2045_parameter( + Rest, + <> + ) + end + end + ). + +-file("src/gleam/http.gleam", 577). +-spec parse_rfc_2045_parameters(binary(), list({binary(), binary()})) -> {ok, + list({binary(), binary()})} | + {error, nil}. +parse_rfc_2045_parameters(Header, Parameters) -> + case gleam_stdlib:string_pop_grapheme(Header) of + {error, nil} -> + {ok, lists:reverse(Parameters)}; + + {ok, {<<";"/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {<<" "/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {<<"\t"/utf8>>, Rest}} -> + parse_rfc_2045_parameters(Rest, Parameters); + + {ok, {Grapheme, Rest@1}} -> + Acc = string:lowercase(Grapheme), + gleam@result:'try'( + parse_rfc_2045_parameter(Rest@1, Acc), + fun(_use0) -> + {Parameter, Rest@2} = _use0, + parse_rfc_2045_parameters(Rest@2, [Parameter | Parameters]) + end + ) + end. + +-file("src/gleam/http.gleam", 559). +-spec parse_content_disposition_type(binary(), binary()) -> {ok, + content_disposition()} | + {error, nil}. +parse_content_disposition_type(Header, Name) -> + case gleam_stdlib:string_pop_grapheme(Header) of + {error, nil} -> + {ok, {content_disposition, Name, []}}; + + {ok, {<<" "/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {<<"\t"/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {<<";"/utf8>>, Rest}} -> + Result = parse_rfc_2045_parameters(Rest, []), + gleam@result:map( + Result, + fun(Parameters) -> {content_disposition, Name, Parameters} end + ); + + {ok, {Grapheme, Rest@1}} -> + parse_content_disposition_type( + Rest@1, + <> + ) + end. + +-file("src/gleam/http.gleam", 553). +-spec parse_content_disposition(binary()) -> {ok, content_disposition()} | + {error, nil}. +parse_content_disposition(Header) -> + parse_content_disposition_type(Header, <<""/utf8>>). + +-file("src/gleam/http.gleam", 407). +-spec parse_header_value_loop( + bitstring(), + list({binary(), binary()}), + binary(), + bitstring() +) -> {ok, multipart_headers()} | {error, nil}. +parse_header_value_loop(Data, Headers, Name, Value) -> + case Data of + <<>> -> + more_please_headers( + Data, + fun(Data@1) -> + parse_header_value_loop(Data@1, Headers, Name, Value) + end + ); + + <<_>> -> + more_please_headers( + Data, + fun(Data@1) -> + parse_header_value_loop(Data@1, Headers, Name, Value) + end + ); + + <<_, _>> -> + more_please_headers( + Data, + fun(Data@1) -> + parse_header_value_loop(Data@1, Headers, Name, Value) + end + ); + + <<_, _, _>> -> + more_please_headers( + Data, + fun(Data@1) -> + parse_header_value_loop(Data@1, Headers, Name, Value) + end + ); + + <<"\r\n\r\n"/utf8, Data@2/binary>> -> + gleam@result:map( + gleam@bit_array:to_string(Value), + fun(Value@1) -> + Headers@1 = lists:reverse( + [{string:lowercase(Name), Value@1} | Headers] + ), + {multipart_headers, Headers@1, Data@2} + end + ); + + <<"\r\n "/utf8, Data@3/binary>> -> + parse_header_value_loop(Data@3, Headers, Name, Value); + + <<"\r\n\t"/utf8, Data@3/binary>> -> + parse_header_value_loop(Data@3, Headers, Name, Value); + + <<"\r\n"/utf8, Data@4/binary>> -> + gleam@result:'try'( + gleam@bit_array:to_string(Value), + fun(Value@2) -> + Headers@2 = [{string:lowercase(Name), Value@2} | Headers], + parse_header_name(Data@4, Headers@2) + end + ); + + <> -> + parse_header_value_loop( + Rest, + Headers, + Name, + <> + ); + + _ -> + {error, nil} + end. + +-file("src/gleam/http.gleam", 359). +-spec parse_header_name(bitstring(), list({binary(), binary()})) -> {ok, + multipart_headers()} | + {error, nil}. +parse_header_name(Data, Headers) -> + case Data of + <<" "/utf8, Rest/bitstring>> -> + parse_header_name(Rest, Headers); + + <<"\t"/utf8, Rest/bitstring>> -> + parse_header_name(Rest, Headers); + + <<_, _/bitstring>> -> + parse_header_name_loop(Data, Headers, <<>>); + + _ -> + more_please_headers( + Data, + fun(_capture) -> parse_header_name(_capture, Headers) end + ) + end. + +-file("src/gleam/http.gleam", 374). +-spec parse_header_name_loop( + bitstring(), + list({binary(), binary()}), + bitstring() +) -> {ok, multipart_headers()} | {error, nil}. +parse_header_name_loop(Data, Headers, Name) -> + case Data of + <<":"/utf8, Data@1/bitstring>> -> + case gleam@bit_array:to_string(Name) of + {ok, Name@1} -> + parse_header_value(Data@1, Headers, Name@1); + + {error, nil} -> + {error, nil} + end; + + <> -> + parse_header_name_loop(Data@2, Headers, <>); + + _ -> + more_please_headers( + Data, + fun(_capture) -> + parse_header_name_loop(_capture, Headers, Name) + end + ) + end. + +-file("src/gleam/http.gleam", 393). +-spec parse_header_value(bitstring(), list({binary(), binary()}), binary()) -> {ok, + multipart_headers()} | + {error, nil}. +parse_header_value(Data, Headers, Name) -> + case Data of + <<" "/utf8, Rest/bitstring>> -> + parse_header_value(Rest, Headers, Name); + + <<"\t"/utf8, Rest/bitstring>> -> + parse_header_value(Rest, Headers, Name); + + <<_, _/bitstring>> -> + parse_header_value_loop(Data, Headers, Name, <<>>); + + _ -> + more_please_headers( + Data, + fun(_capture) -> parse_header_value(_capture, Headers, Name) end + ) + end. + +-file("src/gleam/http.gleam", 345). +-spec do_parse_headers(bitstring()) -> {ok, multipart_headers()} | {error, nil}. +do_parse_headers(Data) -> + case Data of + <<"\r\n\r\n"/utf8, Data@1/binary>> -> + {ok, {multipart_headers, [], Data@1}}; + + <<"\r\n"/utf8, Data@2/binary>> -> + parse_header_name(Data@2, []); + + <<"\r"/utf8>> -> + more_please_headers(Data, fun do_parse_headers/1); + + <<>> -> + more_please_headers(Data, fun do_parse_headers/1); + + _ -> + {error, nil} + end. + +-file("src/gleam/http.gleam", 307). +-spec skip_preamble(bitstring(), bitstring(), integer()) -> {ok, + multipart_headers()} | + {error, nil}. +skip_preamble(Data, Boundary, Boundary_bytes) -> + case Data of + <<"\r\n--"/utf8, Rest/binary>> -> + case Rest of + <> when Found =:= Boundary -> + do_parse_headers(Rest@1); + + <<_:Boundary_bytes/binary, _/bitstring>> -> + skip_preamble(Rest, Boundary, Boundary_bytes); + + _ -> + more_please_headers( + Data, + fun(_capture) -> + skip_preamble(_capture, Boundary, Boundary_bytes) + end + ) + end; + + <<>> -> + more_please_headers( + Data, + fun(_capture@1) -> + skip_preamble(_capture@1, Boundary, Boundary_bytes) + end + ); + + <<"\r"/utf8>> -> + more_please_headers( + Data, + fun(_capture@1) -> + skip_preamble(_capture@1, Boundary, Boundary_bytes) + end + ); + + <<"\r\n"/utf8>> -> + more_please_headers( + Data, + fun(_capture@1) -> + skip_preamble(_capture@1, Boundary, Boundary_bytes) + end + ); + + <<"\r\n-"/utf8>> -> + more_please_headers( + Data, + fun(_capture@1) -> + skip_preamble(_capture@1, Boundary, Boundary_bytes) + end + ); + + <<_, Data@1/binary>> -> + skip_preamble(Data@1, Boundary, Boundary_bytes); + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"unreachable"/utf8>>, + file => <>, + module => <<"gleam/http"/utf8>>, + function => <<"skip_preamble"/utf8>>, + line => 341}) + end. + +-file("src/gleam/http.gleam", 280). +-spec do_parse_multipart_headers(bitstring(), bitstring(), integer()) -> {ok, + multipart_headers()} | + {error, nil}. +do_parse_multipart_headers(Data, Boundary, Boundary_bytes) -> + case Data of + <<"--"/utf8, Found:Boundary_bytes/binary, Rest/bitstring>> when Found =:= Boundary -> + case Rest of + <<"--"/utf8, Rest@1/bitstring>> -> + {ok, {multipart_headers, [], Rest@1}}; + + <<_, _, _/bitstring>> -> + do_parse_headers(Rest); + + _ -> + more_please_headers( + Data, + fun(Data@1) -> + do_parse_multipart_headers( + Data@1, + Boundary, + Boundary_bytes + ) + end + ) + end; + + _ -> + skip_preamble(Data, Boundary, Boundary_bytes) + end. + +-file("src/gleam/http.gleam", 271). +?DOC( + " Parse the headers for part of a multipart message, as defined in RFC 2045.\n" + "\n" + " This function skips any preamble before the boundary. The preamble may be\n" + " retrieved using `parse_multipart_body`.\n" + "\n" + " This function will accept input of any size, it is up to the caller to limit\n" + " it if needed.\n" + "\n" + " To enable streaming parsing of multipart messages, this function will return\n" + " a continuation if there is not enough data to fully parse the headers.\n" + " Further information is available in the documentation for `MultipartBody`.\n" +). +-spec parse_multipart_headers(bitstring(), binary()) -> {ok, + multipart_headers()} | + {error, nil}. +parse_multipart_headers(Data, Boundary) -> + Boundary@1 = gleam_stdlib:identity(Boundary), + Boundary_bytes = erlang:byte_size(Boundary@1), + do_parse_multipart_headers(Data, Boundary@1, Boundary_bytes). diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam@http@cookie.erl b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@cookie.erl new file mode 100644 index 0000000..3a27ce3 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@cookie.erl @@ -0,0 +1,170 @@ +-module(gleam@http@cookie). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/http/cookie.gleam"). +-export([defaults/1, parse/1, set_header/3]). +-export_type([same_site_policy/0, attributes/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type same_site_policy() :: lax | strict | none. + +-type attributes() :: {attributes, + gleam@option:option(integer()), + gleam@option:option(binary()), + gleam@option:option(binary()), + boolean(), + boolean(), + gleam@option:option(same_site_policy())}. + +-file("src/gleam/http/cookie.gleam", 17). +-spec same_site_to_string(same_site_policy()) -> binary(). +same_site_to_string(Policy) -> + case Policy of + lax -> + <<"Lax"/utf8>>; + + strict -> + <<"Strict"/utf8>>; + + none -> + <<"None"/utf8>> + end. + +-file("src/gleam/http/cookie.gleam", 40). +?DOC( + " Helper to create sensible default attributes for a set cookie.\n" + "\n" + " https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Attributes\n" +). +-spec defaults(gleam@http:scheme()) -> attributes(). +defaults(Scheme) -> + {attributes, + none, + none, + {some, <<"/"/utf8>>}, + Scheme =:= https, + true, + {some, lax}}. + +-file("src/gleam/http/cookie.gleam", 118). +-spec check_token(binary()) -> {ok, nil} | {error, nil}. +check_token(Token) -> + Contains_invalid_charachter = (((gleam_stdlib:contains_string( + Token, + <<" "/utf8>> + ) + orelse gleam_stdlib:contains_string(Token, <<"\t"/utf8>>)) + orelse gleam_stdlib:contains_string(Token, <<"\r"/utf8>>)) + orelse gleam_stdlib:contains_string(Token, <<"\n"/utf8>>)) + orelse gleam_stdlib:contains_string(Token, <<"\f"/utf8>>), + case Contains_invalid_charachter of + true -> + {error, nil}; + + false -> + {ok, nil} + end. + +-file("src/gleam/http/cookie.gleam", 99). +?DOC( + " Parse a list of cookies from a header string. Any malformed cookies will be\n" + " discarded.\n" + "\n" + " ## Backwards compatibility\n" + "\n" + " RFC 6265 states that cookies in the cookie header should be separated by a\n" + " `;`, however this function will also accept a `,` separator to remain\n" + " compatible with the now-deprecated RFC 2965, and any older software\n" + " following that specification.\n" +). +-spec parse(binary()) -> list({binary(), binary()}). +parse(Cookie_string) -> + _pipe = Cookie_string, + _pipe@1 = gleam@string:split(_pipe, <<";"/utf8>>), + _pipe@2 = gleam@list:flat_map( + _pipe@1, + fun(_capture) -> gleam@string:split(_capture, <<","/utf8>>) end + ), + gleam@list:filter_map( + _pipe@2, + fun(Pair) -> + case gleam@string:split_once(gleam@string:trim(Pair), <<"="/utf8>>) of + {ok, {<<""/utf8>>, _}} -> + {error, nil}; + + {ok, {Key, Value}} -> + Key@1 = gleam@string:trim(Key), + gleam@result:'try'( + check_token(Key@1), + fun(_) -> + Value@1 = gleam@string:trim(Value), + gleam@result:'try'( + check_token(Value@1), + fun(_) -> {ok, {Key@1, Value@1}} end + ) + end + ); + + {error, nil} -> + {error, nil} + end + end + ). + +-file("src/gleam/http/cookie.gleam", 53). +-spec cookie_attributes_to_list(attributes()) -> list(binary()). +cookie_attributes_to_list(Attributes) -> + {attributes, Max_age, Domain, Path, Secure, Http_only, Same_site} = Attributes, + _pipe = [case Max_age of + {some, 0} -> + {some, <<"Expires=Thu, 01 Jan 1970 00:00:00 GMT"/utf8>>}; + + _ -> + none + end, gleam@option:map( + Max_age, + fun(Max_age@1) -> + <<"Max-Age="/utf8, + (erlang:integer_to_binary(Max_age@1))/binary>> + end + ), gleam@option:map( + Domain, + fun(Domain@1) -> <<"Domain="/utf8, Domain@1/binary>> end + ), gleam@option:map( + Path, + fun(Path@1) -> <<"Path="/utf8, Path@1/binary>> end + ), case Secure of + true -> + {some, <<"Secure"/utf8>>}; + + false -> + none + end, case Http_only of + true -> + {some, <<"HttpOnly"/utf8>>}; + + false -> + none + end, gleam@option:map( + Same_site, + fun(Same_site@1) -> + <<"SameSite="/utf8, (same_site_to_string(Same_site@1))/binary>> + end + )], + gleam@list:filter_map( + _pipe, + fun(_capture) -> gleam@option:to_result(_capture, nil) end + ). + +-file("src/gleam/http/cookie.gleam", 84). +-spec set_header(binary(), binary(), attributes()) -> binary(). +set_header(Name, Value, Attributes) -> + _pipe = [<<<>). diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam@http@request.erl b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@request.erl new file mode 100644 index 0000000..666e80d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@request.erl @@ -0,0 +1,403 @@ +-module(gleam@http@request). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/http/request.gleam"). +-export([to_uri/1, from_uri/1, get_header/2, set_header/3, prepend_header/3, set_body/2, map/2, path_segments/1, get_query/1, set_query/2, set_method/2, new/0, to/1, set_scheme/2, set_host/2, set_port/2, set_path/2, set_cookie/3, get_cookies/1, remove_cookie/2]). +-export_type([request/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type request(ECH) :: {request, + gleam@http:method(), + list({binary(), binary()}), + ECH, + gleam@http:scheme(), + binary(), + gleam@option:option(integer()), + binary(), + gleam@option:option(binary())}. + +-file("src/gleam/http/request.gleam", 30). +?DOC(" Return the uri that a request was sent to.\n"). +-spec to_uri(request(any())) -> gleam@uri:uri(). +to_uri(Request) -> + {uri, + {some, gleam@http:scheme_to_string(erlang:element(5, Request))}, + none, + {some, erlang:element(6, Request)}, + erlang:element(7, Request), + erlang:element(8, Request), + erlang:element(9, Request), + none}. + +-file("src/gleam/http/request.gleam", 44). +?DOC(" Construct a request from a URI.\n"). +-spec from_uri(gleam@uri:uri()) -> {ok, request(binary())} | {error, nil}. +from_uri(Uri) -> + gleam@result:'try'( + begin + _pipe = erlang:element(2, Uri), + _pipe@1 = gleam@option:unwrap(_pipe, <<""/utf8>>), + gleam@http:scheme_from_string(_pipe@1) + end, + fun(Scheme) -> + gleam@result:'try'( + begin + _pipe@2 = erlang:element(4, Uri), + gleam@option:to_result(_pipe@2, nil) + end, + fun(Host) -> + Req = {request, + get, + [], + <<""/utf8>>, + Scheme, + Host, + erlang:element(5, Uri), + erlang:element(6, Uri), + erlang:element(7, Uri)}, + {ok, Req} + end + ) + end + ). + +-file("src/gleam/http/request.gleam", 75). +?DOC( + " Get the value for a given header.\n" + "\n" + " If the request does not have that header then `Error(Nil)` is returned.\n" + "\n" + " Header keys are always lowercase in `gleam_http`. To use any uppercase\n" + " letter is invalid.\n" +). +-spec get_header(request(any()), binary()) -> {ok, binary()} | {error, nil}. +get_header(Request, Key) -> + gleam@list:key_find(erlang:element(3, Request), string:lowercase(Key)). + +-file("src/gleam/http/request.gleam", 86). +?DOC( + " Set the header with the given value under the given header key.\n" + "\n" + " If already present, it is replaced.\n" + "\n" + " Header keys are always lowercase in `gleam_http`. To use any uppercase\n" + " letter is invalid.\n" +). +-spec set_header(request(ECR), binary(), binary()) -> request(ECR). +set_header(Request, Key, Value) -> + Headers = gleam@list:key_set( + erlang:element(3, Request), + string:lowercase(Key), + Value + ), + {request, + erlang:element(2, Request), + Headers, + erlang:element(4, Request), + erlang:element(5, Request), + erlang:element(6, Request), + erlang:element(7, Request), + erlang:element(8, Request), + erlang:element(9, Request)}. + +-file("src/gleam/http/request.gleam", 103). +?DOC( + " Prepend the header with the given value under the given header key.\n" + "\n" + " Similar to `set_header` except if the header already exists it prepends\n" + " another header with the same key.\n" + "\n" + " Header keys are always lowercase in `gleam_http`. To use any uppercase\n" + " letter is invalid.\n" +). +-spec prepend_header(request(ECU), binary(), binary()) -> request(ECU). +prepend_header(Request, Key, Value) -> + Headers = [{string:lowercase(Key), Value} | erlang:element(3, Request)], + {request, + erlang:element(2, Request), + Headers, + erlang:element(4, Request), + erlang:element(5, Request), + erlang:element(6, Request), + erlang:element(7, Request), + erlang:element(8, Request), + erlang:element(9, Request)}. + +-file("src/gleam/http/request.gleam", 114). +?DOC(" Set the body of the request, overwriting any existing body.\n"). +-spec set_body(request(any()), ECZ) -> request(ECZ). +set_body(Req, Body) -> + {request, + erlang:element(2, Req), + erlang:element(3, Req), + Body, + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 120). +?DOC(" Update the body of a request using a given function.\n"). +-spec map(request(EDB), fun((EDB) -> EDD)) -> request(EDD). +map(Request, Transform) -> + _pipe = erlang:element(4, Request), + _pipe@1 = Transform(_pipe), + set_body(Request, _pipe@1). + +-file("src/gleam/http/request.gleam", 140). +?DOC( + " Return the non-empty segments of a request path.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " > new()\n" + " > |> set_path(\"/one/two/three\")\n" + " > |> path_segments\n" + " [\"one\", \"two\", \"three\"]\n" + " ```\n" +). +-spec path_segments(request(any())) -> list(binary()). +path_segments(Request) -> + _pipe = erlang:element(8, Request), + gleam@uri:path_segments(_pipe). + +-file("src/gleam/http/request.gleam", 146). +?DOC(" Decode the query of a request.\n"). +-spec get_query(request(any())) -> {ok, list({binary(), binary()})} | + {error, nil}. +get_query(Request) -> + case erlang:element(9, Request) of + {some, Query_string} -> + gleam_stdlib:parse_query(Query_string); + + none -> + {ok, []} + end. + +-file("src/gleam/http/request.gleam", 156). +?DOC( + " Set the query of the request.\n" + " Query params will be percent encoded before being added to the Request.\n" +). +-spec set_query(request(EDN), list({binary(), binary()})) -> request(EDN). +set_query(Req, Query) -> + Query@1 = begin + _pipe = gleam@list:map( + Query, + fun(Pair) -> + {Key, Value} = Pair, + <<<<(gleam_stdlib:percent_encode(Key))/binary, "="/utf8>>/binary, + (gleam_stdlib:percent_encode(Value))/binary>> + end + ), + _pipe@1 = gleam@string:join(_pipe, <<"&"/utf8>>), + {some, _pipe@1} + end, + {request, + erlang:element(2, Req), + erlang:element(3, Req), + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + Query@1}. + +-file("src/gleam/http/request.gleam", 173). +?DOC(" Set the method of the request.\n"). +-spec set_method(request(EDR), gleam@http:method()) -> request(EDR). +set_method(Req, Method) -> + {request, + Method, + erlang:element(3, Req), + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 180). +?DOC( + " A request with commonly used default values. This request can be used as\n" + " an initial value and then update to create the desired request.\n" +). +-spec new() -> request(binary()). +new() -> + {request, + get, + [], + <<""/utf8>>, + https, + <<"localhost"/utf8>>, + none, + <<""/utf8>>, + none}. + +-file("src/gleam/http/request.gleam", 195). +?DOC(" Construct a request from a URL string\n"). +-spec to(binary()) -> {ok, request(binary())} | {error, nil}. +to(Url) -> + _pipe = Url, + _pipe@1 = gleam_stdlib:uri_parse(_pipe), + gleam@result:'try'(_pipe@1, fun from_uri/1). + +-file("src/gleam/http/request.gleam", 203). +?DOC(" Set the scheme (protocol) of the request.\n"). +-spec set_scheme(request(EDY), gleam@http:scheme()) -> request(EDY). +set_scheme(Req, Scheme) -> + {request, + erlang:element(2, Req), + erlang:element(3, Req), + erlang:element(4, Req), + Scheme, + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 209). +?DOC(" Set the host of the request.\n"). +-spec set_host(request(EEB), binary()) -> request(EEB). +set_host(Req, Host) -> + {request, + erlang:element(2, Req), + erlang:element(3, Req), + erlang:element(4, Req), + erlang:element(5, Req), + Host, + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 215). +?DOC(" Set the port of the request.\n"). +-spec set_port(request(EEE), integer()) -> request(EEE). +set_port(Req, Port) -> + {request, + erlang:element(2, Req), + erlang:element(3, Req), + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + {some, Port}, + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 221). +?DOC(" Set the path of the request.\n"). +-spec set_path(request(EEH), binary()) -> request(EEH). +set_path(Req, Path) -> + {request, + erlang:element(2, Req), + erlang:element(3, Req), + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + Path, + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 232). +?DOC( + " Set a cookie on a request, replacing any previous cookie with that name.\n" + "\n" + " All cookies should be stored in a single header named `cookie`.\n" + " There should be at most one header with the name `cookie`, otherwise this\n" + " function cannot guarentee that previous cookies with the same name are\n" + " replaced.\n" +). +-spec set_cookie(request(EEK), binary(), binary()) -> request(EEK). +set_cookie(Req, Name, Value) -> + {Cookies, Headers} = begin + _pipe = gleam@list:key_pop(erlang:element(3, Req), <<"cookie"/utf8>>), + gleam@result:unwrap(_pipe, {<<""/utf8>>, erlang:element(3, Req)}) + end, + Cookies@1 = begin + _pipe@1 = gleam@http@cookie:parse(Cookies), + _pipe@2 = gleam@list:key_set(_pipe@1, Name, Value), + _pipe@3 = gleam@list:map( + _pipe@2, + fun(Pair) -> + <<<<(erlang:element(1, Pair))/binary, "="/utf8>>/binary, + (erlang:element(2, Pair))/binary>> + end + ), + gleam@string:join(_pipe@3, <<"; "/utf8>>) + end, + {request, + erlang:element(2, Req), + [{<<"cookie"/utf8>>, Cookies@1} | Headers], + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}. + +-file("src/gleam/http/request.gleam", 255). +?DOC( + " Fetch the cookies sent in a request.\n" + "\n" + " Note badly formed cookie pairs will be ignored.\n" + " RFC6265 specifies that invalid cookie names/attributes should be ignored.\n" +). +-spec get_cookies(request(any())) -> list({binary(), binary()}). +get_cookies(Req) -> + {request, _, Headers, _, _, _, _, _, _} = Req, + gleam@list:flat_map(Headers, fun(Header) -> case Header of + {<<"cookie"/utf8>>, Value} -> + gleam@http@cookie:parse(Value); + + _ -> + [] + end end). + +-file("src/gleam/http/request.gleam", 270). +?DOC( + " Remove a cookie from a request\n" + "\n" + " Remove a cookie from the request. If no cookie is found return the request\n" + " unchanged. This will not remove the cookie from the client.\n" +). +-spec remove_cookie(request(EEQ), binary()) -> request(EEQ). +remove_cookie(Req, Name) -> + case gleam@list:key_pop(erlang:element(3, Req), <<"cookie"/utf8>>) of + {ok, {Cookies_string, Headers}} -> + New_cookies_string = begin + _pipe = gleam@http@cookie:parse(Cookies_string), + _pipe@1 = gleam@list:filter_map( + _pipe, + fun(Cookie) -> case Cookie of + {Cookie_name, _} when Cookie_name =:= Name -> + {error, nil}; + + {Name@1, Value} -> + {ok, + <<<>) + end, + {request, + erlang:element(2, Req), + [{<<"cookie"/utf8>>, New_cookies_string} | Headers], + erlang:element(4, Req), + erlang:element(5, Req), + erlang:element(6, Req), + erlang:element(7, Req), + erlang:element(8, Req), + erlang:element(9, Req)}; + + {error, _} -> + Req + end. diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam@http@response.erl b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@response.erl new file mode 100644 index 0000000..c7a716e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@response.erl @@ -0,0 +1,163 @@ +-module(gleam@http@response). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/http/response.gleam"). +-export([new/1, get_header/2, set_header/3, prepend_header/3, set_body/2, try_map/2, map/2, redirect/1, get_cookies/1, set_cookie/4, expire_cookie/3]). +-export_type([response/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type response(EML) :: {response, integer(), list({binary(), binary()}), EML}. + +-file("src/gleam/http/response.gleam", 40). +?DOC( + " Construct an empty Response.\n" + "\n" + " The body type of the returned response is `String` and could be set with a\n" + " call to `set_body`.\n" +). +-spec new(integer()) -> response(binary()). +new(Status) -> + {response, Status, [], <<""/utf8>>}. + +-file("src/gleam/http/response.gleam", 48). +?DOC( + " Get the value for a given header.\n" + "\n" + " If the response does not have that header then `Error(Nil)` is returned.\n" +). +-spec get_header(response(any()), binary()) -> {ok, binary()} | {error, nil}. +get_header(Response, Key) -> + gleam@list:key_find(erlang:element(3, Response), string:lowercase(Key)). + +-file("src/gleam/http/response.gleam", 59). +?DOC( + " Set the header with the given value under the given header key.\n" + "\n" + " If the response already has that key, it is replaced.\n" + "\n" + " Header keys are always lowercase in `gleam_http`. To use any uppercase\n" + " letter is invalid.\n" +). +-spec set_header(response(ENA), binary(), binary()) -> response(ENA). +set_header(Response, Key, Value) -> + Headers = gleam@list:key_set( + erlang:element(3, Response), + string:lowercase(Key), + Value + ), + {response, + erlang:element(2, Response), + Headers, + erlang:element(4, Response)}. + +-file("src/gleam/http/response.gleam", 76). +?DOC( + " Prepend the header with the given value under the given header key.\n" + "\n" + " Similar to `set_header` except if the header already exists it prepends\n" + " another header with the same key.\n" + "\n" + " Header keys are always lowercase in `gleam_http`. To use any uppercase\n" + " letter is invalid.\n" +). +-spec prepend_header(response(END), binary(), binary()) -> response(END). +prepend_header(Response, Key, Value) -> + Headers = [{string:lowercase(Key), Value} | erlang:element(3, Response)], + {response, + erlang:element(2, Response), + Headers, + erlang:element(4, Response)}. + +-file("src/gleam/http/response.gleam", 87). +?DOC(" Set the body of the response, overwriting any existing body.\n"). +-spec set_body(response(any()), ENI) -> response(ENI). +set_body(Response, Body) -> + {response, erlang:element(2, Response), erlang:element(3, Response), Body}. + +-file("src/gleam/http/response.gleam", 27). +?DOC( + " Update the body of a response using a given result returning function.\n" + "\n" + " If the given function returns an `Ok` value the body is set, if it returns\n" + " an `Error` value then the error is returned.\n" +). +-spec try_map(response(EMM), fun((EMM) -> {ok, EMO} | {error, EMP})) -> {ok, + response(EMO)} | + {error, EMP}. +try_map(Response, Transform) -> + gleam@result:'try'( + Transform(erlang:element(4, Response)), + fun(Body) -> {ok, set_body(Response, Body)} end + ). + +-file("src/gleam/http/response.gleam", 96). +?DOC(" Update the body of a response using a given function.\n"). +-spec map(response(ENK), fun((ENK) -> ENM)) -> response(ENM). +map(Response, Transform) -> + _pipe = erlang:element(4, Response), + _pipe@1 = Transform(_pipe), + set_body(Response, _pipe@1). + +-file("src/gleam/http/response.gleam", 107). +?DOC(" Create a response that redirects to the given uri.\n"). +-spec redirect(binary()) -> response(binary()). +redirect(Uri) -> + {response, + 303, + [{<<"location"/utf8>>, Uri}], + gleam@string:append(<<"You are being redirected to "/utf8>>, Uri)}. + +-file("src/gleam/http/response.gleam", 119). +?DOC( + " Fetch the cookies sent in a response.\n" + "\n" + " Badly formed cookies will be discarded.\n" +). +-spec get_cookies(response(any())) -> list({binary(), binary()}). +get_cookies(Resp) -> + {response, _, Headers, _} = Resp, + gleam@list:flat_map(Headers, fun(Header) -> case Header of + {<<"set-cookie"/utf8>>, Value} -> + gleam@http@cookie:parse(Value); + + _ -> + [] + end end). + +-file("src/gleam/http/response.gleam", 132). +?DOC(" Set a cookie value for a client\n"). +-spec set_cookie( + response(ENS), + binary(), + binary(), + gleam@http@cookie:attributes() +) -> response(ENS). +set_cookie(Response, Name, Value, Attributes) -> + prepend_header( + Response, + <<"set-cookie"/utf8>>, + gleam@http@cookie:set_header(Name, Value, Attributes) + ). + +-file("src/gleam/http/response.gleam", 148). +?DOC( + " Expire a cookie value for a client\n" + "\n" + " Note: The attributes value should be the same as when the response cookie was set.\n" +). +-spec expire_cookie(response(ENV), binary(), gleam@http@cookie:attributes()) -> response(ENV). +expire_cookie(Response, Name, Attributes) -> + Attrs = {attributes, + {some, 0}, + erlang:element(3, Attributes), + erlang:element(4, Attributes), + erlang:element(5, Attributes), + erlang:element(6, Attributes), + erlang:element(7, Attributes)}, + set_cookie(Response, Name, <<""/utf8>>, Attrs). diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam@http@service.erl b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@service.erl new file mode 100644 index 0000000..c7bee4a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam@http@service.erl @@ -0,0 +1,85 @@ +-module(gleam@http@service). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/http/service.gleam"). +-export([map_response_body/2, prepend_response_header/3, method_override/1]). + +-file("src/gleam/http/service.gleam", 17). +-spec map_response_body( + fun((EQH) -> gleam@http@response:response(EQI)), + fun((EQI) -> EQK) +) -> fun((EQH) -> gleam@http@response:response(EQK)). +map_response_body(Service, Mapper) -> + fun(Req) -> _pipe = Req, + _pipe@1 = Service(_pipe), + gleam@http@response:map(_pipe@1, Mapper) end. + +-file("src/gleam/http/service.gleam", 29). +-spec prepend_response_header( + fun((EQM) -> gleam@http@response:response(EQN)), + binary(), + binary() +) -> fun((EQM) -> gleam@http@response:response(EQN)). +prepend_response_header(Service, Key, Value) -> + fun(Req) -> _pipe = Req, + _pipe@1 = Service(_pipe), + gleam@http@response:prepend_header(_pipe@1, Key, Value) end. + +-file("src/gleam/http/service.gleam", 41). +-spec ensure_post(gleam@http@request:request(EQQ)) -> {ok, + gleam@http@request:request(EQQ)} | + {error, nil}. +ensure_post(Req) -> + case erlang:element(2, Req) of + post -> + {ok, Req}; + + _ -> + {error, nil} + end. + +-file("src/gleam/http/service.gleam", 48). +-spec get_override_method(gleam@http@request:request(any())) -> {ok, + gleam@http:method()} | + {error, nil}. +get_override_method(Request) -> + gleam@result:'try'( + gleam@http@request:get_query(Request), + fun(Query_params) -> + gleam@result:'try'( + gleam@list:key_find(Query_params, <<"_method"/utf8>>), + fun(Method) -> + gleam@result:'try'( + gleam@http:parse_method(Method), + fun(Method@1) -> case Method@1 of + put -> + {ok, Method@1}; + + patch -> + {ok, Method@1}; + + delete -> + {ok, Method@1}; + + _ -> + {error, nil} + end end + ) + end + ) + end + ). + +-file("src/gleam/http/service.gleam", 59). +-spec method_override(fun((gleam@http@request:request(EQZ)) -> ERB)) -> fun((gleam@http@request:request(EQZ)) -> ERB). +method_override(Service) -> + fun(Request) -> _pipe = Request, + _pipe@1 = ensure_post(_pipe), + _pipe@2 = gleam@result:'try'(_pipe@1, fun get_override_method/1), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(_capture) -> + gleam@http@request:set_method(Request, _capture) + end + ), + _pipe@4 = gleam@result:unwrap(_pipe@3, Request), + Service(_pipe@4) end. diff --git a/examples/http_interactions/build/packages/gleam_http/src/gleam_http.app.src b/examples/http_interactions/build/packages/gleam_http/src/gleam_http.app.src new file mode 100644 index 0000000..56e2734 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_http/src/gleam_http.app.src @@ -0,0 +1,13 @@ +{application, gleam_http, [ + {vsn, "4.3.0"}, + {applications, [gleam_stdlib]}, + {description, "Types and functions for Gleam HTTP clients and servers"}, + {modules, [gleam@http, + gleam@http@cookie, + gleam@http@request, + gleam@http@response, + gleam@http@service, + gleam_http@@main, + gleam_http_test_helper]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_httpc/LICENSE b/examples/http_interactions/build/packages/gleam_httpc/LICENSE new file mode 100644 index 0000000..59e1345 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright {{copyright_year}}, {{author_name}} <{{author_email}}>. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_httpc/README.md b/examples/http_interactions/build/packages/gleam_httpc/README.md new file mode 100644 index 0000000..71414d3 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/README.md @@ -0,0 +1,45 @@ +# httpc + +Bindings to Erlang's built in HTTP client, `httpc`. + +[![Package Version](https://img.shields.io/hexpm/v/gleam_httpc)](https://hex.pm/packages/gleam_httpc) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_httpc/) + +```sh +gleam add gleam_httpc@5 +``` +```gleam +import gleam/http/request +import gleam/http/response +import gleam/httpc +import gleam/result + +pub fn send_request() { + // Prepare a HTTP request record + let assert Ok(base_req) = + request.to("https://test-api.service.hmrc.gov.uk/hello/world") + + let req = + request.prepend_header(base_req, "accept", "application/vnd.hmrc.1.0+json") + + // Send the HTTP request to the server + use resp <- result.try(httpc.send(req)) + + // We get a response record back + assert resp.status == 200 + + let content_type = response.get_header(resp, "content-type") + assert content_type == Ok("application/json") + + assert resp.body == "{\"message\":\"Hello World\"}" + + Ok(resp) +} +``` + +## Use with Erlang/OTP versions older than 26.0 + +Older versions of HTTPC do not verify TLS connections by default, so with them +your connection may not be secure when using this library. Consider upgrading to +a newer version of Erlang/OTP, or using a different HTTP client such as +[hackney](https://github.com/gleam-lang/hackney). diff --git a/examples/http_interactions/build/packages/gleam_httpc/gleam.toml b/examples/http_interactions/build/packages/gleam_httpc/gleam.toml new file mode 100644 index 0000000..70905e4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/gleam.toml @@ -0,0 +1,23 @@ +name = "gleam_httpc" +version = "5.0.0" +gleam = ">= 1.11.0" + +licences = ["Apache-2.0"] +description = "Gleam bindings to Erlang's built in HTTP client, httpc" + +repository = { type = "github", user = "gleam-lang", repo = "httpc" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[erlang] +extra_applications = ["inets", "ssl"] + +[dependencies] +gleam_stdlib = ">= 0.32.0 and < 2.0.0" +gleam_http = ">= 3.0.0 and < 5.0.0" +gleam_erlang = ">= 0.27.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Builder.hrl b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Builder.hrl new file mode 100644 index 0000000..5d004cd --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Builder.hrl @@ -0,0 +1,5 @@ +-record(builder, { + verify_tls :: boolean(), + follow_redirects :: boolean(), + timeout :: integer() +}). diff --git a/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_FailedToConnect.hrl b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_FailedToConnect.hrl new file mode 100644 index 0000000..237577f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_FailedToConnect.hrl @@ -0,0 +1,4 @@ +-record(failed_to_connect, { + ip4 :: gleam@httpc:connect_error(), + ip6 :: gleam@httpc:connect_error() +}). diff --git a/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Posix.hrl b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Posix.hrl new file mode 100644 index 0000000..e554942 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_Posix.hrl @@ -0,0 +1 @@ +-record(posix, {code :: binary()}). diff --git a/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_TlsAlert.hrl b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_TlsAlert.hrl new file mode 100644 index 0000000..7924187 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/include/gleam@httpc_TlsAlert.hrl @@ -0,0 +1 @@ +-record(tls_alert, {code :: binary(), detail :: binary()}). diff --git a/examples/http_interactions/build/packages/gleam_httpc/src/gleam/httpc.gleam b/examples/http_interactions/build/packages/gleam_httpc/src/gleam/httpc.gleam new file mode 100644 index 0000000..cfaeb69 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/src/gleam/httpc.gleam @@ -0,0 +1,257 @@ +import gleam/bit_array +import gleam/dynamic.{type Dynamic} +import gleam/erlang/charlist.{type Charlist} +import gleam/http.{type Method} +import gleam/http/request.{type Request} +import gleam/http/response.{type Response, Response} +import gleam/list +import gleam/result +import gleam/uri + +pub type HttpError { + /// The response body contained non-UTF-8 data, but UTF-8 data was expected. + InvalidUtf8Response + /// It was not possible to connect to the host. + FailedToConnect(ip4: ConnectError, ip6: ConnectError) + /// The response was not received within the configured timeout period. + ResponseTimeout +} + +pub type ConnectError { + Posix(code: String) + TlsAlert(code: String, detail: String) +} + +@external(erlang, "gleam_httpc_ffi", "default_user_agent") +fn default_user_agent() -> #(Charlist, Charlist) + +@external(erlang, "gleam_httpc_ffi", "normalise_error") +fn normalise_error(error: Dynamic) -> HttpError + +type ErlHttpOption { + Ssl(List(ErlSslOption)) + Autoredirect(Bool) + Timeout(Int) +} + +type BodyFormat { + Binary +} + +type ErlOption { + BodyFormat(BodyFormat) + SocketOpts(List(SocketOpt)) +} + +type SocketOpt { + Ipfamily(Inet6fb4) +} + +type Inet6fb4 { + Inet6fb4 +} + +type ErlSslOption { + Verify(ErlVerifyOption) +} + +type ErlVerifyOption { + VerifyNone +} + +@external(erlang, "httpc", "request") +fn erl_request( + a: Method, + b: #(Charlist, List(#(Charlist, Charlist)), Charlist, BitArray), + c: List(ErlHttpOption), + d: List(ErlOption), +) -> Result( + #(#(Charlist, Int, Charlist), List(#(Charlist, Charlist)), BitArray), + Dynamic, +) + +@external(erlang, "httpc", "request") +fn erl_request_no_body( + a: Method, + b: #(Charlist, List(#(Charlist, Charlist))), + c: List(ErlHttpOption), + d: List(ErlOption), +) -> Result( + #(#(Charlist, Int, Charlist), List(#(Charlist, Charlist)), BitArray), + Dynamic, +) + +fn string_header(header: #(Charlist, Charlist)) -> #(String, String) { + let #(k, v) = header + #(charlist.to_string(k), charlist.to_string(v)) +} + +// TODO: refine error type +/// Send a HTTP request of binary data using the default configuration. +/// +/// If you wish to use some other configuration use `dispatch_bits` instead. +/// +pub fn send_bits( + req: Request(BitArray), +) -> Result(Response(BitArray), HttpError) { + configure() + |> dispatch_bits(req) +} + +// TODO: refine error type +/// Send a HTTP request of binary data. +/// +pub fn dispatch_bits( + config: Configuration, + req: Request(BitArray), +) -> Result(Response(BitArray), HttpError) { + let erl_url = + req + |> request.to_uri + |> uri.to_string + |> charlist.from_string + let erl_headers = prepare_headers(req.headers) + let erl_http_options = [ + Autoredirect(config.follow_redirects), + Timeout(config.timeout), + ] + let erl_http_options = case config.verify_tls { + True -> erl_http_options + False -> [Ssl([Verify(VerifyNone)]), ..erl_http_options] + } + let erl_options = [BodyFormat(Binary), SocketOpts([Ipfamily(Inet6fb4)])] + + use response <- result.try( + case req.method { + http.Options | http.Head | http.Get -> { + let erl_req = #(erl_url, erl_headers) + erl_request_no_body(req.method, erl_req, erl_http_options, erl_options) + } + _ -> { + let erl_content_type = + req + |> request.get_header("content-type") + |> result.unwrap("application/octet-stream") + |> charlist.from_string + let erl_req = #(erl_url, erl_headers, erl_content_type, req.body) + erl_request(req.method, erl_req, erl_http_options, erl_options) + } + } + |> result.map_error(normalise_error), + ) + + let #(#(_version, status, _status), headers, resp_body) = response + Ok(Response(status, list.map(headers, string_header), resp_body)) +} + +/// Configuration that can be used to send HTTP requests. +/// +/// To be used with `dispatch` and `dispatch_bits`. +/// +pub opaque type Configuration { + Builder( + /// Whether to verify the TLS certificate of the server. + /// + /// This defaults to `True`, meaning that the TLS certificate will be verified + /// unless you call this function with `False`. + /// + /// Setting this to `False` can make your application vulnerable to + /// man-in-the-middle attacks and other security risks. Do not do this unless + /// you are sure and you understand the risks. + /// + verify_tls: Bool, + /// Whether to follow redirects. + /// + follow_redirects: Bool, + /// Timeout for the request in milliseconds. + /// + timeout: Int, + ) +} + +/// Create a new configuration with the default settings. +/// +/// # Defaults +/// +/// - TLS is verified. +/// - Redirects are not followed. +/// - The timeout for the response to be received is 30 seconds from when the +/// request is sent. +/// +pub fn configure() -> Configuration { + Builder(verify_tls: True, follow_redirects: False, timeout: 30_000) +} + +/// Set whether to verify the TLS certificate of the server. +/// +/// This defaults to `True`, meaning that the TLS certificate will be verified +/// unless you call this function with `False`. +/// +/// Setting this to `False` can make your application vulnerable to +/// man-in-the-middle attacks and other security risks. Do not do this unless +/// you are sure and you understand the risks. +/// +pub fn verify_tls(config: Configuration, which: Bool) -> Configuration { + Builder(..config, verify_tls: which) +} + +/// Set whether redirects should be followed automatically. +pub fn follow_redirects(config: Configuration, which: Bool) -> Configuration { + Builder(..config, follow_redirects: which) +} + +/// Set the timeout in milliseconds, the default being 30 seconds. +/// +/// If the response is not recieved within this amount of time then the +/// client disconnects and an error is returned. +/// +pub fn timeout(config: Configuration, timeout: Int) -> Configuration { + Builder(..config, timeout:) +} + +/// Send a HTTP request of unicode data. +/// +pub fn dispatch( + config: Configuration, + request: Request(String), +) -> Result(Response(String), HttpError) { + let request = request.map(request, bit_array.from_string) + use resp <- result.try(dispatch_bits(config, request)) + + case bit_array.to_string(resp.body) { + Ok(body) -> Ok(response.set_body(resp, body)) + Error(_) -> Error(InvalidUtf8Response) + } +} + +// TODO: refine error type +/// Send a HTTP request of unicode data using the default configuration. +/// +/// If you wish to use some other configuration use `dispatch` instead. +/// +pub fn send(req: Request(String)) -> Result(Response(String), HttpError) { + configure() + |> dispatch(req) +} + +fn prepare_headers( + headers: List(#(String, String)), +) -> List(#(Charlist, Charlist)) { + prepare_headers_loop(headers, [], False) +} + +fn prepare_headers_loop( + in: List(#(String, String)), + out: List(#(Charlist, Charlist)), + user_agent_set: Bool, +) -> List(#(Charlist, Charlist)) { + case in { + [] if user_agent_set -> out + [] -> [default_user_agent(), ..out] + [#(k, v), ..in] -> { + let user_agent_set = user_agent_set || k == "user-agent" + let out = [#(charlist.from_string(k), charlist.from_string(v)), ..out] + prepare_headers_loop(in, out, user_agent_set) + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_httpc/src/gleam@httpc.erl b/examples/http_interactions/build/packages/gleam_httpc/src/gleam@httpc.erl new file mode 100644 index 0000000..fc7b1c6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/src/gleam@httpc.erl @@ -0,0 +1,257 @@ +-module(gleam@httpc). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). +-define(FILEPATH, "src/gleam/httpc.gleam"). +-export([configure/0, verify_tls/2, follow_redirects/2, timeout/2, dispatch_bits/2, send_bits/1, dispatch/2, send/1]). +-export_type([http_error/0, connect_error/0, erl_http_option/0, body_format/0, erl_option/0, socket_opt/0, inet6fb4/0, erl_ssl_option/0, erl_verify_option/0, configuration/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type http_error() :: invalid_utf8_response | + {failed_to_connect, connect_error(), connect_error()} | + response_timeout. + +-type connect_error() :: {posix, binary()} | {tls_alert, binary(), binary()}. + +-type erl_http_option() :: {ssl, list(erl_ssl_option())} | + {autoredirect, boolean()} | + {timeout, integer()}. + +-type body_format() :: binary. + +-type erl_option() :: {body_format, body_format()} | + {socket_opts, list(socket_opt())}. + +-type socket_opt() :: {ipfamily, inet6fb4()}. + +-type inet6fb4() :: inet6fb4. + +-type erl_ssl_option() :: {verify, erl_verify_option()}. + +-type erl_verify_option() :: verify_none. + +-opaque configuration() :: {builder, boolean(), boolean(), integer()}. + +-file("src/gleam/httpc.gleam", 84). +-spec string_header( + {gleam@erlang@charlist:charlist(), gleam@erlang@charlist:charlist()} +) -> {binary(), binary()}. +string_header(Header) -> + {K, V} = Header, + {unicode:characters_to_binary(K), unicode:characters_to_binary(V)}. + +-file("src/gleam/httpc.gleam", 181). +?DOC( + " Create a new configuration with the default settings.\n" + "\n" + " # Defaults\n" + "\n" + " - TLS is verified.\n" + " - Redirects are not followed.\n" + " - The timeout for the response to be received is 30 seconds from when the\n" + " request is sent.\n" +). +-spec configure() -> configuration(). +configure() -> + {builder, true, false, 30000}. + +-file("src/gleam/httpc.gleam", 194). +?DOC( + " Set whether to verify the TLS certificate of the server.\n" + "\n" + " This defaults to `True`, meaning that the TLS certificate will be verified\n" + " unless you call this function with `False`.\n" + "\n" + " Setting this to `False` can make your application vulnerable to\n" + " man-in-the-middle attacks and other security risks. Do not do this unless\n" + " you are sure and you understand the risks.\n" +). +-spec verify_tls(configuration(), boolean()) -> configuration(). +verify_tls(Config, Which) -> + {builder, Which, erlang:element(3, Config), erlang:element(4, Config)}. + +-file("src/gleam/httpc.gleam", 199). +?DOC(" Set whether redirects should be followed automatically.\n"). +-spec follow_redirects(configuration(), boolean()) -> configuration(). +follow_redirects(Config, Which) -> + {builder, erlang:element(2, Config), Which, erlang:element(4, Config)}. + +-file("src/gleam/httpc.gleam", 208). +?DOC( + " Set the timeout in milliseconds, the default being 30 seconds.\n" + "\n" + " If the response is not recieved within this amount of time then the\n" + " client disconnects and an error is returned.\n" +). +-spec timeout(configuration(), integer()) -> configuration(). +timeout(Config, Timeout) -> + {builder, erlang:element(2, Config), erlang:element(3, Config), Timeout}. + +-file("src/gleam/httpc.gleam", 243). +-spec prepare_headers_loop( + list({binary(), binary()}), + list({gleam@erlang@charlist:charlist(), gleam@erlang@charlist:charlist()}), + boolean() +) -> list({gleam@erlang@charlist:charlist(), gleam@erlang@charlist:charlist()}). +prepare_headers_loop(In, Out, User_agent_set) -> + case In of + [] when User_agent_set -> + Out; + + [] -> + [gleam_httpc_ffi:default_user_agent() | Out]; + + [{K, V} | In@1] -> + User_agent_set@1 = User_agent_set orelse (K =:= <<"user-agent"/utf8>>), + Out@1 = [{unicode:characters_to_list(K), + unicode:characters_to_list(V)} | + Out], + prepare_headers_loop(In@1, Out@1, User_agent_set@1) + end. + +-file("src/gleam/httpc.gleam", 237). +-spec prepare_headers(list({binary(), binary()})) -> list({gleam@erlang@charlist:charlist(), + gleam@erlang@charlist:charlist()}). +prepare_headers(Headers) -> + prepare_headers_loop(Headers, [], false). + +-file("src/gleam/httpc.gleam", 104). +?DOC(" Send a HTTP request of binary data.\n"). +-spec dispatch_bits(configuration(), gleam@http@request:request(bitstring())) -> {ok, + gleam@http@response:response(bitstring())} | + {error, http_error()}. +dispatch_bits(Config, Req) -> + Erl_url = begin + _pipe = Req, + _pipe@1 = gleam@http@request:to_uri(_pipe), + _pipe@2 = gleam@uri:to_string(_pipe@1), + unicode:characters_to_list(_pipe@2) + end, + Erl_headers = prepare_headers(erlang:element(3, Req)), + Erl_http_options = [{autoredirect, erlang:element(3, Config)}, + {timeout, erlang:element(4, Config)}], + Erl_http_options@1 = case erlang:element(2, Config) of + true -> + Erl_http_options; + + false -> + [{ssl, [{verify, verify_none}]} | Erl_http_options] + end, + Erl_options = [{body_format, binary}, {socket_opts, [{ipfamily, inet6fb4}]}], + gleam@result:'try'( + begin + _pipe@6 = case erlang:element(2, Req) of + options -> + Erl_req = {Erl_url, Erl_headers}, + httpc:request( + erlang:element(2, Req), + Erl_req, + Erl_http_options@1, + Erl_options + ); + + head -> + Erl_req = {Erl_url, Erl_headers}, + httpc:request( + erlang:element(2, Req), + Erl_req, + Erl_http_options@1, + Erl_options + ); + + get -> + Erl_req = {Erl_url, Erl_headers}, + httpc:request( + erlang:element(2, Req), + Erl_req, + Erl_http_options@1, + Erl_options + ); + + _ -> + Erl_content_type = begin + _pipe@3 = Req, + _pipe@4 = gleam@http@request:get_header( + _pipe@3, + <<"content-type"/utf8>> + ), + _pipe@5 = gleam@result:unwrap( + _pipe@4, + <<"application/octet-stream"/utf8>> + ), + unicode:characters_to_list(_pipe@5) + end, + Erl_req@1 = {Erl_url, + Erl_headers, + Erl_content_type, + erlang:element(4, Req)}, + httpc:request( + erlang:element(2, Req), + Erl_req@1, + Erl_http_options@1, + Erl_options + ) + end, + gleam@result:map_error( + _pipe@6, + fun gleam_httpc_ffi:normalise_error/1 + ) + end, + fun(Response) -> + {{_, Status, _}, Headers, Resp_body} = Response, + {ok, + {response, + Status, + gleam@list:map(Headers, fun string_header/1), + Resp_body}} + end + ). + +-file("src/gleam/httpc.gleam", 94). +?DOC( + " Send a HTTP request of binary data using the default configuration.\n" + "\n" + " If you wish to use some other configuration use `dispatch_bits` instead.\n" +). +-spec send_bits(gleam@http@request:request(bitstring())) -> {ok, + gleam@http@response:response(bitstring())} | + {error, http_error()}. +send_bits(Req) -> + _pipe = configure(), + dispatch_bits(_pipe, Req). + +-file("src/gleam/httpc.gleam", 214). +?DOC(" Send a HTTP request of unicode data.\n"). +-spec dispatch(configuration(), gleam@http@request:request(binary())) -> {ok, + gleam@http@response:response(binary())} | + {error, http_error()}. +dispatch(Config, Request) -> + Request@1 = gleam@http@request:map(Request, fun gleam_stdlib:identity/1), + gleam@result:'try'( + dispatch_bits(Config, Request@1), + fun(Resp) -> case gleam@bit_array:to_string(erlang:element(4, Resp)) of + {ok, Body} -> + {ok, gleam@http@response:set_body(Resp, Body)}; + + {error, _} -> + {error, invalid_utf8_response} + end end + ). + +-file("src/gleam/httpc.gleam", 232). +?DOC( + " Send a HTTP request of unicode data using the default configuration.\n" + "\n" + " If you wish to use some other configuration use `dispatch` instead.\n" +). +-spec send(gleam@http@request:request(binary())) -> {ok, + gleam@http@response:response(binary())} | + {error, http_error()}. +send(Req) -> + _pipe = configure(), + dispatch(_pipe, Req). diff --git a/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc.app.src b/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc.app.src new file mode 100644 index 0000000..4960b04 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc.app.src @@ -0,0 +1,13 @@ +{application, gleam_httpc, [ + {vsn, "5.0.0"}, + {applications, [gleam_erlang, + gleam_http, + gleam_stdlib, + inets, + ssl]}, + {description, "Gleam bindings to Erlang's built in HTTP client, httpc"}, + {modules, [gleam@httpc, + gleam_httpc@@main, + gleam_httpc_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc_ffi.erl b/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc_ffi.erl new file mode 100644 index 0000000..561cdf0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_httpc/src/gleam_httpc_ffi.erl @@ -0,0 +1,32 @@ +-module(gleam_httpc_ffi). +-export([default_user_agent/0, normalise_error/1]). + +normalise_error(Error = {failed_connect, Opts}) -> + Ipv6 = case lists:keyfind(inet6, 1, Opts) of + {inet6, _, V1} -> V1; + _ -> erlang:error({unexpected_httpc_error, Error}) + end, + Ipv4 = case lists:keyfind(inet, 1, Opts) of + {inet, _, V2} -> V2; + _ -> erlang:error({unexpected_httpc_error, Error}) + end, + {failed_to_connect, normalise_ip_error(Ipv4), normalise_ip_error(Ipv6)}; +normalise_error(timeout) -> + response_timeout; +normalise_error(Error) -> + erlang:error({unexpected_httpc_error, Error}). + +normalise_ip_error(Code) when is_atom(Code) -> + {posix, erlang:atom_to_binary(Code)}; +normalise_ip_error({tls_alert, {A, B}}) -> + {tls_alert, erlang:atom_to_binary(A), unicode:characters_to_binary(B)}; +normalise_ip_error(Error) -> + erlang:error({unexpected_httpc_ip_error, Error}). + +default_user_agent() -> + Version = + case application:get_key(gleam_httpc, vsn) of + {ok, V} when is_list(V) -> V; + undefined -> "0.0.0" + end, + {"user-agent", "gleam_httpc/" ++ Version}. diff --git a/examples/http_interactions/build/packages/gleam_json/LICENCE b/examples/http_interactions/build/packages/gleam_json/LICENCE new file mode 100644 index 0000000..3d89615 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021 - present, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_json/README.md b/examples/http_interactions/build/packages/gleam_json/README.md new file mode 100644 index 0000000..1a06b52 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/README.md @@ -0,0 +1,47 @@ +# json 🐑 + +Work with JSON in Gleam! + +## Installation + +```shell +gleam add gleam_json@3 +``` + +## Encoding + +```gleam +import myapp.{type Cat} +import gleam/json + +pub fn cat_to_json(cat: Cat) -> String { + json.object([ + #("name", json.string(cat.name)), + #("lives", json.int(cat.lives)), + #("flaws", json.null()), + #("nicknames", json.array(cat.nicknames, of: json.string)), + ]) + |> json.to_string +} +``` + +## Parsing + +JSON is parsed into a `Dynamic` value which can be decoded using the +`gleam/dynamic/decode` module from the Gleam standard library. + +```gleam +import myapp.{Cat} +import gleam/json +import gleam/dynamic/decode + +pub fn cat_from_json(json_string: String) -> Result(Cat, json.DecodeError) { + let cat_decoder = { + use name <- decode.field("name", decode.string) + use lives <- decode.field("lives", decode.int) + use nicknames <- decode.field("nicknames", decode.list(decode.string)) + decode.success(Cat(name:, lives:, nicknames:)) + } + json.parse(from: json_string, using: cat_decoder) +} +``` diff --git a/examples/http_interactions/build/packages/gleam_json/gleam.toml b/examples/http_interactions/build/packages/gleam_json/gleam.toml new file mode 100644 index 0000000..f7e7f8d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/gleam.toml @@ -0,0 +1,18 @@ +name = "gleam_json" +version = "3.1.0" +gleam = ">= 1.13.0" + +licences = ["Apache-2.0"] +description = "Work with JSON in Gleam" + +repository = { type = "github", user = "gleam-lang", repo = "json" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.51.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.2.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_json/src/gleam/json.gleam b/examples/http_interactions/build/packages/gleam_json/src/gleam/json.gleam new file mode 100644 index 0000000..e3a3cae --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/src/gleam/json.gleam @@ -0,0 +1,316 @@ +import gleam/bit_array +import gleam/dict.{type Dict} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string_tree.{type StringTree} + +pub type Json + +pub type DecodeError { + UnexpectedEndOfInput + UnexpectedByte(String) + UnexpectedSequence(String) + UnableToDecode(List(decode.DecodeError)) +} + +/// Decode a JSON string into dynamically typed data which can be decoded into +/// typed data with the `gleam/dynamic` module. +/// +/// ## Examples +/// +/// ```gleam +/// > parse("[1,2,3]", decode.list(of: decode.int)) +/// Ok([1, 2, 3]) +/// ``` +/// +/// ```gleam +/// > parse("[", decode.list(of: decode.int)) +/// Error(UnexpectedEndOfInput) +/// ``` +/// +/// ```gleam +/// > parse("1", decode.string) +/// Error(UnableToDecode([decode.DecodeError("String", "Int", [])])) +/// ``` +/// +pub fn parse( + from json: String, + using decoder: decode.Decoder(t), +) -> Result(t, DecodeError) { + do_parse(from: json, using: decoder) +} + +@target(erlang) +fn do_parse( + from json: String, + using decoder: decode.Decoder(t), +) -> Result(t, DecodeError) { + let bits = bit_array.from_string(json) + parse_bits(bits, decoder) +} + +@target(javascript) +fn do_parse( + from json: String, + using decoder: decode.Decoder(t), +) -> Result(t, DecodeError) { + use dynamic_value <- result.try(decode_string(json)) + decode.run(dynamic_value, decoder) + |> result.map_error(UnableToDecode) +} + +@external(javascript, "../gleam_json_ffi.mjs", "decode") +fn decode_string(a: String) -> Result(Dynamic, DecodeError) + +/// Decode a JSON bit string into dynamically typed data which can be decoded +/// into typed data with the `gleam/dynamic` module. +/// +/// ## Examples +/// +/// ```gleam +/// > parse_bits(<<"[1,2,3]">>, decode.list(of: decode.int)) +/// Ok([1, 2, 3]) +/// ``` +/// +/// ```gleam +/// > parse_bits(<<"[">>, decode.list(of: decode.int)) +/// Error(UnexpectedEndOfInput) +/// ``` +/// +/// ```gleam +/// > parse_bits(<<"1">>, decode.string) +/// Error(UnableToDecode([decode.DecodeError("String", "Int", [])])), +/// ``` +/// +pub fn parse_bits( + from json: BitArray, + using decoder: decode.Decoder(t), +) -> Result(t, DecodeError) { + use dynamic_value <- result.try(decode_to_dynamic(json)) + decode.run(dynamic_value, decoder) + |> result.map_error(UnableToDecode) +} + +@external(erlang, "gleam_json_ffi", "decode") +fn decode_to_dynamic(json: BitArray) -> Result(Dynamic, DecodeError) { + case bit_array.to_string(json) { + Ok(string) -> decode_string(string) + Error(Nil) -> Error(UnexpectedByte("")) + } +} + +/// Convert a JSON value into a string. +/// +/// Where possible prefer the `to_string_tree` function as it is faster than +/// this function, and BEAM VM IO is optimised for sending `StringTree` data. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(array([1, 2, 3], of: int)) +/// "[1,2,3]" +/// ``` +/// +pub fn to_string(json: Json) -> String { + do_to_string(json) +} + +@external(erlang, "gleam_json_ffi", "json_to_string") +@external(javascript, "../gleam_json_ffi.mjs", "json_to_string") +fn do_to_string(a: Json) -> String + +/// Convert a JSON value into a string tree. +/// +/// Where possible prefer this function to the `to_string` function as it is +/// slower than this function, and BEAM VM IO is optimised for sending +/// `StringTree` data. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string_tree(array([1, 2, 3], of: int)) +/// string_tree.from_string("[1,2,3]") +/// ``` +/// +@external(erlang, "gleam_json_ffi", "json_to_iodata") +@external(javascript, "../gleam_json_ffi.mjs", "json_to_string") +pub fn to_string_tree(json: Json) -> StringTree + +/// Encode a string into JSON, using normal JSON escaping. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(string("Hello!")) +/// "\"Hello!\"" +/// ``` +/// +pub fn string(input: String) -> Json { + do_string(input) +} + +@external(erlang, "gleam_json_ffi", "string") +@external(javascript, "../gleam_json_ffi.mjs", "identity") +fn do_string(a: String) -> Json + +/// Encode a bool into JSON. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(bool(False)) +/// "false" +/// ``` +/// +pub fn bool(input: Bool) -> Json { + do_bool(input) +} + +@external(erlang, "gleam_json_ffi", "bool") +@external(javascript, "../gleam_json_ffi.mjs", "identity") +fn do_bool(a: Bool) -> Json + +/// Encode an int into JSON. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(int(50)) +/// "50" +/// ``` +/// +pub fn int(input: Int) -> Json { + do_int(input) +} + +@external(erlang, "gleam_json_ffi", "int") +@external(javascript, "../gleam_json_ffi.mjs", "identity") +fn do_int(a: Int) -> Json + +/// Encode a float into JSON. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(float(4.7)) +/// "4.7" +/// ``` +/// +pub fn float(input: Float) -> Json { + do_float(input) +} + +@external(erlang, "gleam_json_ffi", "float") +@external(javascript, "../gleam_json_ffi.mjs", "identity") +fn do_float(input input: Float) -> Json + +/// The JSON value null. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(null()) +/// "null" +/// ``` +/// +pub fn null() -> Json { + do_null() +} + +@external(erlang, "gleam_json_ffi", "null") +@external(javascript, "../gleam_json_ffi.mjs", "do_null") +fn do_null() -> Json + +/// Encode an optional value into JSON, using null if it is the `None` variant. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(nullable(Some(50), of: int)) +/// "50" +/// ``` +/// +/// ```gleam +/// > to_string(nullable(None, of: int)) +/// "null" +/// ``` +/// +pub fn nullable(from input: Option(a), of inner_type: fn(a) -> Json) -> Json { + case input { + Some(value) -> inner_type(value) + None -> null() + } +} + +/// Encode a list of key-value pairs into a JSON object. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(object([ +/// #("game", string("Pac-Man")), +/// #("score", int(3333360)), +/// ])) +/// "{\"game\":\"Pac-Mac\",\"score\":3333360}" +/// ``` +/// +pub fn object(entries: List(#(String, Json))) -> Json { + do_object(entries) +} + +@external(erlang, "gleam_json_ffi", "object") +@external(javascript, "../gleam_json_ffi.mjs", "object") +fn do_object(entries entries: List(#(String, Json))) -> Json + +/// Encode a list into a JSON array. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(array([1, 2, 3], of: int)) +/// "[1, 2, 3]" +/// ``` +/// +pub fn array(from entries: List(a), of inner_type: fn(a) -> Json) -> Json { + entries + |> list.map(inner_type) + |> preprocessed_array +} + +/// Encode a list of JSON values into a JSON array. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(preprocessed_array([int(1), float(2.0), string("3")])) +/// "[1, 2.0, \"3\"]" +/// ``` +/// +pub fn preprocessed_array(from: List(Json)) -> Json { + do_preprocessed_array(from) +} + +@external(erlang, "gleam_json_ffi", "array") +@external(javascript, "../gleam_json_ffi.mjs", "array") +fn do_preprocessed_array(from from: List(Json)) -> Json + +/// Encode a Dict into a JSON object using the supplied functions to encode +/// the keys and the values respectively. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(dict(dict.from_list([ #(3, 3.0), #(4, 4.0)]), int.to_string, float) +/// "{\"3\": 3.0, \"4\": 4.0}" +/// ``` +/// +pub fn dict( + dict: Dict(k, v), + keys: fn(k) -> String, + values: fn(v) -> Json, +) -> Json { + object(dict.fold(dict, [], fn(acc, k, v) { [#(keys(k), values(v)), ..acc] })) +} diff --git a/examples/http_interactions/build/packages/gleam_json/src/gleam@json.erl b/examples/http_interactions/build/packages/gleam_json/src/gleam@json.erl new file mode 100644 index 0000000..8a33e49 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/src/gleam@json.erl @@ -0,0 +1,304 @@ +-module(gleam@json). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/json.gleam"). +-export([parse_bits/2, parse/2, to_string/1, to_string_tree/1, string/1, bool/1, int/1, float/1, null/0, nullable/2, object/1, preprocessed_array/1, array/2, dict/3]). +-export_type([json/0, decode_error/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type json() :: any(). + +-type decode_error() :: unexpected_end_of_input | + {unexpected_byte, binary()} | + {unexpected_sequence, binary()} | + {unable_to_decode, list(gleam@dynamic@decode:decode_error())}. + +-file("src/gleam/json.gleam", 88). +?DOC( + " Decode a JSON bit string into dynamically typed data which can be decoded\n" + " into typed data with the `gleam/dynamic` module.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > parse_bits(<<\"[1,2,3]\">>, decode.list(of: decode.int))\n" + " Ok([1, 2, 3])\n" + " ```\n" + "\n" + " ```gleam\n" + " > parse_bits(<<\"[\">>, decode.list(of: decode.int))\n" + " Error(UnexpectedEndOfInput)\n" + " ```\n" + "\n" + " ```gleam\n" + " > parse_bits(<<\"1\">>, decode.string)\n" + " Error(UnableToDecode([decode.DecodeError(\"String\", \"Int\", [])])),\n" + " ```\n" +). +-spec parse_bits(bitstring(), gleam@dynamic@decode:decoder(DNO)) -> {ok, DNO} | + {error, decode_error()}. +parse_bits(Json, Decoder) -> + gleam@result:'try'( + gleam_json_ffi:decode(Json), + fun(Dynamic_value) -> + _pipe = gleam@dynamic@decode:run(Dynamic_value, Decoder), + gleam@result:map_error( + _pipe, + fun(Field@0) -> {unable_to_decode, Field@0} end + ) + end + ). + +-file("src/gleam/json.gleam", 47). +-spec do_parse(binary(), gleam@dynamic@decode:decoder(DNI)) -> {ok, DNI} | + {error, decode_error()}. +do_parse(Json, Decoder) -> + Bits = gleam_stdlib:identity(Json), + parse_bits(Bits, Decoder). + +-file("src/gleam/json.gleam", 39). +?DOC( + " Decode a JSON string into dynamically typed data which can be decoded into\n" + " typed data with the `gleam/dynamic` module.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > parse(\"[1,2,3]\", decode.list(of: decode.int))\n" + " Ok([1, 2, 3])\n" + " ```\n" + "\n" + " ```gleam\n" + " > parse(\"[\", decode.list(of: decode.int))\n" + " Error(UnexpectedEndOfInput)\n" + " ```\n" + "\n" + " ```gleam\n" + " > parse(\"1\", decode.string)\n" + " Error(UnableToDecode([decode.DecodeError(\"String\", \"Int\", [])]))\n" + " ```\n" +). +-spec parse(binary(), gleam@dynamic@decode:decoder(DNE)) -> {ok, DNE} | + {error, decode_error()}. +parse(Json, Decoder) -> + do_parse(Json, Decoder). + +-file("src/gleam/json.gleam", 117). +?DOC( + " Convert a JSON value into a string.\n" + "\n" + " Where possible prefer the `to_string_tree` function as it is faster than\n" + " this function, and BEAM VM IO is optimised for sending `StringTree` data.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(array([1, 2, 3], of: int))\n" + " \"[1,2,3]\"\n" + " ```\n" +). +-spec to_string(json()) -> binary(). +to_string(Json) -> + gleam_json_ffi:json_to_string(Json). + +-file("src/gleam/json.gleam", 140). +?DOC( + " Convert a JSON value into a string tree.\n" + "\n" + " Where possible prefer this function to the `to_string` function as it is\n" + " slower than this function, and BEAM VM IO is optimised for sending\n" + " `StringTree` data.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string_tree(array([1, 2, 3], of: int))\n" + " string_tree.from_string(\"[1,2,3]\")\n" + " ```\n" +). +-spec to_string_tree(json()) -> gleam@string_tree:string_tree(). +to_string_tree(Json) -> + gleam_json_ffi:json_to_iodata(Json). + +-file("src/gleam/json.gleam", 151). +?DOC( + " Encode a string into JSON, using normal JSON escaping.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(string(\"Hello!\"))\n" + " \"\\\"Hello!\\\"\"\n" + " ```\n" +). +-spec string(binary()) -> json(). +string(Input) -> + gleam_json_ffi:string(Input). + +-file("src/gleam/json.gleam", 168). +?DOC( + " Encode a bool into JSON.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(bool(False))\n" + " \"false\"\n" + " ```\n" +). +-spec bool(boolean()) -> json(). +bool(Input) -> + gleam_json_ffi:bool(Input). + +-file("src/gleam/json.gleam", 185). +?DOC( + " Encode an int into JSON.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(int(50))\n" + " \"50\"\n" + " ```\n" +). +-spec int(integer()) -> json(). +int(Input) -> + gleam_json_ffi:int(Input). + +-file("src/gleam/json.gleam", 202). +?DOC( + " Encode a float into JSON.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(float(4.7))\n" + " \"4.7\"\n" + " ```\n" +). +-spec float(float()) -> json(). +float(Input) -> + gleam_json_ffi:float(Input). + +-file("src/gleam/json.gleam", 219). +?DOC( + " The JSON value null.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(null())\n" + " \"null\"\n" + " ```\n" +). +-spec null() -> json(). +null() -> + gleam_json_ffi:null(). + +-file("src/gleam/json.gleam", 241). +?DOC( + " Encode an optional value into JSON, using null if it is the `None` variant.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(nullable(Some(50), of: int))\n" + " \"50\"\n" + " ```\n" + "\n" + " ```gleam\n" + " > to_string(nullable(None, of: int))\n" + " \"null\"\n" + " ```\n" +). +-spec nullable(gleam@option:option(DNU), fun((DNU) -> json())) -> json(). +nullable(Input, Inner_type) -> + case Input of + {some, Value} -> + Inner_type(Value); + + none -> + null() + end. + +-file("src/gleam/json.gleam", 260). +?DOC( + " Encode a list of key-value pairs into a JSON object.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(object([\n" + " #(\"game\", string(\"Pac-Man\")),\n" + " #(\"score\", int(3333360)),\n" + " ]))\n" + " \"{\\\"game\\\":\\\"Pac-Mac\\\",\\\"score\\\":3333360}\"\n" + " ```\n" +). +-spec object(list({binary(), json()})) -> json(). +object(Entries) -> + gleam_json_ffi:object(Entries). + +-file("src/gleam/json.gleam", 292). +?DOC( + " Encode a list of JSON values into a JSON array.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(preprocessed_array([int(1), float(2.0), string(\"3\")]))\n" + " \"[1, 2.0, \\\"3\\\"]\"\n" + " ```\n" +). +-spec preprocessed_array(list(json())) -> json(). +preprocessed_array(From) -> + gleam_json_ffi:array(From). + +-file("src/gleam/json.gleam", 277). +?DOC( + " Encode a list into a JSON array.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(array([1, 2, 3], of: int))\n" + " \"[1, 2, 3]\"\n" + " ```\n" +). +-spec array(list(DNY), fun((DNY) -> json())) -> json(). +array(Entries, Inner_type) -> + _pipe = Entries, + _pipe@1 = gleam@list:map(_pipe, Inner_type), + preprocessed_array(_pipe@1). + +-file("src/gleam/json.gleam", 310). +?DOC( + " Encode a Dict into a JSON object using the supplied functions to encode\n" + " the keys and the values respectively.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " > to_string(dict(dict.from_list([ #(3, 3.0), #(4, 4.0)]), int.to_string, float)\n" + " \"{\\\"3\\\": 3.0, \\\"4\\\": 4.0}\"\n" + " ```\n" +). +-spec dict( + gleam@dict:dict(DOC, DOD), + fun((DOC) -> binary()), + fun((DOD) -> json()) +) -> json(). +dict(Dict, Keys, Values) -> + object( + gleam@dict:fold( + Dict, + [], + fun(Acc, K, V) -> [{Keys(K), Values(V)} | Acc] end + ) + ). diff --git a/examples/http_interactions/build/packages/gleam_json/src/gleam_json.app.src b/examples/http_interactions/build/packages/gleam_json/src/gleam_json.app.src new file mode 100644 index 0000000..3bad27e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/src/gleam_json.app.src @@ -0,0 +1,9 @@ +{application, gleam_json, [ + {vsn, "3.1.0"}, + {applications, [gleam_stdlib]}, + {description, "Work with JSON in Gleam"}, + {modules, [gleam@json, + gleam_json@@main, + gleam_json_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.erl b/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.erl new file mode 100644 index 0000000..06a26a0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.erl @@ -0,0 +1,66 @@ +-module(gleam_json_ffi). + +-export([ + decode/1, json_to_iodata/1, json_to_string/1, int/1, float/1, string/1, + bool/1, null/0, array/1, object/1 +]). + +-if(?OTP_RELEASE < 27). +-define(bad_version, + error({erlang_otp_27_required, << "Insufficient Erlang/OTP version. + +`gleam_json` uses the Erlang `json` module introduced in Erlang/OTP 27. +You are using Erlang/OTP "/utf8, (integer_to_binary(?OTP_RELEASE))/binary, " +Please upgrade your Erlang install or downgrade to `gleam_json` v1.0.1. +"/utf8>>})). + +decode(_) -> ?bad_version. +json_to_iodata(_) -> ?bad_version. +json_to_string(_) -> ?bad_version. +int(_) -> ?bad_version. +float(_) -> ?bad_version. +string(_) -> ?bad_version. +bool(_) -> ?bad_version. +array(_) -> ?bad_version. +object(_) -> ?bad_version. +null() -> ?bad_version. +-else. + +decode(Json) -> + try + {ok, json:decode(Json)} + catch + error:unexpected_end -> {error, unexpected_end_of_input}; + error:{invalid_byte, Byte} -> {error, {unexpected_byte, hex(Byte)}}; + error:{unexpected_sequence, Byte} -> {error, {unexpected_sequence, Byte}} + end. + +hex(I) -> + H = list_to_binary(integer_to_list(I, 16)), + <<"0x"/utf8, H/binary>>. + +json_to_iodata(Json) -> + Json. + +json_to_string(Json) when is_binary(Json) -> + Json; +json_to_string(Json) when is_list(Json) -> + list_to_binary(Json). + +null() -> <<"null">>. +bool(true) -> <<"true">>; +bool(false) -> <<"false">>. +int(X) -> json:encode_integer(X). +float(X) -> json:encode_float(X). +string(X) -> json:encode_binary(X). + +array([]) -> <<"[]">>; +array([First | Rest]) -> [$[, First | array_loop(Rest)]. +array_loop([]) -> "]"; +array_loop([Elem | Rest]) -> [$,, Elem | array_loop(Rest)]. + +object(List) -> encode_object([[$,, string(Key), $: | Value] || {Key, Value} <- List]). +encode_object([]) -> <<"{}">>; +encode_object([[_Comma | Entry] | Rest]) -> ["{", Entry, Rest, "}"]. + +-endif. diff --git a/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.mjs b/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.mjs new file mode 100644 index 0000000..1d8d3ff --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_json/src/gleam_json_ffi.mjs @@ -0,0 +1,201 @@ +import { + Result$Ok, + Result$Error, + List$isNonEmpty, + List$NonEmpty$first, + List$NonEmpty$rest, +} from "./gleam.mjs"; +import { + DecodeError$UnexpectedByte, + DecodeError$UnexpectedEndOfInput, +} from "./gleam/json.mjs"; + +export function json_to_string(json) { + return JSON.stringify(json); +} + +export function object(entries) { + return Object.fromEntries(entries); +} + +export function identity(x) { + return x; +} + +export function array(list) { + const array = []; + while (List$isNonEmpty(list)) { + array.push(List$NonEmpty$first(list)); + list = List$NonEmpty$rest(list); + } + return array; +} + +export function do_null() { + return null; +} + +export function decode(string) { + try { + const result = JSON.parse(string); + return Result$Ok(result); + } catch (err) { + return Result$Error(getJsonDecodeError(err, string)); + } +} + +export function getJsonDecodeError(stdErr, json) { + if (isUnexpectedEndOfInput(stdErr)) return DecodeError$UnexpectedEndOfInput(); + return toUnexpectedByteError(stdErr, json); +} + +/** + * Matches unexpected end of input messages in: + * - Chromium (edge, chrome, node) + * - Spidermonkey (firefox) + * - JavascriptCore (safari) + * + * Note that Spidermonkey and JavascriptCore will both incorrectly report some + * UnexpectedByte errors as UnexpectedEndOfInput errors. For example: + * + * @example + * // in JavascriptCore + * JSON.parse('{"a"]: "b"}) + * // => JSON Parse error: Expected ':' before value + * + * JSON.parse('{"a"') + * // => JSON Parse error: Expected ':' before value + * + * // in Chromium (correct) + * JSON.parse('{"a"]: "b"}) + * // => Unexpected token ] in JSON at position 4 + * + * JSON.parse('{"a"') + * // => Unexpected end of JSON input + */ +function isUnexpectedEndOfInput(err) { + const unexpectedEndOfInputRegex = + /((unexpected (end|eof))|(end of data)|(unterminated string)|(json( parse error|\.parse)\: expected '(\:|\}|\])'))/i; + return unexpectedEndOfInputRegex.test(err.message); +} + +/** + * Converts a SyntaxError to an UnexpectedByte error based on the JS runtime. + * + * For Chromium, the unexpected byte and position are reported by the runtime. + * + * For JavascriptCore, only the unexpected byte is reported by the runtime, so + * there is no way to know which position that character is in unless we then + * parse the string again ourselves. So instead, the position is reported as 0. + * + * For Spidermonkey, the position is reported by the runtime as a line and column number + * and the unexpected byte is found using those coordinates. + */ +function toUnexpectedByteError(err, json) { + let converters = [ + v8UnexpectedByteError, + oldV8UnexpectedByteError, + jsCoreUnexpectedByteError, + spidermonkeyUnexpectedByteError, + ]; + + for (let converter of converters) { + let result = converter(err, json); + if (result) return result; + } + + return DecodeError$UnexpectedByte(""); +} + +/** + * Matches unexpected byte messages in: + * - V8 (edge, chrome, node) + * + * Matches the character but not the position as this is no longer reported by + * V8. Boo! + */ +function v8UnexpectedByteError(err) { + const regex = /unexpected token '(.)', ".+" is not valid JSON/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[1]); + return DecodeError$UnexpectedByte(byte); +} + +/** + * Matches unexpected byte messages in: + * - V8 (edge, chrome, node) + * + * No longer works in current versions of V8. + * + * Matches the character and its position. + */ +function oldV8UnexpectedByteError(err) { + const regex = /unexpected token (.) in JSON at position (\d+)/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[1]); + return DecodeError$UnexpectedByte(byte); +} + +/** + * Matches unexpected byte messages in: + * - Spidermonkey (firefox) + * + * Matches the position in a 2d grid only and not the character. + */ +function spidermonkeyUnexpectedByteError(err, json) { + const regex = + /(unexpected character|expected .*) at line (\d+) column (\d+)/i; + const match = regex.exec(err.message); + if (!match) return null; + const line = Number(match[2]); + const column = Number(match[3]); + const position = getPositionFromMultiline(line, column, json); + const byte = toHex(json[position]); + return DecodeError$UnexpectedByte(byte); +} + +/** + * Matches unexpected byte messages in: + * - JavascriptCore (safari) + * + * JavascriptCore only reports what the character is and not its position. + */ +function jsCoreUnexpectedByteError(err) { + const regex = /unexpected (identifier|token) "(.)"/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[2]); + return DecodeError$UnexpectedByte(byte); +} + +function toHex(char) { + return "0x" + char.charCodeAt(0).toString(16).toUpperCase(); +} + +/** + * Gets the position of a character in a flattened (i.e. single line) string + * from a line and column number. Note that the position is 0-indexed and + * the line and column numbers are 1-indexed. + * + * @param {number} line + * @param {number} column + * @param {string} string + */ +function getPositionFromMultiline(line, column, string) { + if (line === 1) return column - 1; + + let currentLn = 1; + let position = 0; + string.split("").find((char, idx) => { + if (char === "\n") currentLn += 1; + if (currentLn === line) { + position = idx + column; + return true; + } + return false; + }); + + return position; +} diff --git a/examples/http_interactions/build/packages/gleam_otp/LICENCE b/examples/http_interactions/build/packages/gleam_otp/LICENCE new file mode 100644 index 0000000..619ec77 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_otp/README.md b/examples/http_interactions/build/packages/gleam_otp/README.md new file mode 100644 index 0000000..56c729e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/README.md @@ -0,0 +1,108 @@ +# Gleam OTP + +Fault tolerant multi-core programs with OTP, the BEAM actor framework. + +[![Package Version](https://img.shields.io/hexpm/v/gleam_otp)](https://hex.pm/packages/gleam_otp) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_otp/) + +```shell +gleam add gleam_otp@1 +``` +```gleam +import gleam/erlang/process.{type Subject} +import gleam/otp/actor + +pub fn main() { + // Start an actor + let assert Ok(actor) = + actor.new(0) + |> actor.on_message(handle_message) + |> actor.start + + // Send some messages to the actor + actor.send(actor.data, Add(5)) + actor.send(actor.data, Add(3)) + + // Send a message and get a reply + assert actor.call(actor.data, waiting: 10, sending: Get) == 8 +} + +pub fn handle_message(state: Int, message: Message) -> actor.Next(Int, Message) { + case message { + Add(i) -> { + let state = state + i + actor.continue(state) + } + Get(reply) -> { + actor.send(reply, state) + actor.continue(state) + } + } +} + +pub type Message { + Add(Int) + Get(Subject(Int)) +} +``` + +Gleam’s actor system is built with a few primary goals: + +- Full type safety of actors and messages. +- Be compatible with Erlang’s OTP actor framework. +- Provide fault tolerance and self-healing through supervisors. +- Have equivalent performance to Erlang’s OTP. + +This library documents its abstractions and functionality, but you may also wish +to read the documentation or other material on Erlang’s OTP framework to get a +fuller understanding of OTP, the problems it solves, and the motivations for its +design. + +Not all Erlang/OTP functionality is included in this library. Some is not +possible to represent in a type safe way, so it is not included. Other features +are still in development, such as further process supervision strategies. + +## Common types of actor + +This library provides several different types of actor that can be used in +Gleam programs. + +### Process + +The process is the lowest level building block of OTP, all other actors are +built on top of processes either directly or indirectly. Typically this +abstraction would not be used very often in Gleam applications, favour +other actor types that provide more functionality. + +Gleam's [process](https://hexdocs.pm/gleam_erlang/gleam/erlang/process.html) module is defined in the `gleam_erlang` library. + +### Actor + +The `actor` is the most commonly used process type in Gleam and serves as a good +building block for other abstractions. Like Erlang's `gen_server` it handles +OTP's system messages automatically to enable OTP's debugging and tracing +functionality. + +[Documentation](https://hexdocs.pm/gleam_otp/gleam/otp/actor.html) + +### Supervisor + +Supervisors are processes that start and then supervise other processes. +They can restart them if they crash, and terminate them when the application is +shutting down. + +Supervisors can start other supervisors, resulting in a hierarchical process +structure called a supervision tree, providing fault tolerance and monitoring +benefits to a Gleam application. + +- [gleam/otp/static_supervisor](https://hexdocs.pm/gleam_otp/gleam/otp/static_supervisor.html) documentation. +- [gleam/otp/factory_supervisor](https://hexdocs.pm/gleam_otp/gleam/otp/factory_supervisor.html) documentation. + +## Limitations and known issues + +Actors do not yet support all OTP system messages, so some of the OTP debugging +APIs may not be fully functional. These unsupported messages are discarded by +actors. + +If find that you have a need for one of the unimplemented system messages, open +an issue and we will implement support for it. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-dev-erlang.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-dev-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-dev-javascript.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-dev-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-lsp-erlang.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-lsp-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-lsp-javascript.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-lsp-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-prod-erlang.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-prod-erlang.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/gleam-prod-javascript.lock b/examples/http_interactions/build/packages/gleam_otp/build/gleam-prod-javascript.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache new file mode 100644 index 0000000000000000000000000000000000000000..6df971327f18f749a0b793f15ff987404cf6c2c3 GIT binary patch literal 4959 zcmb1OfPnOz)Wlr<)S{fkymbA4~Sr3U;wL$hp1tMig7~e;F83m zl90-RR1kkNBLl-xMg|6A3xiycBQ{ z<)tf>WTYx2=jWBAR+K1Y<|&kCWF}{T1(7YuELKQL&CE-O7@C@*$HfYD8XJ@jN-ZwU zDPe;07H~rt3!yY9z+rMQ+CLj43Q`9W0b!Uf1_lP0@vcQh`9pKsVO=N`3Q5s zi3-^zAjKJ(=^(obiZb(yGD|AC6iPA@^T3XV`5f$OM3}oJX6AtG1;r&aM8FhH!aN^r zG_}J#4eA9*n1g}^&95-OgqEZ^=cl9=~C0F_RB;hU3V?Y>WJ-Qqt10%=-APiChlZN>p zB*wtZz#sv_U=Fm5hGiI}6v)iLIN~z}C>%i;8dP8kmUzGg4YY0}t?&UG!@z*G7$dv# zVq|21#WpMp!|Xv$C@?u963P^228K06B@`wG#sSL7pvVDXm{&pcV2vOq9CacndN69s zH;fDnpNMD>fix3Wse*zAgs}w=`KgY9fe&OT0|P@rQD#|uN@h`Na!GzsB~*|BRPNzW zLC8{AyvDOKFf_3;Ffax{MHGTkOG=CKiXqilVo8QVewspJ9&$?pRGWevl%N22k&Z%n zMru*2LTW`xQKCX#ex9y7s0GUfszM=EutHj94yYAZl&VltS&#{8+*K-MrxujxDYzyk zXDEQxD-Yp39!t)Zk}oS&PjpOX)1oa;lB=!0~E)FmV&aB+bOdIm-TXq18NftG9W zX=#}_VhS9ISd!8@b5JL2RsANU;W#fj|Z! z)$xcL9%oAzq=Tv%31lD`!`uVrAT>5%g*!@B&cFaK^wGt!xsJqY3Kq2kU^RLuz(NB? ztHBry3=IAZ3=A_F7#JQhFfe>(U|{&dz`!8M$iN`Q$iR@y$iM*VTb^KKV0gvI!0?%o zfx(%Hfnfp@1H%I*28Mr33=ERY3=Emf3=ET)85q_wGcc@YW?nD|F=A`ID zi<@G7a7K#HFDbw-0qJDx7Z)W%RAOn->4A-ax(3nRGte{CGk}T08l7HH0R_iAXc>o6 zc!3H(h2;F4oK&!5K?QJfeo88+cTu8{oS3JOl&X-EpO})GqL7(ak`F4WU126d3)`~H zqLR|Y9EIG(d0|E&wSD#AZG3`RhEOerz4(M>tu$5!h7wi=P@A0K z*arvHp|E}c%nXob4rbEKKr{m(hJxC9bBHtpR+vM2_Apn#8Z4lk3oDdiG&tRXBtVw= zW#+*2Ol4(Ym;tKU83>R3U=+nLL-s(2jzH$4SO{|`q&5K6S0L-FSs54_p?3DNGB8Yr zvaxD~)IBhp+E^JFdZ5Y)rVCJ4ALJyEox+R^4C0Io;KqO z@@HURn90Dv@Q{Il;WGmR14ykTBLjmJBLhP+BLl-`Mh1oxj0_B~7#SEoGcqtZGchns zU}9i+z{J4tkBNanl9_=albL~G5;FtCT4n}@^~?+m8<-gw7+Dw?JXshRqF5Lh;#e3M zmas4|tYBea*v7)Zu#1I(;W!He!v_`y25D9X24z+T26a{jhIm#6hFMSyavKN(0QEQ_ A+W-In literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache new file mode 100644 index 0000000000000000000000000000000000000000..b998dbbaf5ac2461a74ed1e69be08885e2839306 GIT binary patch literal 6330 zcmWe+fPnOz)Wlr<)S{fkymbAcvC@v|=%uCnJ$;?hw zNJ%V7)GetjNL47wNGt)V%grw?QAp0u&CSotsZ=N}PEAor%P-CNmVE>$}A~K%~QzCQ^+sLNG(!ua&`1o0EJd*VtQ(^LUCzwhC*Vof-5)# z6%zAOxD;G-GAlBR^b|b7_7xPRrll4oCgr3ml;kUAbl#5FtJXIk%F;5{G zB%TVh3gq>Y3{ZHbDnuth;?kTFXc}Z#zzq>z2&F-p1ttfh{j))$Aax)S z5QgbuU|@h5?^;xpUxcm>W-0>%1GfACQw}m3mbpSp(wy^CQVa4k^GZPCAdIYz86?2K zz~Gme17$IQq(KvSXC8dIr94MWG zQe{bMF*sMHq=HI}qGCNRE(S&r6o9$`xfqR4OUvYdif5*z=9OfYRDyjA@&O1#<-imy zHjxd$83eHK2bG6l-N*#0NyQ~8IhjekPz7Mq^`Sumiy9Zy_+WsELp=%A3U)cV6iyey zH1%;ZFl^&wU|_U?dRV~`9wSimK}9k+zCfX=5Sd@9keCB0xKoQt5;H+1cX393X-*2P z_){nWMNUb+LV12sHl+Mcttd$?%1g}2sf3ogDTuPGSWh7&BehbYD7B=tC{H0f4^$R| zBE48gp*SB@JS#xWhZfkN0t99tk}(jQK$6M%MMbH}C7{wbwO9vYBPdCg7bO-Hq!z(T z&0Aft0C74q{+@}cEA*s;*01`S3i z1q)^{jVNLbmDo(eCSK)D@R7^jIa z=WJqTU^vdqz`!^K>JlmlMxfY9&n!#LgEkC6)u(5gLLQ`^ z05u8H@=NnlK=A@nidG4z7lYHGLJ7DA2j};URH#Xy$jjpb+X^bTK}JCmRB8$|W*8W8 z6#_7EkTc`cit=;gA6BY7O%ON!aq!odb7`R|f0@T!ynFfk`u(2umsm0*3C^4rfH8G`9 z0h|s|(;pW!*?e= zp*>Ivnobl15o%aN;t8j3U_pn{2IB+;4+8^3Nj|KIhYEm#3{w)P;jo}O%f`U)jE#YT zaV^vwg&=S%2Gj*kfMkLM1#okuxFA0-1)OQYrG-LCYDI}cQEEX^YH?~_3AkYkDmg#} z9k_zzQb+)~JONZdDJv_uLb_YUTnY&Z3E-IEQpiayQOM5*=K}>>XwOkYDL-3DlS={a zK@F%1O)dp}eFa@R1&{);DfmkPP~HcX0g%!FXDL8=lt2RxPNAfKSe}FgC0q?G__4+@ z0|Nu~T?Ml`2*zMwU=U?sU@&B0U@&4}V5ntaU^vCVz#z%Uz+lYCz+leEz>vkrz>vqt zz|g|Tz|g_Sz;KX}fq{#OfgzHKfnf;~1H&OE1_scO%@rmFhO0~r3~bB{3|!0%3{lJs z3?C}_44~H4U1kP`d&~?B8Y~P9g)9sVwJZz_jVufdXIK~*#90{_l2{oS8dw<^ zT38tvHnB1=TxMlpxWdZ7pvcC+pv%UFs$cbVEDxF{K-61fD_1M7u4 z12Ga{pl7IO02hTZKt%?KjjsuhwOwmwFH*ZcQP|D>|thL07>E02d>K? zr5m(HfN5XL&cLvp9iknddqGJWq4B&AOl>86lfX6Igo-yHKV6eoe8fE0hmWzSG37cw| zL;T@`K|)*%3?fjaF!zH~1acaIH4M-KWhy5F!yHbCcW~;36=#qpG|T}zSs57ivqH2w zb1^V@LY)BfJlF}aq7F2q0}BW)E(QiZsA8OU!rTjOF~KbD;$mQ!$OWTNJEFg#>qV0a3ul^GZqcC#}u><5iPF)%Qgb1*Pi zb3l^WMotEXtx!LsxE2;ikSYgc4Jbn#U}j)A2DR!iD+9wxs8z4n7#Kc4twJ#drqM4m z2j=G+%nS^-p=O|{g;@#d1;Uh{W@ccx2vv@?9DxN1bbJVA;~91ahRaYnZ4L$oBXCoW kfnfm`1H*D?5rMA^fNEtx8+KU9$-uAymIy#W!oa`)08o1t#Q*>R literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..12c902a6f64008259c2be1c5ae9ce56356f97ce9 GIT binary patch literal 442 zcmca8o0`D@158i`FO*KtNlnbvPpQmH%*{*&iHR^UFn}V{gCrvZgE1omgE=DuLlz?gLmndo zLklAVLkA-R!$C#{1}-KBhDas`h9yi442PH)7>+S9FkE3`V7SV}z`(}Lz`(`Kz!1gE zz)-@>z)-==z_5v#f#EJQ1H(OL1_lil28Kcw28LP|28Ko!28J^%3=HC|3=B!E3=9pd z3=A!-3=ErC85l0JGB8|WWnfTbV_?u_V_-03V_*%=s|*%=rL*cli~*clj>u`@7SVP{~t%Fe*R&cVQ7 z!@dRz<) iL0k+B5nK!mv0Mxc@mvfH30w>e?OY5D8=)8)oD2X!%{^BD literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache new file mode 100644 index 0000000000000000000000000000000000000000..95e444e5b8921b9dfd27ef16e4d07046cf905f43 GIT binary patch literal 2532 zcmWe-fPnOz)Wlr<)S{fkymbBKjKrdx%;FM|6c31CU|;|%h=(X(go<%MX=fz$hZq?c zE;BMP5HuL77ffNZ1g`;gNDb`bP&d<%w&&#P)C@oG+Q7F$W$xv_w2L_iyT4`Q#NoIatu|i2k zVu?aZzCvDpi9%v>a%w?|LQ-a4Vo{|+aY<2TUV5=YacOb}$WV7saHuqyU|?YI%glkY7(mh> z4B}&vW?%tHFfcGU<>#a50r>_b1(OdcDoq9P!5HQ@x5S)cgeXi4Cs+*wLrH!-BtD>g z29N?wNxTUR=JXUM28Ko^1_s7Js9_4u`FUljMI{P};4}qJUnThpiO^V9C@s#+ONXW% z^APiLpreLv! zoK^995?2xcn}$rl!X0cfEZqi!(>z=mG`%vU73Jq*34d(TcpV4xodh!jgDEou1EW9G zDx?^IM6&`o{6RqljtwGX0HhXY_zOYp0vQS^Hb9XE(tC%JB$9v+0|P@O0|P@G0|UcK1_p*73=9nNj0_A4j0_AJj0_B07#SG$F)}cK zDvT?P3=CHp85r1^7#Osf7#Q@K7#Kh~uZM|&p_hq)VLuZC!$T$phNnyn450Mj#LU0| z!Z7PvpbY(t{M=Oiw2b_;wA{qJJpJ^d{9OIiip1Q4oYZ3djFOUq_{_YL)S^U4fv%ra znwgWLUyzubotU0ltPhT|`23Or>=Ka5PrtY*8KM%sy3_;fhdKpO5F6+j>KVX9VVTDr zDxlzqC`K}i6%yeU7Ra6Hpz5kTBef`1Au%UMp(G;}R9mHjg%gWX70MEGGE)>lr3)x0POALR2_xN{8EMT%$yu>MV6eOo2rnQT$Eo7s^C&{74p+c zQuA~aQY*j}TtRA4Mq)v+LU~4No)^qCkmHKd5|cqi9a3GXqmY@WP>@(ul9^nZlUM}S z11%8qb5c@^6ml|?iV}-5Q$aR?t5PMHiRe`-NKIm~LSj*RX>Ka0hK4F+0Ovmt2XEnl zrL=+N5P4<>1}$a=22iDh*9?pr17<=t69WUNz6O~9s#*xT1{OQ8b`nSVXu!FuPls7#O;tTJ@M27)+pAK~{h;07>ch A<^TWy literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ed515bfd065908dfb164dc33ffb3d1ec7c49386f GIT binary patch literal 149 zcmY#bz?H#(4tA`ZdcE-4)47sJ5w$3@aHJ7=AD?Fvv49FeorGFk~f#C`x1H)BD1_pK}1_o^=1_pg528I+S28JFc28LcH28R7i3=9vM T7#N;1F)&ClGcY(oG0ZvuQr{CS literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache new file mode 100644 index 0000000000000000000000000000000000000000..67ee5f8a431467516fe2b65bd5d306949aa8ed2f GIT binary patch literal 5454 zcmWe+fPnOz)Wlr<)S{fkymbA%{FGFXFb{}eU|;}C#zQ2Tpkgdg+7GHWgNcEml!<`> zuYpiKU<#YzI5nf&1~vgqz>IOu&&x|qE^#d?$}a**D6lXvn6l8yJ-BQI*$lyK5Q+gt z2c;I5=9IufZvi(%dLfi%WME)`$-!v+5O9Y_R(VY(O?7+68#3=9ll|DvmdnaaSx zfX|^IZ6ISoM)89P1_lPV#LS%3l#qOAa6&~H7*(Jwh492;h0?slq?}ZRl6-|^NT`FD zB^jv-poF8xB@9*Tlb@WJ14YWef(93=9kkAsML(IUt2#%?g>t z3VHb@3W*@aIts=43YjG^v4Z^K;!N!J=9gro7IA@1(Bp!6IkY6rIX@+}AU`v&1Zo=t z$jdM`GnC<%nFD2krD002NHbtdhA{OYGhp&g`S~bj!a^A)A5v5b4+?lxx+UfmBSc~8 zh8<)s0|Nszq@g@;VnLU}nR{SPT*$`2u$zs6fl(7GqTpIwl9-f}S)8Ge2#d?i{5()} zCW1o~90jnLEmlZQ%u^^%%}Y_pO)V}?OiwKasZ32w&fro2M}b0OUW!6uUL}+Vb#rP8 z*sNkbE-rZ*zj9zk4YGO&MhEifdfuRF9eP!eqmndn14Iq?GIG}#cOi9fv$tQRRCPz zN(CwiHUwO7C8a7rw56sfBvmRTC+6gU3`i(W%}LYHOaPZ%1x2X^sd+GixfF`PwKgcV zAks%pDyV+S0Vj-N1zUw^P$cL}=VTl!oYBZg@NG$3j@P576yhFEDQ`USr`~tSQ!|!SQ!|~Ss54> zure?#VP#-=&dR{>nw5dUhK+$CosEGZn~i}1)CRuA#=vl!je+40sJLNgU=UzuU=U$v zU{GXdV2EaCU`S?XU`S_YU;s5KcCs@tfG{j58lVjQjQreG{j`kyw6xsBygdE%qWoO_ zRA_rbKcl3iAU-p%B(*3pxg;|`uUJ2+G&3hfzaTL=8`N&rheT?ANda~VNd2H+T$Bt^ ziQF^S1M7u41JM&S&@>7O zQx!ZyLIU&@Jo8YU1oIEXDWF6OGExC-ds${tNoit^LT+MmMrK|rs5=3w^ixw55*5I; zcf6iLNJeHcsF7cqlM1r1EHfpwSOHW9C={jUB$lM6z>O@aEJ!T|HxklH^B|E5@@G=2 zLTNF`$YO;=g|z(AycCG*lVF`7Sd>GJ2L%PliJ-o3Zhld!LUBQAa%N&qCdd$&$%#3c zC6#(mWeiSGhJssukpfsxW*#WIAQt7Lc34oNrdXjkH5J@Mf(X09OiIa5F3nBND*+o} zrCCWuB@dl6^IVXHG> zRe>gS_zdLjGi(eDH$cM%3^<0;QGua2W`SSD1mI%n0*JA#{)tWWiY`28N5E#w7y-gCq+BgDj|D!N9<9jE#Ze6x5>C w>P`>6vL7+q)QD8>iujC3`e2%fIJJr01Sd&Qvd(} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d4535d6c7b3c85cdfbb26bc3e6a1f056cd21414d GIT binary patch literal 338 zcmZ4X&Mt!i1{k3XK`5P`lbV>TpIVfYn3t}fSdyRX#Kgdm#>Bv|cZyNt&f^n!9H0t8 zls5wd!%PMShGz^63}%cB3~h`I467L#7&b66FnniZU~pt&U;yb)XJTN;U}9h>V`5+^ zXJTNO!oaD;_{;Q|W-!!s5Jh8HXh z3@=$27+6>t7_?X!7|K~07#6THFf3tZV0g~T!0?)tfx(82fgzoZfgziXfngyV1H&yg z28P>g3=DtR7#JAX85jiE85l&^85k7V85pA385ok;85q*p85m}=GcfFgVo(@>FaVth BFW&$F literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache new file mode 100644 index 0000000000000000000000000000000000000000..37af2350c85b69ac1c248e930193f7d6fc5373bc GIT binary patch literal 1336 zcmWe+fPnOz)Wlr<)S{fkymbA7{Gt+&Fb{}eU|;}C#zQ0-p<*mhIsmG+gpq-vk&%G` zuYpiKU<#YzI5nf&1~vgiFvdU~qyTbAu|i@|szOG7xk7S&N~%IpXsS25SC80_|;!<$SFH*?OF9JCfWKd#BW_}*XOJJK6ic?d;E{=x!4kDV8pIn-onpXl= z92;#I3oG~=8$@)d9X{kl2dC95qxruqDi8{(1_lN;C>@kqT$)qD1m!K@hAOPjUa@{sX=YA}enDb# zc4B&Ju|C98`6UI|B_KtOesNJUL?tw}f)k}4STEEWh{R){XQ*cY7sbVZ#VJt%2~%7P LE!toS2V^(^knROf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..496423bd4173137107f6c33cb0047d5c29e5331d GIT binary patch literal 89 zcmY#bz?H#(4jR1El=qw~Udn|e;lsedFqMIU;UNP9!&3$Z1_MS01`|dGh5|+gh7v{w Kh9)S6X#oHNAPOu1 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache new file mode 100644 index 0000000000000000000000000000000000000000..780f9d40ca9ee654d85d542a39b89e548177026c GIT binary patch literal 66734 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�jUIGkk4SH0FpE_WY7mB3sMEM3Tbo+ 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache new file mode 100644 index 0000000000000000000000000000000000000000..e292ea58cb5df02821d6a06878e6ccfe89a8e95e GIT binary patch literal 1741 zcmWe*fPnOz)Wlr<)S{fkymbAd)U?#1)V$IPHTtif*rR?ER=fe6L`s3R2|6-x6m3rkZK;NDUwOUx-v)#Kt)@GMct z$j?bBR>;d&C`c?S$xJTINi0&xO-;QOHP5EX&NPR4C6Z$;ix8NXjqCP;dtYwnAcF zih?URz}1Tt{6hj1(uxvuQ_J&a1mTHdkbNZ? zsS42$Lljc-lS^|`^GXs+GV}9dqYYz0rbHXYS}A0dloS+O>Fbx5m+L_yP%pnIT|Xs1 zSwA%`Ei*YYH7~g`KD{(EB~?E$r7SToIW!TmSfUCoNpsFmNiE3F%qszjgD|o>W{>~_1A|{?4wS_Jk_KTAAB!{t3rK>2 zfx#(1A4LzynII{cd`M9#I2(b);GT0!%qd2QqFa-fS`Jptz`y`94Q4w?7=Mm{={m>A zz;KI^fq_vGDx%$M8ti11Jt&FfuTJFwCe{C__IZKQ~oB zEh9fIEjKYQPd~jVKUY7sA~ClhC$(4~lw0F7^GZ^S5|cq$xmZ7`G&3hfzaTL=J25@A zSRWG3`6UI|B_Oewyh`x&@Kr4D<~34B(>Z3|PeDk65I%3e#|vk%8d` lBLf3S3MPi%5RAA7+rS04ZX literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..a0c82fae07c21b7e070f05a5ef8c0b26a42be261 GIT binary patch literal 113 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@actor.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..c01841329892b8f65523a03b0db72db6695e027e GIT binary patch literal 2944 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 k1_l-@1_lEw1_n7gOQ{ z21Y%QOBfg!6ddyu5_3SoR+3qks*s-nxasiS(1^Nr{G$YlbDyT?;jGt z#R}EM2Bjf^2lfyH1A`kcMA#ilgMu0+2c!M7L82gaAQ2FT>0)4DfEn*vRFq$Yt`24@ z0|NsKNIwGugHwKf4wT6NQVuc>#D~d;6qTle_+Sh(+$}Mu7$M37mSbQDElG3EPf0Dv z&&(@<3W1G;v2g@GEZ$-94U1=F*T7umpABPyXb^_!Mx-N{IfMcOW)A}c11uf*Wuk-y z$ORzBW03}@T96#b4ItV-8$^OINHs!~iGd-Sfq?-eOE`IgwSjB}Gl<9=Ap5{-2QDu# zqNO>IlRy|046yV9ON@C83=CC76%I_GlmiM05C$0s@+Al(#Tq0I2GHk>AV-26$AFf! z!D7(p8|Y#RBa!F=> zUa@{sX=YA}enDb#c4B&Ju|B*q(l0JbhVZafV0vJMFo(m7M*}@WJp-sqFe#9qL83(F zSOx|LM41fA?Xl1@g|L3~90IbH0hCROp+>-5PS_~4GzimZ#lXPe2sI3BIs*d(07GU1 ACIA2c literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@internal@result2.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@internal@result2.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..9cc18a632be5d897368d9900aa508f25816e1bab GIT binary patch literal 73 ucmY${c|U^z9b_qeJGjjv;Rzd(gdqb1LnH$OLk3=Fu9 zf@%O$7{+4PhHeR1FUSS5U={;IXmP3n#A&HTIf;4c3LxhwWabr@q$Z{?c7l{LFfb^% zq!tvVCMTAprdTQ9)uPA61va8sA+ab`AtS$BAvr%KRiUUfFE2AMT_HbDp(GYpR5_3RW(u)#v74nM|ic*X7ON)|I zK^}9q%@wth4rHMJp1Fw`?JFq~juU=U$sV31&BU`S?UU`S_VU|7t^z_6T=fdN!PJYr;E0Aa93 z1_p*iC__IZKQ~oBEh9fIEjKYQPd~jVKUY7sA~ClhC$(4~l+@y3xhpe2uUJ2+G&3hf zzaTL=J25@ASRYaZfXc1nqGSjUUW(~~C872p(ujecp`HO;6c+;)wuFl(n994*au60{ GAd>-stqi09 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@port.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@port.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..79e2f281c05fd224d5cb2b0e4a40258d3969ed66 GIT binary patch literal 101 zcmb=n<;-9}2fki^m+n0>?Hn(XggyfUgE0dGLp=in!wCik1`$RE1_?$6hGa$thIB>- WhQ*8w49giA7_KlfFg$`{n05fbI}G0d literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@static_supervisor.cache new file mode 100644 index 0000000000000000000000000000000000000000..e7ec4ea8a78a87b47ddb6e6f1f9331ee98955fed GIT binary patch literal 33385 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@supervision.cache new file mode 100644 index 0000000000000000000000000000000000000000..001046b8b4ed37374e9cada16c46f82df18ffe8f GIT binary patch literal 15062 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 K4-`X_5d#2hFIbxZ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache new file mode 100644 index 0000000000000000000000000000000000000000..b08536f6a807c7ed0528ae777abfe4cad270c8b6 GIT binary patch literal 12394 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 SgUtBB$iVOiY6i%B5C#BBzxz%A literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_otp/_gleam_artefacts/gleam@otp@system.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..7caa8d4423a069a9b08aa62d445ba8a529092e09 GIT binary patch literal 531 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 VGhyivrf9e)aHIf+MJ`gB2LOxqP2m6l literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..85380d03b28af5b06dab1ad4cb52ac3fab8ee236 GIT binary patch literal 1245 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache new file mode 100644 index 0000000000000000000000000000000000000000..fe62be798c44834ad860fcbb212c64360976e2c9 GIT binary patch literal 31217 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6550d99bef65421a4c6cc6444c979a991aab8187 GIT binary patch literal 2409 zcmd;k)sx8p1B_4x50p;NNlnbv&o3y+%+CV}DKaoH{Q4=^y3nmM%AE-$0>MfQ3=GN) z3=AF&3=BRD3=A_F7#JQiFfcr4U|^77WMGhCWMIfC$Fm>C!rGBYqNVrF2t$jrdN$-=;3 z&ceWu&BDO2frWu#BMSq=KNbdtNLB`hc2)+4y{rrjoNNpXTx<*sE^G`8li3&;rn50H zEN5e2Sk1=3u!D_(;TjtQ!#y?zhDU4+4DZ<(7}VJr81&g07>wB&7(&?@7@F7_7?!g$ zFsxu_V7ST7z;KtHf#EGX1H(sl28Ms^3=Gm73=GN~3=HZV3=Ae53=A0@3=9Pv3=Ab4 z3=9i67#KEjFfeT4U|`tF!N9=8$-tn_$-tn^$-ofD$-t1r$-q#=$-q#?$-uCUlYwCu zCj-NAP6mdToD2*fIT;wfaxyTua4|4sa4|6Ca4|4caxpM;aWOC~;$mRf#l^sIiHm{Z zF&6^^A2$Po7&ilh6gLBdJvReGF*gIl0d59{L);7uhq)OT*mxKixOf;C!g&}NDtH(e zYIqnJ7V|JL+~i?kVC7|CVCQ9E;NWFokmhAzu;pc72;pU5NaJN-DB)#bn8C}yu$`BI z;WsY>!+%}|21Y&x1|~iRh5$YWh7djmh7>*qh73LihEhHTh827a3>)|u7`E^+FdXD# zVED<$z`)4Qz`)ASz+lMFz>vbvz)-`_z);7}z);W6z_6H~f#EVg1H%=51_m|(1_mwx z1_o^b28IX$28Ln*28I#=28N{q3=As;7#O|^FfcF+GBB_UGBAh>GB8LBGB9WeGB6|v zGB9KaGBD%_GB8vMGB9itWMJ4Q$iQ$&kb&VnNSzP^Ly!;yL$DA7L#Ge}L$446!&M;$ zhHpX)48MdJ7`TKP82E%47^H<67$Srj7!rgT7*d287z%|M7!C+CFq{x(U^pYp!0=m` zfx%aVfgxLjfgx9ffgw+XfuUc7f#IA81H*X{28ItJ3=GDi3=AHk3=Bb{3=DCi3=9pT z3=A_x85m}XGBCUpWnlOr%E0hPl!1XqjDbNwjDbN`jDf*TjDev_jDcaH7z4vfF$RXU zVhjv7#TXbg#Tgh}#2Fae#2Fae#Tghf#2FY?i!(5+5ochyDbB!fSDb-CPlAD=R)T?H zhXez|9tj49GZG987bF-M?n*E)C`d9eXhuiKz)-Kmz%WILfnkFZ0|TQn1B0*H3o(SY77i_)EF4p)fpK0)fpIs)fpJV)fpIO zsWULlQDzaMEO8@X};p@Y7^qh}L9an6Amda7dGZ;glu=!#Pa`hTED942oI|3>I1p3=Uch z3@%y>4AZn27`AIMFznP~VA!R_z;IuSfk921fgxO*fgw_xfgwtpfgwYifuT#Afnk|8 z1H%q&28N5;3=Chj85nGI7#JLM7#N&%7#N&&7#ODNFfh#2VPJT!!@!`a%fO(k%fMi( z%fR5M%fR5L%fL{g%fK)}mw{ovE(603T?U3zx(p1LbQu_K=rS&z(_>)J z(qmvS*JEI4(PLof(PLnkpvS;4Uyp%dn;rwhaXkix=XwkbKlK&~CuMaL9mx;g|sf!!-j2hFb;<49^W1 z7?cee7zzy;7-|g}7+MV(7&;9Z7^WFAFsw6VVAy2H!0^tHf#Ihi1A~qc14D=r14FD4 z14E7x14D@s14D%o1H)1y28K;W3=F4@7#JQHF)%P0GcfQPGcdRrGccqWGccqXGccqZ zGca@-Gca5;W?;B(%)p>x!oZ+!!oXl`!oW~s!oW~r!objK!oV=ogn?nM2?N7h69$Hl zCJYRJOc)p>Oc@w#O&J*6Oc@x0O&J(cO&J&}Oc@xuO&J&_n=&v=H)UXWY|6mEWX8b2 zX2!taXvV;hY{tM)XvV-$X~w`%XU4$LV8+1EXvV;B-i(3avKa$|vN;1osyPEgra1#c zr8xsbtvLfjmpKE&4s!;EXXXqH@)isXp%x4bu@(#ri53hDg%%79triRnvn&`GwpuVS z9JgR#_+!Dq@XvyQ;lBj~gSsUHgPA1*L$DLdDpjv zez`(PMyf(dWkIS!ewspMYKb0~LU^h|PG)v$PNhP6Y6(Oih_6tRky)&eR+OKskeOGK zT9jX)P@Y+mq2OASlbDyTqmW+&mgG|K^oK}*OwKPYDb7quRVdFd%1P1V;!+3!8Iqfy zQks*hker`al9-uStdN|alB$pfG9bSwH#09?0UGFF%XJhI^HM<0Q0&y`YK=Qz$QLLvBnO~|mli1$6y;~7CYKaLyqcw#baVjVO%gHwKf4wMO&XM%EI z@*zc~sZb#X2ACyoi8;jxQD&&TUnYu4Fx#+5Gq6Fm1f>?2=9Iwfgy}%$!`Lv|KO0#U zNEF6rK)BDfs3^Y(CJs^q!%%B+HL)aBArYRm!7&cbF$#$aAe;2KxEL5g02ZC##11WHIH2ZarljVT zWR_Gi3PT0-^z@O{>E&h>W2j|d07o;-aZsIb3Kqj)b6|e-$t*5G*MQAoSc3saSi^0E zr&zF+P%n~X5-c$lmlS2@rNaycMG~3}R#RafTgS@4aEz6KfiWLy0)c3P*aphm{&~=H z4^k?E%ZVgV^d@EIB^FgGKvXIeC4x!`g_4ZKJcUGs3M%RI@=G!@^V0D| z5vW#5EXmJRNXW~~NzefmvtEg1iNVQ5nFS>%vB{;7P@0#LnwFWDnvwv^ow&0S#km8P z^^%Z^XHZz7h+_>bSbAQ~!oaYfh4^9zWE0F|jM&_dQIycS*g>wEpb<`mM92v>Dk-xh zKC!4Mu@cH>00l3mB;GK-!p^|(k)45oQ5$Lqfifd0vqS-G=imrvSd2PlmN+7Z1k^KB z2y9q&0`@e_SWrNtO5hFTubd1F!dwgtj0d0w5C~*Y{iy?LPnM+?m4M2D%o2r?e1$}X z;?%-YP>q?G18anFff|~*i6tmyGpJA~N=+}#NraTmP&Igpkwk@((t?~+eC3A%sL%(6 zF+ApBg>5Rh$qWktkmKV^^5ek<;;Y!vHGo4Hl=op68dxY4EY!gzIIJicrku;#?Ak2%Nh!#A^Ltu=}VOWC! zSMv*&_mguHi;FYUP)b1zIjknb0{9&>0|O7JqS1niCp}aIYvgu~H!^71Ym4E6o9AL{PpiE-5Na2Ah@&YUeBD zrWO|`rl%HzdYw7>>BV{qo@t;S7?(nMVjei}gPjAj3epMAE6G25N0dt;vA8(3s6+u4pc;^7pN5i0YEDkRlBTADt*wF*q-mwZ zrI3)2fUk`&0FGq_2AFB_P}k!t5^(Cl(dMT(4ME)qr%;L)SosF=F@~D7ocu(T4i!u@ zL;`P$OJ`$XsAXedU{r@1OQ1#o8#y@gG|ZQ7$lizgf(rF5tV~Z%MD*A|r6saB_Aw}E z-epMVVPL2RrwmZ{ljaRUkmEqO5LQk>!vgFAwBa?ZX#%DXI>rUn&cMJ0Wfc_V7l7IU zsl`wMa61o+ELPKDq4I)@fk6Y*^VkM81y_p8O-uogo+aifl;-6ZrKA?6rYIDr7G;8p zr!mH`mG7MSm#eu7i5L<+7?VMzzMnE9%dNDQ4ceS z@{t8t0O3kx)buvo)lfzXlm<~k3=9mQ-c}F;1H)7X28JyR3=BIM7#MyqFfh0=GB8YJ zWMEjw$iT3Pk%8e4BLjmZ69WTi#95DtfkB^%fgzWPfuWFzf#Dz%0|Of~1A`_r1A{Iz z1A`Sa14A}514A)014B781H)Qo1_n@dF3!ThAi=`G;K0Jb;KIVdP{hK(0P0VkW?^7B z!@|JujfH{X7YhS}Gb;mwJ1YZ2DJug5X#C_7D+9x2RtAPYpcKT$z@W#*zyK<&JJ}c* zy4V;PHnA}W?=Zs&A{*UKpTHkXq zF#LqN4sV2k8V)e4vv?R7@^~O#(&c7gFofFunVW&(JCu#pFqi>(2)EzkW?*>84KWL= z9$27)+iEap=dm#`6tm&c1+ytL4?aMx!p^{;4vjuoxeYTM9I~*~0vcX|S+t9Vfngu0 z4}%^JFvXzS4`$f{4hDuLpaFf5VlD;-PSD^40|NsyHvodGrI#3RTJEG!HR+)!U0W?^7B z1!bRNWnj1n8v15nU}$7xVCVp~tr-{?jM*6&te|#&WoKac17$DeU|?7S4S-D?3=BJ= zY%VSa20>_Gh;uP8$U)g$+zbqY(0~!urM%)LA|n{m4V?X)GH`jVH|M78y2YLYzz!FP|rdmfdK$p_Lb%U literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..217e51605e2a42dec2a06edc4d2593b6071d5395 GIT binary patch literal 475 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..3ebfeec536dbd72adbbd7fde51cf82a4c0629c64 GIT binary patch literal 4725 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache new file mode 100644 index 0000000000000000000000000000000000000000..70dda0f58a7fec3910b59f111471cee48554c5a2 GIT binary patch literal 25660 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 R7#JQ{Ffg!LLNEgq001zMe5?Qf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache new file mode 100644 index 0000000000000000000000000000000000000000..65ef41b05729369eaec02f77341506c25029d44d GIT binary patch literal 886 zcmd;MfPnOz)WlrrflFa-(kQfh$U|?VX%fuI#q~v5KL4_DtpbVIr(2_Lg{FKy! z{LH)(s1ySujKRPFSMQXcp92#H$umJYF!Mr+N>iai3=FJLmRn*@F+!9XD(aVsViL$S zkPH@S1~!lc0|P@)YH?{!3CvEI4rD%z4Ws?DkyU|2VSEOJ`&^5P@{3^NAT=-ywH7AD z0p(|=q~?`mmQ+Hy3?Stga@gYqW|Rv914A4G0|TQS)DVS`#O%~!g+zto%)IoRRE5N% z^wQkayb^`PycC7RobtrVVuhmAlG38QVuj3-Vuj4Sg3=O&vc#OyR6Q;(Mn13Y{|df#>p0M@68li4LM4^G6p`HO;6rBMI6Ob78I6;p(m}+kZ28Lh; M1_oG)1epNC0R5qU1ONa4 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..078b07ab2da9713cbb890df83ce6ad86894980eb GIT binary patch literal 73 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%0D*e$sX3=FRg85rJy&Tj#wb3+D( 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 AjsO4v literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e14fb7a3e91392f5c1eaf21a6baeed353c0c2e0d GIT binary patch literal 273 zcmd;k)sxA94puC^_WwcEt`inW5{3*645kbW40Q|)3{4CS3=FIX5D-mow*$g?sq#IiClbV4!As{qsiC6fRE literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache new file mode 100644 index 0000000000000000000000000000000000000000..281c38cd7de24f5f0bbd6567402039d0c2fe1cdb GIT binary patch literal 96375 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..fe8d921b8f3db76f908f73544a1c9dd1360defd3 GIT binary patch literal 9274 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 Dpnh8DhFZ0>q683Pq^}MXAN9c_qaPi3(+j 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ϙl~TF7IVwVCaXsb`c8$!xAWaJv#%#MyShmxfmGq3A-FpW5Zmo z$;`l@&y3UMunG-zgafAe6ek124NjbzVG-t92#YXS{RHy13=;!`9Mr2yObiSvPq z14Aa%+f^(K3^h=%cCs)qbVJ$u*%=rPLcMLv#lT>X+uN`z4>}kFbGjA_1A`$8PN&0k zL3>azUB_4%7|ycd)CEht@D?ykUmQCFLpnP|A1qX`q!W}niXW7)7#J9ep%V_EFnh(w z!0;Y42Eo9<@C7y=!N9;^&dk7I0}V4%76t|@XgImBFfe#R*?zE*TLuP($E*wtFF^fW z1_p+ou)Z$?149uz14B79TwB>07`mYBepr_Uq?eO{;RPooqcL+aFmOP_o|g+cHi$HC Y4K4vd4hQ+gGp_^|9*(dUEJz%R0froS(*OVf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..70bd1d3200f2301f2c20ed767c21411aab018cea GIT binary patch literal 629 zcmd;k)sxA94*n)}U-CJBwqYWYgckz?LmC4ELly%ALp1{fLpK8h!vqEf29TP$3=9m5 z7#J89GcYi0V_;y|&A`C0mw|y{9|HpeJ0k;w93umR5+ehH5hDYG86yLOGa~~-JR<`` zIwJ!^HX{Q=1tSB)6h;PyIgAVp3m6#~)-p0MoMdERxX8%BaFvmP;SVDN12+=`g9H-; zgDMjPgAo%0gBcS8g9Q@8 z28McO28Kpv28JeP28Pqj3=HR)85r&}GcY`6W?=Zh%)lVd!oVQU!oZ-+!oU#5!oZNi z!oX0=!oV<%g@Iuy3j@PS76yjZEDQ{5SQr=>Ss554SQ!{(SQ!{}SQ!`$SQ!{>Ss56D zSQ!{HSQ!{{SQ!{LvobL3W@TX5&&t5S$i~1R$Hu^*z{bF!$i~2s%*Mcw&c?t{&c?t{ z&Bnmc!N$NamyLm8AsYk3T{Z@Wr)&%iFWDFvzOgYdaI-To@Ut^8c(XGw1hX?RgtId+ zOlN0cSjNu4u$!HM;WRq~!+CZFhKuYB442p$7?d~|81y+97>qd>7#cYk7&db-Fl^^w zU^vdfz;K#_f#C)R1H)Sm28NFu3=Gzs3=Gbk3=Hm^3=AQh3=El^3=FxP3=G>j85s6+ kGB6zGWMB~GVqj3=Vqmc3Vqgg1Vqgg2Vqge^Vpwtl01)L+Hvj+t literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache new file mode 100644 index 0000000000000000000000000000000000000000..a80cc49e2cc3284763cb7f11617505d4346e713b GIT binary patch literal 3323 zcmd;LfPnOz)WlrKLLi}H#U5*6}N%N0PetpGAcp(G zN)(he40RNYG!<&>6cGN_NXyg6(^RNd$Wzc#FjA=1RIs&$s5H?8o597!$O!Tm11LH{ z448L7d>9)!AgpK9mm%DUduU1VA(h z<5CMN4%0G=is6MUNCT<_W`KiY8)QKx0|UcM1_lPkL=X=-z(D~9HWn7Z3Z$etuuhaD zXNWyiU=G3NYb9uifII}k%4mF;JXDwgRG5G*hWZ6uhGIxkr3htVW?*n2!r&2 z#6cKctrk=*HgjQeAblVVGZ$8S6_+O#fXoJAkUWYw?sWW%k%2*;iGhJJAEXgE9b+pv zpmkZX0?4+4)D$A}IY=c+dREpj(orx(tC~O|1H&MPU<&~?Xy|}^2g2%5J~p*5IgmOK zhUo>F4Z;B+0*W;m7#MUJ7#M6B7#JKG7#RE*7#MOH7#K@N0}KIPBJqv++=28xXaAI@QRs%L7jzxL7#^HVP(8Dhm4RUg zD+9wWD24_jIIKWy{fzwFRQ0@DFaACov50v0RF3L+Shgnq5%D~XXic14Dqaez8m^N-^1_lvkT-uO}5SR{7CBVuA K(E)M;G6n!-7UfF- literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..8a631a8d2da920ecd57badcdeb017e678d2f13be GIT binary patch literal 369 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6f85ffb5147b7e2ab92449838f0ddf9be283dc3e GIT binary patch literal 1699 zcmd;k)sx8p1B_4x7nDxVNlnbv&&ezB;l3@2F`7@n{)FuY)8VEDnx!0?Bafq|2afgzBMfgzHOfgzTSfgy{H zfng0B1H%?J28JDM3=F5(7#NN>$-%&2$icv1%E7?k z#KFLj&%wY@&cVP?&B4I1mxF=fEC&O_9S#PDzZ?tp2-1 zHghsCuyHXk=x{MG7;rH#G;lF6oaAC)ILpPraF>gL;UO0T!#gep1|@C=1}$y|20d;D z25W8xhH7pGhGuRChIVcShAG?(4A;0B818X1Fg)UBV0h2Xz@W>+z+lS5z+lP4z~IWm zz!1yBz|hFUz%Z4Efng~R1H(=p28K&K3=G$J7#MEwFfiQYVPMeVWni%9WnggTWnc*A zWnc*BWnf6*WngIKWnk##Wnh@d%fK*?mx19hF9XAAUIvEqybKJSd<+bdd<+a4d<+Z@ zd<+aOd<+b3d<+ckd<+Z=_!t;A^D!`N=VM?v&d0!Tnva3u1|I_hJ3j*hKR*M5Fh2u> z0zU&oFh2uBG(Q7FJU;`&GJXbzZTt)jNBJ2T?(;J+JmzO$c*@Vf@Qj~z+fTB zz~C#$zz{FUz>qA+z>q4)z>p@$z_3-2fnld01H(x{28OeO3=Fpf85sTxGBB_UF)(lo zF)&C7F)(-tF)#!OF))M(F)$;z%3^HO24BBE040d7+4B=u74AEi?46$Mi z3~^!%43ote7~YC8FldT1FzAXiFxZMSFgS`cF!+fxFjR{(Ff@xZFtm#^Fia6=U^phu zz;I5Sf#H%k1A~wR1B0Uk1B0^!1A~hM1H&W<28Krx3=GDS3=Bn*3=CzG3=D0O3=Cb8 z3=GpH85pieGBA9WWMKF$$-wYml7YcZih&_Rih&_Zih&_pih*Ib6a&KLOPYb1Q;kXO~!&?~!hL17~ z42-f246L#Y3_`LD3?8x!3<0tX3?Z@%42iM~4E?eU4AW&97-q{dFg%iFVE6=5E62c~ zB*(y@Cda^_A;-X=DaXK&Eyuu+FUP=8FUP>pEXTkwL5_jpgd79I1vv(WD{>4BJn{?- zcJd4i4)P2Pj`9o)Q{@>Lp2#yW@F_4b2q`cyq$w~kG%7GKv??$#TvA|QU{YjYU{hpZ z2vcNWh*D%=$Wmlr$Wvrss8(cP=uu=~IIGCOa7U4W;fW#x!wW?QhOdeY44g^~430_+ z3;{|E3=v8U3^7U!44Fy{47ExO42P5$7@jFHFuYS@VEClOz`&-=z#ym0z~HFNz!0j; cz>uoUz|f$~z%XB#fnl*S1H)1%hE_rh0CYjPmf(U4Kq+i?U>JgA8D8@kS3*rya8xKxEm6oUR!CGx&d&)pjaUzu`HEKAtygQ zu_&`7BR4Zyp(HamRUtVaWO7AjNu?ea7c11kA^F*BU22n%7q%p90Jl!hsTMO0`>nsa_iYC(Qx9ypPJlpz}r zbAnTTKHMmfJjfX!wJ`aRqS90lAB1E)P$Ai88G!#xRd3djNw4Z<*sL2QsX%mx?@^Ap%qn1RR%2c{54 zqiBG60X4xe!ps3>5C#Tus8*P0ab^`fc0tZU5y!~&Fh`d&Gce3%W?*1k3pGx`J+-75 zoE`H@LD?9Ti(wf$GfyEAl=ndyIV2;qSRt)650d$dO7n^pGV>IY^Ye;J67x#Hxe=sH zSy{ogA`z4+i@CTI5)vSJoJ%3G7?h_K@>0t+G`SQM6l(0C*<8aAjc)|wgFFaQYHO=t z1U8C`i-D1mk&%G`baSDrNy9t#hKV9Ni#6Km1baIya3WiX<`F42+}f(ic3K0jSH0C zz{w3HT%4MmpO->JqC*4#dQwA6XP~49Not^i2tA$ADTVpMVg?#~SgR|H(1#TTSc)&& z83;=xDMk4OFxP@I9I`mxRE46D0ZWSvWE#k1GZ6-cHW3B}#%mxR_Cy6rstSn;>6vAz zd7#vyP@Y+mp^%u91Fpuv&Ck@llA_GiVujR-$t;df%`GUYgmM``wqnR(B!5te1kzEKusw_1RN9_aB4>E>MjT=K)!UIJQ2!rAl6nP*FmjnqSVOZM}95%2f14>B+Q%q^T zfV%)@OG#ojO2LOLj*?`tBvP1KQ2c=$h%?dN6J=n~6Juat{0LHpFVR90BdBqsqmZAM zQ>l=dmz-0Y0!qY*d6n>#Tb_}boS^{fwq@pV!J9;|_6(&d720P9c?*#&K}~0PG6aPh zq|u|JV2TjM=(ic8r8-c80AWyKfCdGaqBL49VB`;XT3eo`53@J2mCN>*(28LR8l*E>inw*Vj9VLR27h)s;)}V&e zr+7;`lrdP4O43Jtu-A$S2?^LnjR@DZus{WmCef*GRfZ)tP=tUmTmTe1NEmD4Ln+o^ zox0pal**&1Qa?T8tQ#8MQM&UfyFz>a1aKW3bF)*VUi#= z2%EtagA_4P-rjMJcH$qv$9Sm~%)VM}gcTB*DOtBmrWfj;X@rK{6mrX=u5i z1pvh@PPl=f+%a@q3yYQ#*yI!_aKWhu)EEGDg)nk4iWX3w|Ifg{02==WX~l+N8c^C# z=(QY3V>&!iz~kv4A!JOHIiR42n*(d(L#FOvW`UHT%7BY8kT6II$V(tPGp{5cM8o*7 ziVh<{U>f^b85pj!GB7Z%0%-#kYo6eqH)xm}Qhk9M#QCt^AgD;f*%qQgT}HtS2FyoL ze^8t`6krhxG8ly60#F+$&CYQ1VC`UNX$& zfhV@zL38;E`9%t@MLCIi=@2Wi&4>}%3xu|i| z-cv~i20JPIjW_VL9W+DWpUeZz+aY?D&^&`R0q3WamWGjog@}2VSWV7 z2MRtAhJ`h_ii1w+Vwcr8Gy}{B>ej0d<0By<|u;0lpWT3H+qeg{! zAL?m}Yg7knrv%y=2y1zR`tdODgMtG^9A|o2ro_PTK#75Yu^pOw6r2-tazKr7_DVl!6x!h8Cx$mF9p3-xUgq@>5Ea zLBkM<3dNZzsS2rSX{pI2V7b(?)S^m-%#zexP%{y1Hl)-_g|zZOqN$KIh~VN4WLIWM zYEdGnDF|NMmo4#1bxr;*9*#oD}fnQ&OrzQEE;mXlAJ(KM!OJ zuHmnoRPbsB1zYfh7IX|%2{f3Js-&Z!l$4m4n3o7*C1<1-6;&$5Y9fk2P*iAuCoD4a z^$LnI^Gb5^G(jywzswvi1$})5urXj3#2hdaVi5MC4HTiU$OX}q)_l>h^bLx25C$0v zvIK--k{~t+$HEkY6d_?)*#)k(V8s>kIyi2q;>_asl+5BRjbFoN+OF)==U?~_> zae%~dCgPcL3=9wD7#J8IK`m53no!P6Qz$9V2Q@p3VFRWLdHJB#r@8rguxF<% z0akc2$j?cE=>jDmWN}7N@&jRz0+>X88DugO#s`f|qG&A2&&8UqUF1>^7FvOWH4wICukxFv_=oy=S@ukt)&K)bKtVH7(7h`k}U$yYo?}v3*o#clUR~kM5upFATxn-G_*)H1UC{b;S*44`8g>X20EYtLze02Kfw6%Zd}35W(^m>h@= z!Z6JsHVC83!}zcgFS9tlxHJj68v$l8h9pYi2{IO>GCvLEAedTkQ4Q)~z@#N*85m+^ zk=u)?MJKp62Gy}?r8zm3umTj4SQ7K{K}&V;)uM>P6XJf9q7!XlAE<>#sKmr#3eLr- z^eis}p^*;?aWJMdd0`j`HWg_FJ~$>}B^ye42}|inJ!p^+2!jj(3p0We1qg#gV5UIU zsxvTXF)=WB;M~Ik(gngGoiL2ex3CZaJ9JPl*2m`yjEsX4j1Zlmyad9qjRw$F?jT7l zszJio!iyZ6z^my&2BJqHE-`Et521*Hr8^is!n_TNRS=#H^FIRvgB$|`g8>5rg9`%# zgDV3ALpK8h!*&J+2GIVgD+~+_4;UC29x^a6fM%+g7#SFt85tPF85tN<7#SF}7#SF} z85tPt85tM?85tOY7#SEQFfuSqVPs%<%E-XL#>Bt?+R&!K#K7Rm#K7Rq#K7Rg#K4fr z#J~U=?CoY^V3^Cqz%ZYQfnfm?1H(xs28Odt3=H3x7#Mzm^fEIr@G&zmNHa4q=rA)d z*fTRQ1Tix(Brr2DWHU1`6f-k0fHt+vV`gC3!py+1jhTUAJ2L~rb7lsH*UStIznK{r z{xdT$@USp2XtFRc7_u-hn6fZ1II%D=6tXZdRI)HIfEJUkWMN>~!NS0>i-mzuA2J8$B7VHcRpd}p*>!NAbT z!N4$;gMncM2Lr=y4hDt`91INCI2ai2aWF7|R%GgOGB8ANGB8AOGB8AQGB8ZzWMG)Z z$-wZ6lYv2qi-AFmi-EzGi-EzBi-7@Dv4nClFeGs?FihfNV3@@PnajDz#lY~Ki-F-K z7X!m9E(Qh#ZUzPwZUzQFZU%-RZU%-VZU%-lZU%;8ZU%-9ZU%;V+zbqhxEUA@a5FGm z=4N1c!p*?I!Nb5H$HTy&#>2n>8o5m7VPGiWVPM$6!@zK!hk@Z54+Fy^9tMUdJPZs^ zc^DYXco`V%c^Md-c^Mcc@iH*n;$>iv<6~e@;$vVi;$vVi<6~fO=3`(;=3`)3#>c?0 ziI0I{7as!ys9k=VkAdL{9|OZrJ_ZIweg*~|eg=jbeg=j*eg=kmeg=k9{0t0l_!$^J z@G~$-2{16o2{16|2{14i2{16&3otN*2rw{22rw{o2rw|L6JTK2FTlWXS%87zwg3YI zXo2E80R{$EK?VjrK?Vj#K?a64K?a5nK?a6SK?a6vf(#6w1sNE=3oQL>L&fL>L&1L>L%Ai|ac@7#I$S zFfbevVPH5c!oVOb%D|v1%D|v0%D@mT%D@mV%D|8=%D|8<%D_+|%D^x~l!0NRCU-B>(~s%7#LE-7#PyT7#PyU7#LQIF)*wbV_>)`#=vk_jDg{;7z4vc zF$RWzVhjup;tUMI;tUMY;tUL+!Ns-W3=DV085sB_7#IX47#Kin5mF@>80JbaFzk|G zV7MW{z;H)`f#IG61H*j@1_lF31_l#J28K9E28JX_28JR@28J?828L!y28J_|3=B6V z85kZ)GBAKd0|TEl z1B0A21A~$@1B1Ra14E@W14ElM14ExQ1H&X~28N~53=9XP85r(LGcbIRW?+z(VPKGz zVPKGxVPFWAVPJ@qVPMFVVPMFWVPL3|VPM!K!@#ghhJj(931A~}61B19c14Ftz14F4i14E@e14E}g14FMo1H&wN z28PG-3=Ci785r0V7#R2!7#Kj?!3-1_7(5ji7*Z4%7|InG7+Mt=7}hB;Fx*#QV0f&+ z!0=Rof#I0~1B13A1B00&1A~er~FMT1I|aT5e)qo_=~!ey)BhbPJ(=MoCFQJa~dB5xnuR zSU;&WGbcsAATc>RF+H_dAKWsG&o3#!E&Wth)PpT1 zhL0$~w-RHttl;{g43vQ^SepWU?FCYu3)P0OAM7evTMu~z46YK)0M*H$JCQekLhGF;=rWCQe3e->qsh%m#z_3`FfdOQ7r2+#(Gid7}0|Nt0 z2iQyCW;;j)NJn03IZX33CI*I?P|ge<28P*Chhx|eTR{R@PY>0>@REap;XMb$?j0fw z4EsQvhOpWViww}H04xHG`573@`9UtivKI-a0W<^))38>FfnlQ(#5$ZVgsgsn`E?pA z1H&9vh!$yK1_ni8$j()E2?hpU2?!fAq+phVcE7?*TFT78uoB8Clwe>e1+Uv;VCa-! zVCaRiF&qUm2;^-T`zRj+!%04flO72%FgydT4FqkH7iVC244Uy_U|@Kv#K7~27lwn{Hlz{{e zhFP#>vY?gjpsho&$Z26=VCZ3i7>1zx6)xaGH;^fyU>B8OV33l4xafp91H)Np1ja})FeKt~9k%gukYNlA3=8BK7*@+c z%!647&cQGX{W5c4!S`N?f#DO*L;};0j@Y?Bm7RfM1~e@|lM3W03~=!P52(cKRG1Yu zpoW7O#BDeOIXJZhYAATbL6-^Q{9Yyoh6$imBn%7;cUT!1?t>PKfhrqL1_mS0)*}W6 zh8sK#47Wkk;tUK7&VmdKZlK*H3=9m$Vhjvsphf!FwkhJ>zYTFKgOmaTgPsD!A(&pp zo*aDy85n{DAqHT07MAQ`>pWl~ST z3=C(XSqi2R90M@Fg3c*{`E{WL1H)2i1YlST(*W943v;ur2m^zo2qbVmNi#5f1MNM> zmh@q!!Dk3yRvPj!Fj(C$lL5FEDFfdGFVPKdFE!UT`GBB)y7CC2F85qt(*&*x<4B?4h7qb$b2paw1W%ETEM zDxtMWzc>TKBq)2nI0M5XC|gN_fk71-edZDj3|3IKrvw9o50o7y!N3p+WwS{!FmOU^ zB}FL)1{EmVSc-wc49eat&A_k?8mkwi85k}@*^i_d7@k7ehO!I{CeX6OQ)dW!oq)FxW%OlyC(GhA1dIQ-OgY2g=^A#K5o%TAQJyTUc7~%!406 zGXr*j3aGIHO&^f71Ilf%bc=kJ45)!7!@vMu8f(JDz+eGQJy>n>%glkLchDIg_dqMY z85kJu!j@Ptpmi8v7J_;jup|xII)4kQ@emgS!!c-ba^YcM@PH<#ED;8VJP}CL3yCo> zh(V*?k%@u9jfsH)WG;#WK>{G}f~E;z5{qD)uo)N_Dq-7=7#J8d1Q{4~1R;h;!X}46 MzJSfKgA9UV0QRq9Gynhq literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..47d373c6c2ac84dd3c607e0b8022db65823b2748 GIT binary patch literal 1773 zcmd;k)sx8p1I$nc7nDxVNlnbvPsvO!0SU-5FfgFXD%)s!QnStRyGXnz;3j>2D z3j>283j>2G3j>1_3j;$T3j;$X3j;$f3j@PS76yhLEDQ|0SQr>~voJ8+V_{%;#KOS9 z$I8GU#LB>+#LB>+#>&88%*w#v!pgu9&dR`$#mc}?!OFnU%*w#f&C0;g&&t4XiMT7{WLh7@{~B7_vAR81gt67^*oK7&a5FH><7Qx3#Ld8PfSZBg zGB*Rm6K)0u4ju*uIUWWEH68{AEglAjWF7{F0v-m24Ll4C=Xn?yuJJH1JmO(sc*4WL z@RWyv!Hk!I!Je0a!I_tVVG=I`!!2G0201cQ1|vQO1~Wbe24_A7hGaelhGl#V z44e2E7f7#NHM7#QpY7#KnX7#Jc17#KPP7#P+GFfi;FU|_f`z`$@@ zfPvw@00YB20R{$EK?VjrK?Vj#K?a64K?a5nK?a6SK?a6vf(#6w1sNE=3o*<7lauYJ_|E2aEdT6NQp2oXo)Z|7>O`2n29hj zbc!%A91vk(I3&Wra9D(aL0FW5K~TU`P{VU`Q8ZU|222z_4D7f#IeY1H)Z0 z28Op{3=AK|7#RMEF)%oYGcW{;GcZJpGcd%9Gcc?bXJEK1&cMJg!N4FW!N4FS!N8Cz z!N4$Af`MU|1Ovkj2?mBc5)2IYBp4X(OE54PNHQ>(NHQ?QNir}bNir}LNir~$Nir}r zOENH=kz`=FDapX_P?CY+sU!o#FG&UlVJQX%Jt+nTM=1t|LMaA@g;ER*i=`MCmPj!$ ze3D{d;F4xw;FD%xkdtO$P?Ba~(3fUlsFY@4Xp?4O=#yq(m?X`>uvD6X;ea#)!(C|x zh7ZyV4AL?T46-r|4019I452a%43RPn44Epxzz%W6Ufnky?1H)ul28I)|3=C&v85r)!GB7-lWng$K%fMhD z$G~7A$G~7C$H3qz$H34l$G|XEj)7sO90S7>IR*w1c?JeCc?Je?c?O1bc?O13c?O0` zc?O0~c?O1Fc?O1A@(c`*4zsR9GTGX(|)ZAAtKGerglD@6u|Dn$l{IzFBKUW z-YPOM{8eONU{qpY;8S8?a8+Vp@Kj=8C{$u#s8eEK=uu)|n5D$Puth-1o9k628%QU8%TnIfgvch zxHP8(W+zMsG9Si<(f--Usz9PJJ_Eviu0=)pMKE!Y8W@IJi!&@idO&JH>exX90|SFk zYGMjBA%I0Vz$61hNKs;D4zh$fR05_Tvp7CAx1gjFD$D>f7()(QRD)ar(zJw;f#C!r z0|R3_h{wRdpx}~Pl3J9TnU`9ukeQ~CsE`m0PNWG6nZ*iVyY#rY6qJ<}Tq_cD3vyD6 zxwsS(5)#1anM)zDxHz?_L;>b34J9Q_urxMBiU?ID8L0~8`9(P?aOI4QU>AZ27=}6! zg@UCPu=TJQhd3BT9Wn<+4=fELr%?t$sPdBh`1GR0f{fJM)Z+M@{QLrtQ$TW{V8*Ek zo{7_Mk7}QZE`SGPCY4ORhvKce&;#Y|^L}3YZz5@dTvm+!#K_Lml zFnOpO;1o&#!HOoQ%o4|DYpy(nwalr~aY!wvD1_Vt@yF{SYq~ynA#0n_n zK+>R)!>0~wbig#rIWRCpJ77i!OdezbGDh(%Icbl|ks%CqTv}0nE?TOE%7E)Me9B<4 z4K9eF#VySH4z3Igb*>ByjFX@u3eNd?WvN9a#h{uER9Yn{G~j^KBy@a4{i(DDJ15la1l}; znwJU6p~@hA#RZAUsqu+SzxG2#-hqn6JGmciA#;> zL>+}B9fjmrO$A$91*OEKWF=_B08}(HGNMExw23MMO#vvbj?c*~E&*jGNZXkamNNA8 z^uY-Ls#h;JtC%SLpsWMMQ0q`Al>9>Fw2xvDQqvOb4;*l%R*^ADC_nQ3i%0Q3eJ^K9Eju^ROttpjaU}Kd&S; zucR21c|eV~w9KO75=2w6BqOmzAwM~}v`7JBuL4L>acXjYUJ6{59+!e=8dxF$1<0M(OO%%xD2T2fk+mzttbnwOlBn3tZKf-hS@ zo2Z}w)BxFQrJxj&k*eU6pO>oOUYeJinx~`#b+wg(5=dAHl4e0nm!5ok#cVzmOuYOpjkvE}Eb5|J1bA-cg1 z*H9|SNX;**RMJsU0#UFI4DNDD28@tJuksTH6C1(s}cQuESFGC=xawt;dcNEfzb0yD8&fPvwb z00RT#VUSKx;S`dXoeHYii%XLrk)e>DS(ciokf;C-Wd&GoP66x!P&KWPr~t8CPr)xW zJ+UOSEEOV|TFj-8Sd^+zl9-*E2hpCHm#zSE7dU-srRJq*!HPeKWuSx>l95@gkXD)p z9;Z<#D$Ofa$jnp7$;?YlEK(@R%uPi~p_x^wFar?oDal9$Rm!07z;qJWMNq>KDILX} zg3=OLL7JJzrI47Ck_m1%l;ne*oe$1G$@#@4c-s!pLJ}Okuu>9SEh*_Jz#`B}!B7Vp zcvcEVkTNnS6;`cd4hj%&n4tm8w8UKU%`-r_&KP1I*=Fe)A5Rt4Di`-aXz*i-}#th-E1Njk#p}s?*V5KcMTyO+9iU#ynCakyx^@Knc zp(_UQiLwN3Bok&9DAk}zF)%P(GGSo2iDT>r>t^N^ zr{)!Bf@`Vd{M>@XqRis_JYq_6xUm{a5xQ=kKCZ#gR&9lDT4qjaF-rNv2un(kdJQ9x zh)Kh63qgSg!_Z(up@>ReC^`^>@c7a_%nykL1*v%{Fato5jw*xFTLMXgl$PX!NSMOn z(zLWpaDxR~>VWBqRApe8smj2>Xa*I*l{%6t6|(X(^FYmslJb0bssRlmt-L6Ow3Ec*4WTdNGdJi0;j^%ip1m+g{0Js#Inr% z(jtZO%$yvBlvGgIfW{Ou^2@=>ic0gqt(40AQgCCVv>4>f{DP9q+{|K7o2U|`88iZ+ zr{I*DmzQr_I3NqXR0r zQ73?4@*p+H7$q2}R+}N)0Ai!qf+G+>>X0$5^uVl7&d*CugwMNzk_nm&wz3LT5Q7|f zRgHmxS)GA_@dk(oZZu)9$&kvbTu{D()mr!pD`*h~tN##MKv@<`mk5;YaSdWZJ0cLz zYD6pLrIw`@f!b9i8L1#DC$+d(2|B0K1(giNz6;FuDOOO3$%ED<5CN2?9Nzv0y1zgvABLfhN1-T=Em#SKR)4_E1rO=LYy>4UR0({c z9_Y=$(BjR&z^DiE03_5wqr%1Da0VNU82w4kDJ?EZEdsU26QLtSutEyllqoMtEGSmU zECDOvQb>R-A<=-iT1UYzGe;MfzSX97iSbR{N#UUXIiek9gNDPXtfwdNpmJ2a3a6xTIPAteQNzBPa?FnL$#h2bn z{23VL`xDz01hq23v4BW}(0L8iz91K9&I2?(32iYK6hOMH;8Y1Rq$o8v5z=!&^eRj8 zQ9F$UQV+uW8cMl|x%s7eiI5bb2}(Gy*g=tp z34#+SwEYfC%pWWn7=*1D7#P(++CfD`FsSYXjTh(T=jnpiHo?183YmE&`3jjO#R`yk zOU+5mP0cG&(8x$lOwk0#M^S2ViGoH+VrGt}9+v{Rzm}6)QVi~N6eNOL{CNtwi6zMy zp#BxCd?{7{cf@ik6%rvT%kxWfQXm5cp!Nu?FCT#D9C3jRPc2FXEx<|yO^hWb<)kX) zm!%eg+yd(Uz`8O>L(e)2#ra^DfVw}R$uwveh)bcgI2BYtWr9Y$- za%7s~5)oGOq87ZMauS*ukZl9Asb(cwz6KkPM4;FMau7%krQqfTi7+rQ6oK1O@dZVx zX_>Ib6?k7X(K7BAqm1T8yYfio$Bf#FI70|TQD zNF}K3fHmqsNgOiC1aTK)h6pm{l#jfIQU^39Qc#pyoSIhxYU;t}zac@CfSCG*=!H-5 zpjZI15t8IUB?6?Ok($Cq((E@%P--YS

D)fO3ojBoios`Ota*GvA=;&PdJ4$yY?y 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

L;+hpyk%Qql9<1tQM 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 I$`&9D06L@;1^@s6 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..7a1de9f3ff1f492704abc57bd6ac57fe7be3aa2c GIT binary patch literal 4017 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 P*hQFu!3)X;`5lA-6H-37 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..3b483a7c16f4f18c9e9ec8d0452fb155653a43ae GIT binary patch literal 891 zcmd;k)sx8p1B_4x7nDxVNlnbv&&ez<0SO2)FffR`?^E8at8wiNNC1k37#J9Y85kJi z85kJWGcYjxU|?WyV`N~M!pOjIlaYbpE+Yej4if`I1QP>83=;#xS|$dDKTHe^KFkaZ zvzQqe)-f|MY+`0$IL6GtaGaTeL6n7oL6e1nL6?Pr!Hb1~A(n-KA)bYSA%TT~VJ-^; z!$KAYhCM6{3

k7`Ry(7))3h7|d817|dB27z$Y#7)n_g7$&eXFic@(V7SN1z`)MN zz`)7Iz`(`Ez~I8hz~I5gz>v(wz>vPUUD-q$nY>QDDW^ac=IqYH1RMntl?o`Sj)q}@Pmhe;SUc3gEKD!gEub& zLoqJ{Lp?78LnAK(LlZ9p!wFsnh8w&L40m`L7+Coj80`5N7z+3p7z+6q7`F21H(#w28Ij#3=E6{3=A9s3=HA|3=BE~3=DPx z3=9DR3=Ba63=F{n3=F*j3=9VZ7#I!;Ffd#aU|_f*z`$@zfPvw*00V=ZAOnMvAOk~$ zAOpifK?a6{f(#6Y1sNEQ2r@9d6l7p95@KL57Ghwi7Ghwi7h+)8EX2U@Nr-`gNtl5_ zQka3kP?&+iN0@;jMwo#iR+xcdnJ@#xW?=?~?ZONUY$6N{;UWwSZ6XW|T_Ow&vqTse z=7}&ctQKKlcp}2U@J57z;e!YR!(R~w20Kv(1~*X#1}{+thI~;5hUKCR3>!ol7&eJA zFl-iOU|n{V3;n(z%XBofnl*2 p1H%R}28J7A3=9v%7#N<2F);9mGcZ_)GcW{+GcbfeF*MsS00380cH;m5 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache new file mode 100644 index 0000000000000000000000000000000000000000..e6241ad90f89aaa2600ad750e0ac4f042e0debe7 GIT binary patch literal 37709 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..2ad6809253ad37b26f9e6517dd40499b96b90af0 GIT binary patch literal 3269 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_version b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_version new file mode 100644 index 0000000..71bd5d9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleam_version @@ -0,0 +1 @@ +1.16.0 \ No newline at end of file diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache new file mode 100644 index 0000000000000000000000000000000000000000..97592f1685ae8806b2a4bac1626139847cdcac5e GIT binary patch literal 9349 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 LDkuhpF9-twD!D&v literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache new file mode 100644 index 0000000000000000000000000000000000000000..59e605c1b6a86595208f906699d88da605a53f47 GIT binary patch literal 15806 zcmb1TfPnOz)YQ_v%o6>~ypq(Syu=*+^qkbh-1vgTyv$^f5)Kf-z`y`g$O`52L21{D zf}+&o;>`R!@65auCXnb0CI*K8ObiS}+X2!9!eHI#77}F&cGrN+L|`E()3LZXwWuUD z1>thAy=_bk3=8S&YTPb@Iu%NBLg@fdq`-rD8v_Hw2?n|bGloN;4ueu$P#O}#aEH!g zWMJ6HNLPnqx&-PpC}z(6>uf;59L z$RgaTnL*N6Fw7m`(g&uP=zGc+|X1*{1a5Dc^^Y#3R< z<})y4XM(JP+3S~?0~Kcg1rrE^_*kSF@Rw#FoiJVOAR`zU7<@8IQi~FEKvGZ(eB_}h97c2uV&=Yg@Q!4Wkb2F1+=D0w(P$diu@K7&H%qdM}V1T7oMA=Dm zE4iVL3C&9dIU=zn6%v0SVVH9e*%%}W!XRgXFo=z*3YM8bvd9=E6~Pn*mlS2@rNfmm z!on&ywYWGjJryniV-RHsERCdP=8$Ou%z3%_DW&9^0ZaaArFqFEnfZAzFTiOOzrYe5 zO16Y~BquWu;t}%m6|ATOm&c&m1MUp8ECsXEDL)@2Pl2swU|@jBhZKR*EYuX3SKJbF ziXkoqNx*VQXi1uLeoAUVekP*Kfu$diI#?P)RBa$p5C*A&g$yj?I%Vc17FGHeq!uNX zD) z2MdOx%=C;B1_oG+xIxpTb7Brgxdw732*b33Ll740KAFWOphy8J0J$8QO`^YFkOOJvC`MSSO@%ko85m&h#K>i!aDs&) zj1I|9$%lFZ(N+NKgp0z=^GPj%WDZ!Qz*RsQFmZ5X!SV!3Spri*ttuXBF_?mxjoWru z#aNJ70_qCIlj{mtg#|MQ*5Cj+1D3eKuAoMR3@bJuK@W>IkX6W-u;*ba$n`WVK9M~O za{=7L!QOBi5vAm70>g4#E!pg6>mgg|E9VPIegBDN#N0_q5Wtc95latShq ziQ|kGWW^x%Aksi_J_7j%6!heky|9!3E(wv-6(bY8@s8RC0NDq^plHO^N?-&@f-p!S zC>elgP$dkCMObZ*+zx<62)O11`DPm<1A`2)V;EqYKt_WZAiK~o%y@7DffY1p?Gum| zkj>ZHU>seE&^eYDX;_$>wJMU!Q>#FI#A%i z;uzFX1jP}I59)7$*jRd_AYqUNpkx7x1U&`@hE2qdYJuzkd5!_*6p%^C7-S-d4^9U# zMOaD&kQx{sGI~I90CF^Xx(3Arc_|5JRROYwf)t9Gyub!RQYNf=MD9buq(NT9CeO&g z2#R|U1}TNP8>u_Pz#z}Wzz{%eiNXv@Mj+!r7-S?mMoAU0L_09OH;~iFk7HO|V74aNc1A0SS=FNV9eEkoX19=EbL_Wn#0CQ4Xp}4>b1&o()9T zrk9&lj7=-Z4!60A3)PU-AkU=m< zz}tc#L69xbwqGbC1H&xhyNb-<1`T}R6l4%GhS>%Vewa<@btLUIfC7TD=!Jwk6&s4M zY7(<2$PAxb1Ixie2NZg+;DkwBVPIgmNo)g+1yrqqOd~%ua66h2*O(>9LJ$TyALp

Z4C8}%R;QlU+TnaUWM;CTyqNEH;Cu$&DQ zKg++Qp>~1C!qB7$HCkAh85op_ubCmOSXe@b*$b~u z!Nt&^YqTH+$YDtzvoaw+@!~cUUfsY_60EBT3O8gi5FZ)CiXm`t!fGb;o*YaZ(IW*J zfhr0TLB>QG409Q%MuTZU76TbbesJMT)`(P%Hh&9C(P+XTXMixwiFgAG)`~>#(t|Xk zi-W|FF;RxY0teRf2N{4Y2I3=Q6hlEJ4M-BD(uNhQu-HU*CQK8$ILIJmjA9eaFuElq z91({@80K!G+zbm|s^%z|q11~3FH*y@Bmwgv?c%) zMj$m{aah8pVy_QT3&5M^AnRbs4Mh+n0m3jzaM=RObSOC$CJ1fagES!tfw;(+uz9eE zgBb$Tf#N)@f-rkvlJs-)z{V;p#9;o%S;3H>uy7g)vlHHigbP3!u*iT7i@;_WQ}a?_ zF^okDsu@nfOu-$cFcsjzC|G=;2*M1bd;|byEH#rP&KX#cv$0BoB#<%8Rk*_l7U0y% zs<2?jH#z`vAuf5495N>C1DK7Vq7vpCWHFFwG&G7%SV(|E0UyTgPuheX z)Ge?GgVC_)hR+d$)S(E11ju(15!Et$$Od`t8e}W1`b8H9$$>D;nc&QZt6d3;57b1V|EuQ9KHB`~b~_ze0W_@5$iQIC$iU#w$iNWH$iOh0k%3_@BLf3ytH~Bd28Lsd3=DS|85n*s zGB8Mjh8vj}7y_9X7@C8Zt_IhVxT`23OrEE3?!82#d+ zWRNNd2W%ADJ}o_P0SQY7@Ns7YJwrVMs3Q>+Oc|`X1J(=M2LKZ!Y7Pq4!-ftI!VCtD z^>H&ZfO}&^nS*mY8RBTrkZ}?d3Fg6yTlk7pn9D%x=bkc@CG)1pXN@&4KCGcbf%oNy+1kt|3-3)*k30jS` zgA|9se2F|c3N!3EGXukCQqmZ#97kQ12Qv<|Zte&p2?+yMM}r3KVTQm)?uiaFSc?YI z5{4NAnz@T6#VxReg}j6Sl5;>y1q`5}1WU@W9FCIhVL2JP{RiedCKd(;PAJEXk%7S% z>PBg11_mXNHyIch9GDpx+@Wlu@&PO+5Y7cn$AN2T1_p+Y(7mG|6R$EcFg%9N01;(0 zEaQWdGP0@rp<5+ErY>P%U|0*CV1T84aA1HWLE+|?3Eyv^$il#&!UCCtLQxA16b9rr znmH2#gEiE-D2ie644JQnh9LtV3j>1))NG=BhdYbGZ0m<^VFmf*86yM34@RidnHU(v zp)RvwVqoxtvh$f37#g8$qMQIrYKSrn7H{&3;A#0eFI|BoQFVs1a3=9nMP(1hfFg;n*EFn3^Sp|f|4f)0|0l&{Zs$| literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@gleam_panic.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..12d1ee0b12da437eec859c1ed1c68259b5a6d61d GIT binary patch literal 278 zcmX@S!yTpHi8Zn46gl5))xyV3?aF_MZ8T(|se502GTdFffQQ zFfiyaFfdp#FfjNtFfb%CFff!dFfep8FfhzwU|?9!z`(GXfq`Kw0|Uc01_p-H3=9mH z7#J9CF)%PZXJBCX&cMLH%E-VV%gDfB%*ept&&a?K%*ennn~{NGE+YfOJVpkFEsP8d z#~2wH?l3Yi{9@RW&x;W-ln!wV(` s1}0_(1{G!o22W-NhHz#EhDc@xhA3tRh8fHZ3=fzY7}QuG80sAc0J3f*ssI20 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache new file mode 100644 index 0000000000000000000000000000000000000000..ab3727f612fba1bace1f9767f4e620e6804cf0a2 GIT binary patch literal 14905 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@internal@reporting.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..02e97a62dde6b743ff1194c95dc893f38db56c86 GIT binary patch literal 1277 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/logging/_gleam_artefacts/logging.cache b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/logging/_gleam_artefacts/logging.cache new file mode 100644 index 0000000000000000000000000000000000000000..92d2b96cf0d05ae587a5741d61c08b4913ac6d97 GIT binary patch literal 6118 zcmZQ)fPkF*^z_WUbPxwkh#4ftz`(!(rG4_#eNxL(bD&%XK?Vi}G%YYG7>y>4#>HhY zL^sTO@6<|01_p*476yhA7Q8lsl%ZLH#zis{VhN1ogfd+6{qjqEQWLX5W+gB%Fy!I) zBuE*W9cWw(lc6?2DVSUhH8-^=JvA@65^4qm11pr}n3Gym0%ap2&bcVF zBr`b?9&vExu0=)pMR4UXr-UaK2z$61hXi1uLeoAUVer6url^_p7r5Rw+ZQyCZ-V6h8Nf*^Gu4ATQ*!-Bv&6(0E@X%GfU!#swPPZ{_?QVa|X zFb|?eB*;h%8FvN-24Bpi&jC+5m~yb>11(Tsi59)oU}R;GfV!tRwIsfvC^I*)s4_k| zKQApaJw69iz`%S13P?g~(MuqZ9+2@C#OfxO1R2!b#Vf*hAxBm`2yz`%g%00xkL zko_PUs)_-`NAn#T7iT^4zOaVMl?sGadG*9kr|$-K_Lx}Td0BHaE3V^A&F)%$p0V>{45L%<}3^h zj0PYc0|SGCb4Frbda6Q6Myf(ie!4{Fr2^O%g_3-Qq*R6c(vpJG5Dvi%{wbWCy?`lo%Ko)UhQBxDzmHVj@G0kr5sYD3t`VD_|1g z3=9l0*jxd#3sVjz1Ip-`dFi;~j*%UnG%;#ZkV{}m6IoJ>fq_9D(?*yT;FN%7JsKCL z6r)T;l>U%%u(SwXErALmm}?M{Xaa5s~K-K#hR}JuWT`6M zotU1=rI48ik}gsxD9X=DO)dd>Fi)W*BeNK6f674vj#la#RXdT;MEd zKuH*+B_|)=2m_gkER2?dK++%`OPLrLZh%sdEr4lvIr{BS17taKLK%;F2QbMl47jGDgt@3m4?F z8e8)gqz+9yfPsM_1Y4m1a|fmzTFB!p6xbNJpe{}YXV>_g{B&5V0EHx`EP8-~RDq08 zVP;?m200#F4>H1>21DExO@Q8iGooA9p(xw5e4!k znjL6dSf0UXEW?8Utrr9GCCm(j6ey9qV8##!yjy`O2Qvf|c=#$97I?oIz54}n3M|Nw zqFO;{ZnrG#Jq=N8`c_#i(AG;B7gKa03N3$Z!yiW*iz9Ln}xdUJUXmNCC75 z4`LFw21yf00}2M|MixNl;|c(f9yH&eaq;N{>4rxpm_q>pvxovCz*<2m1p~}7?N2S z7&2KH7_wO)!#U+F3=H)w3=EA>4D(MRl%b!IpPQC6la>zN__L>m?ch7eGv3$sju6$SA256lUEObiU+Oc4DrS3*M=rU%~H zgXs}tVPKGF!Kw$dhJ!g&otc5b5bDsoObiUqK}{A+hr)6jteAz_2`g(sK?RF0a0&nk zfC9oVGY4kXFD3?tzfj{-Sr`~Hu!jk9y8vbltZ@J`6)iYmwt{{h{o65ct1*$3;Pfb`?A58UgAsfTs_v8V?LfdT@l&9R1wfnh!N$c6a^+;M}M4(q*v zjDjgZa}~@d@M;{S7Gy1``vV$!1@Xg~85j~k#S8-j!#QRKhU-u^npv>8hSh#BD?xUE zhN?kEZD(R&*bj|MOJ)WJN2pN;nHd;PLfPsp3=I0vfQOlZo;E;cft(r3#K4dMHSa2{ z<;=jqU;-Pkz)X@bt=_4XuxKxY4sC;^wlXs??0~ui#dcUJ0ckG5w4P#OV7LHvfDbbR MLl9Ie$Z;SH02Q`KW&i*H literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/logging/_gleam_artefacts/logging.cache_meta b/examples/http_interactions/build/packages/gleam_otp/build/lsp/erlang/logging/_gleam_artefacts/logging.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6c31ea6d4943d5e6f6c2a2a43622d25003c991ea GIT binary patch literal 249 zcmea(ElFcQ2Qy5QHb2btSz&}EA;Q4GpuoVupuxbvV9db4V9mh5;L585YNECkifvekk7!tP{6>zu$zH_;VlCL!$$@N1_4F}1`$REhImE>hV_gL4F4Dz z7(|&E7$lh(7~Gi{7!sKn7?PM67#1=yFf3(aV7Sf1!0>~Kf#Dw$0|Nsy0|O&71A_`P z14AM+149xs1H%+%28J8V3=B7!85p=(7#R3j7#OTr7#NaS7#K2H7#Oly7#MO`7#PY~ P7#QkV7#JF%80IAapxqkf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam.lock b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam.lock new file mode 100644 index 0000000..e69de29 diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/LICENSE b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/LICENSE new file mode 100644 index 0000000..59e1345 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright {{copyright_year}}, {{author_name}} <{{author_email}}>. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/README.md b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/README.md new file mode 100644 index 0000000..02fa9f9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/README.md @@ -0,0 +1,25 @@ +# Gleam Erlang 🐙 + +Types and functions for programs running on Erlang! + +[![Package Version](https://img.shields.io/hexpm/v/gleam_erlang)](https://hex.pm/packages/gleam_erlang) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_erlang/) + + +```sh +gleam add gleam_erlang@1 +``` +```gleam +import gleam/io +import gleam/erlang/process + +pub fn main() { + process.spawn(fn() { + io.println("Hello from another process running concurrently!") + }) +} +``` + +Documentation can be found at . + +This library requires OTP 27.0 or higher. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/gleam.toml b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/gleam.toml new file mode 100644 index 0000000..f1c49a1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/gleam.toml @@ -0,0 +1,18 @@ +name = "gleam_erlang" + +version = "1.3.0" +licences = ["Apache-2.0"] +description = "Types and functions for programs running on Erlang!" + +repository = { type = "github", user = "gleam-lang", repo = "erlang" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] +gleam = ">= 1.11.0" + +[dependencies] +gleam_stdlib = ">= 0.53.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.3.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl new file mode 100644 index 0000000..61b6542 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Failover.hrl @@ -0,0 +1 @@ +-record(failover, {previous :: gleam@erlang@node:node_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl new file mode 100644 index 0000000..0c80158 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@application_Takeover.hrl @@ -0,0 +1 @@ +-record(takeover, {previous :: gleam@erlang@node:node_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl new file mode 100644 index 0000000..807c997 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Abnormal.hrl @@ -0,0 +1 @@ +-record(abnormal, {reason :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl new file mode 100644 index 0000000..b82b49f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Cancelled.hrl @@ -0,0 +1 @@ +-record(cancelled, {time_remaining :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl new file mode 100644 index 0000000..c476308 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ExitMessage.hrl @@ -0,0 +1,4 @@ +-record(exit_message, { + pid :: gleam@erlang@process:pid_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl new file mode 100644 index 0000000..d4801f5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_NamedSubject.hrl @@ -0,0 +1 @@ +-record(named_subject, {name :: gleam@erlang@process:name(any())}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl new file mode 100644 index 0000000..d40ace9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_PortDown.hrl @@ -0,0 +1,5 @@ +-record(port_down, { + monitor :: gleam@erlang@process:monitor(), + port :: gleam@erlang@port:port_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl new file mode 100644 index 0000000..7b013a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_ProcessDown.hrl @@ -0,0 +1,5 @@ +-record(process_down, { + monitor :: gleam@erlang@process:monitor(), + pid :: gleam@erlang@process:pid_(), + reason :: gleam@erlang@process:exit_reason() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl new file mode 100644 index 0000000..8c4b577 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/include/gleam@erlang@process_Subject.hrl @@ -0,0 +1,4 @@ +-record(subject, { + owner :: gleam@erlang@process:pid_(), + tag :: gleam@dynamic:dynamic_() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/application.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/application.gleam new file mode 100644 index 0000000..e5c6a40 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/application.gleam @@ -0,0 +1,37 @@ +//// An Erlang application is a collection of code that can be loaded into the +//// Erlang virtual machine and even started and stopped if they define a +//// start module and supervision tree. Each Gleam package is an Erlang +//// application. + +import gleam/erlang/node.{type Node} + +/// The Erlang/OTP application `start` callback takes a start-type as an +/// argument, indicating the context in which the application is being started. +pub type StartType { + /// A normal application start. + Normal + /// The application is distributed and started at the current node because of + /// a takeover from Node, either because Erlang's `application:takeover/2` + /// function has been called, or because the current node has higher priority + /// than the previous node. + Takeover(previous: Node) + /// The application is distributed and started at the current node because of + /// a failover from the previous node. + Failover(previous: Node) +} + +/// Returns the path of an application's `priv` directory, where extra non-Gleam +/// or Erlang files are typically kept. Each Gleam package is an Erlang +/// application. +/// +/// Returns an error if no application was found with the given name. +/// +/// # Example +/// +/// ```gleam +/// application.priv_directory("my_app") +/// // -> Ok("/some/location/my_app/priv") +/// ``` +/// +@external(erlang, "gleam_erlang_ffi", "priv_directory") +pub fn priv_directory(name: String) -> Result(String, Nil) diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam new file mode 100644 index 0000000..3664f03 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/atom.gleam @@ -0,0 +1,81 @@ +import gleam/dynamic +import gleam/dynamic/decode + +/// Atom is a special string-like data-type that is most commonly used for +/// interfacing with code written in other BEAM languages such as Erlang and +/// Elixir. It is preferable to define your own custom types to use instead of +/// atoms where possible. +/// +/// Atoms are not used much in typical Gleam code! +/// +/// ## Creating atoms +/// +/// We can create atoms with the the [`create`](#create) +/// function, though we must be careful when doing so as atoms are never +/// garbage collected. If we create too many atoms (for example, if we convert +/// user input into atoms) we may hit the max limit of atoms and cause the +/// virtual machine to crash. +/// +pub type Atom + +/// Finds an existing atom for the given string. +/// +/// If no atom is found in the virtual machine's atom table for the string then +/// an error is returned. +/// +@external(erlang, "gleam_erlang_ffi", "atom_from_string") +pub fn get(a: String) -> Result(Atom, Nil) + +/// Creates an atom from a string, inserting a new value into the virtual +/// machine's atom table if an atom does not already exist for the given +/// string. +/// +/// We must be careful when using this function as there is a limit to the +/// number of atom that can fit in the virtual machine's atom table. Never +/// convert user input into atoms as filling the atom table will cause the +/// virtual machine to crash! +/// +@external(erlang, "erlang", "binary_to_atom") +pub fn create(a: String) -> Atom + +/// Returns a `String` corresponding to the text representation of the given +/// `Atom`. +/// +/// ## Examples +/// ```gleam +/// let ok_atom = create("ok") +/// to_string(ok_atom) +/// // -> "ok" +/// ``` +/// +@external(erlang, "erlang", "atom_to_binary") +pub fn to_string(a: Atom) -> String + +/// Convert an atom to a dynamic value, throwing away the type information. +/// +/// This may be useful for testing decoders. +/// +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn to_dynamic(a: Atom) -> dynamic.Dynamic + +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn cast_from_dynamic(a: dynamic.Dynamic) -> Atom + +/// A dynamic decoder for atoms. +/// +/// You almost certainly should not use this to work with externally defined +/// functions. They return known types, so you should define the external +/// functions with the correct types, defining wrapper functions in Erlang if +/// the external types cannot be mapped directly onto Gleam types. +/// +pub fn decoder() -> decode.Decoder(Atom) { + decode.new_primitive_decoder("Atom", fn(data) { + case is_atom(data) { + True -> Ok(cast_from_dynamic(data)) + False -> Error(create("nil")) + } + }) +} + +@external(erlang, "erlang", "is_atom") +fn is_atom(data: dynamic.Dynamic) -> Bool diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam new file mode 100644 index 0000000..f7e94da --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/charlist.gleam @@ -0,0 +1,24 @@ +//// A charlist is a list of integers where all the integers are valid code +//// points. +//// +//// In practice, you will not come across them often, except perhaps when +//// interfacing with Erlang, in particular when using older libraries that do +//// not accept binaries as arguments. + +/// A list of characters represented as ints. Commonly used with Erlang +/// functions that do not accept binary strings such as Gleam's core string +/// type. +/// +pub type Charlist + +/// Convert a charlist to a string using Erlang's +/// `unicode:characters_to_binary`. +/// +@external(erlang, "unicode", "characters_to_binary") +pub fn to_string(a: Charlist) -> String + +/// Convert a string to a charlist using Erlang's +/// `unicode:characters_to_list`. +/// +@external(erlang, "unicode", "characters_to_list") +pub fn from_string(a: String) -> Charlist diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/node.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/node.gleam new file mode 100644 index 0000000..294d851 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/node.gleam @@ -0,0 +1,63 @@ +//// Multiple Erlang VM instances can form a cluster to make a distributed +//// Erlang system, talking directly to each other using messages rather than +//// other communication protocols like HTTP. In a distributed Erlang system +//// each virtual machine is called a _node_. This module provides Node related +//// types and functions to be used as a foundation by other packages providing +//// more specialised functionality. +//// +//// For more information on distributed Erlang systems see the Erlang +//// documentation: . + +import gleam/erlang/atom.{type Atom} + +pub type Node + +/// Return the current node. +/// +@external(erlang, "erlang", "node") +pub fn self() -> Node + +/// Return a list of all visible nodes in the cluster, not including the current +/// node. +/// +/// The current node can be included by calling `self()` and prepending the +/// result. +/// +/// ```gleam +/// let all_nodes = [node.self(), ..node.visible()] +/// ``` +/// +@external(erlang, "erlang", "nodes") +pub fn visible() -> List(Node) + +pub type ConnectError { + /// Was unable to connect to the node. + FailedToConnect + /// The local node is not alive, so it is not possible to connect to the other + /// node. + LocalNodeIsNotAlive +} + +// TODO: test unknown node +// TODO: test successfully connecting +/// Establish a connection to a node, so the nodes can send messages to each +/// other and any other connected nodes. +/// +/// Returns `Error(FailedToConnect)` if the node is not reachable. +/// +/// Returns `Error(LocalNodeIsNotAlive)` if the local node is not alive, meaning +/// it is not running in distributed mode. +/// +@external(erlang, "gleam_erlang_ffi", "connect_node") +pub fn connect(node: Atom) -> Result(Node, ConnectError) + +/// Get the atom name of a node. +/// +/// ## Examples +/// +/// ```gleam +/// assert name(my_node) == atom.create("app1@localhost") +/// ``` +/// +@external(erlang, "gleam_erlang_ffi", "identity") +pub fn name(node: Node) -> Atom diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/port.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/port.gleam new file mode 100644 index 0000000..4e1b4d8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/port.gleam @@ -0,0 +1,9 @@ +/// Ports are how code running on the Erlang virtual machine interacts with +/// the outside world. Bytes of data can be sent to and read from ports, +/// providing a form of message passing to an external program or resource. +/// +/// For more information on ports see the [Erlang ports documentation][1]. +/// +/// [1]: https://erlang.org/doc/reference_manual/ports.html +/// +pub type Port diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/process.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/process.gleam new file mode 100644 index 0000000..35df254 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/process.gleam @@ -0,0 +1,876 @@ +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/erlang/atom.{type Atom} +import gleam/erlang/port.{type Port} +import gleam/erlang/reference.{type Reference} +import gleam/string + +/// A `Pid` (or Process identifier) is a reference to an Erlang process. Each +/// process has a `Pid` and it is one of the lowest level building blocks of +/// inter-process communication in the Erlang and Gleam OTP frameworks. +/// +pub type Pid + +/// Get the `Pid` for the current process. +/// +@external(erlang, "erlang", "self") +pub fn self() -> Pid + +/// Create a new Erlang process that runs concurrently to the creator. In other +/// languages this might be called a fibre, a green thread, or a coroutine. +/// +/// The child process is linked to the creator process. When a process +/// terminates an exit signal is sent to all other processes that are linked to +/// it, causing the process to either terminate or have to handle the signal. +/// If you want an unlinked process use the `spawn_unlinked` function. +/// +/// More can be read about processes and links in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/reference_manual/processes.html +/// +/// This function starts processes via the Erlang `proc_lib` module, and as +/// such they benefit from the functionality described in the +/// [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html). +/// +@external(erlang, "proc_lib", "spawn_link") +pub fn spawn(running: fn() -> anything) -> Pid + +/// Create a new Erlang process that runs concurrently to the creator. In other +/// languages this might be called a fibre, a green thread, or a coroutine. +/// +/// Typically you want to create a linked process using the `spawn` function, +/// but creating an unlinked process may be occasionally useful. +/// +/// More can be read about processes and links in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/reference_manual/processes.html +/// +/// This function starts processes via the Erlang `proc_lib` module, and as +/// such they benefit from the functionality described in the +/// [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html). +/// +@external(erlang, "proc_lib", "spawn") +pub fn spawn_unlinked(a: fn() -> anything) -> Pid + +/// A `Subject` is a value that processes can use to send and receive messages +/// to and from each other in a well typed way. +/// +/// Each subject is "owned" by the process that created it. Any process can use +/// the `send` function to send a message of the correct type to the process +/// that owns the subject, and the owner can use the `receive` function or the +/// `Selector` type to receive these messages. +/// +/// The `Subject` type is similar to the "channel" types found in other +/// languages and the "topic" concept found in some pub-sub systems. +/// +/// # Examples +/// +/// ```gleam +/// let subject = new_subject() +/// +/// // Send a message with the subject +/// send(subject, "Hello, Joe!") +/// +/// // Receive the message +/// receive(subject, within: 10) +/// ``` +/// +pub opaque type Subject(message) { + Subject(owner: Pid, tag: Dynamic) + NamedSubject(name: Name(message)) +} + +/// Create a subject for the given process with the give tag. This is unsafe! +/// There's nothing here that verifies that the message the subject receives is +/// expected and that the tag is not already in use. +/// +/// You should almost certainly not use this function. +/// +@internal +pub fn unsafely_create_subject(owner: Pid, tag: Dynamic) -> Subject(message) { + Subject(owner, tag) +} + +/// A name is an identity that a process can adopt, after which they will receive +/// messages sent to that name. This has two main advantages: +/// +/// - Structuring OTP programs becomes easier as a name can be passed down the +/// program from the top level, while without names subjects and pids would +/// need to be passed up from the started process and then back down to the +/// code that works with that process. +/// - A new process can adopt the name of one that previously failed, allowing +/// it to transparently take-over and handle messages that are sent to that +/// name. +/// +/// Names are globally unique as each process can have at most 1 name, and each +/// name can be registered by at most 1 process. Create all the names your +/// program needs at the start of your program and pass them down. Names are +/// Erlang atoms internally, so never create them dynamically. Generating too +/// many atoms will result in the atom table getting filled and causing the entire +/// virtual machine to crash. +/// +/// The most commonly used name functions are `new_name`, `register`, and +/// `named_subject`. +/// +pub type Name(message) + +/// Generate a new name that a process can register itself with using the +/// `register` function, and other processes can send messages to using +/// `named_subject`. +/// +/// The string argument is a prefix for the Erlang name. A unique suffix is +/// added to the prefix to make the name, removing the possibility of name +/// collisions. +/// +/// ## Safe use +/// +/// Use this function to create all the names your program needs when it +/// starts. **Never call this function dynamically** such as within a loop or +/// within a process within a supervision tree. +/// +/// Each time this function is called a new atom will be generated. Generating +/// too many atoms will result in the atom table getting filled and causing the +/// entire virtual machine to crash. +/// +@external(erlang, "gleam_erlang_ffi", "new_name") +pub fn new_name(prefix prefix: String) -> Name(message) + +/// Create a subject for a name, which can be used to send and receive messages. +/// +/// All subjects created for the same name behave identically and can be used +/// interchangably. +/// +pub fn named_subject(name: Name(message)) -> Subject(message) { + NamedSubject(name) +} + +/// Get the name of a subject, returning an error if it doesn't have one. +/// +pub fn subject_name(subject: Subject(message)) -> Result(Name(message), Nil) { + case subject { + NamedSubject(name:) -> Ok(name) + Subject(..) -> Error(Nil) + } +} + +/// Create a new `Subject` owned by the current process. +/// +pub fn new_subject() -> Subject(message) { + Subject(owner: self(), tag: reference_to_dynamic(reference.new())) +} + +/// Get the owner process for a subject, which is the process that will +/// receive any messages sent using the subject. +/// +/// If the subject was created from a name and no process is currently +/// registered with that name then this function will return an error. +/// +pub fn subject_owner(subject: Subject(message)) -> Result(Pid, Nil) { + case subject { + NamedSubject(name) -> named(name) + Subject(pid, _) -> Ok(pid) + } +} + +type DoNotLeak + +@external(erlang, "erlang", "send") +fn raw_send(a: Pid, b: message) -> DoNotLeak + +/// Send a message to a process using a `Subject`. The message must be of the +/// type that the `Subject` accepts. +/// +/// This function does not wait for the `Subject` owner process to call the +/// `receive` function, instead it returns once the message has been placed in +/// the process' mailbox. +/// +/// # Named Subjects +/// +/// If this function is called on a named subject for which a process has not been +/// registered, it will simply drop the message as there's no mailbox to send it to. +/// +/// # Panics +/// +/// This function will panic when sending to a named subject if no process is +/// currently registed under that name. +/// +/// # Ordering +/// +/// If process P1 sends two messages to process P2 it is guaranteed that process +/// P1 will receive the messages in the order they were sent. +/// +/// If you wish to receive the messages in a different order you can send them +/// on two different subjects and the receiver function can call the `receive` +/// function for each subject in the desired order, or you can write some Erlang +/// code to perform a selective receive. +/// +/// # Examples +/// +/// ```gleam +/// let subject = new_subject() +/// send(subject, "Hello, Joe!") +/// ``` +/// +pub fn send(subject: Subject(message), message: message) -> Nil { + case subject { + Subject(pid, tag) -> { + raw_send(pid, #(tag, message)) + } + NamedSubject(name) -> { + let assert Ok(pid) = named(name) as "Sending to unregistered name" + raw_send(pid, #(name, message)) + } + } + Nil +} + +/// Receive a message that has been sent to current process using the `Subject`. +/// +/// If there is not an existing message for the `Subject` in the process' +/// mailbox or one does not arrive `within` the permitted timeout then the +/// `Error(Nil)` is returned. +/// +/// Only the process that is owner of the `Subject` can receive a message using +/// it. If a process that does not own the `Subject` attempts to receive with it +/// then it will not receive a message. +/// +/// To wait for messages from multiple `Subject`s at the same time see the +/// `Selector` type. +/// +/// The `within` parameter specifies the timeout duration in milliseconds. +/// +/// ## Panics +/// +/// This function will panic if a process tries to receive with a non-named +/// subject that it does not own. +/// +pub fn receive( + from subject: Subject(message), + within timeout: Int, +) -> Result(message, Nil) { + case subject { + NamedSubject(..) -> perform_receive(subject, timeout) + Subject(owner:, ..) -> + case owner == self() { + True -> perform_receive(subject, timeout) + False -> + panic as "Cannot receive with a subject owned by another process" + } + } +} + +@external(erlang, "gleam_erlang_ffi", "receive") +fn perform_receive( + subject: Subject(message), + timeout: Int, +) -> Result(message, Nil) + +/// Receive a message that has been sent to current process using the `Subject`. +/// +/// Same as `receive` but waits forever and returns the message as is. +@external(erlang, "gleam_erlang_ffi", "receive") +pub fn receive_forever(from subject: Subject(message)) -> message + +/// A type that enables a process to wait for messages from multiple `Subject`s +/// at the same time, returning whichever message arrives first. +/// +/// Used with the `new_selector`, `selector_receive`, and `select*` functions. +/// +/// # Examples +/// +/// ```gleam +/// let int_subject = new_subject() +/// let string_subject = new_subject() +/// send(int_subject, 1) +/// +/// let selector = +/// new_selector() +/// |> select(string_subject) +/// |> select_map(int_subject, int.to_string) +/// +/// selector_receive(selector, 10) +/// // -> Ok("1") +/// ``` +/// +pub type Selector(payload) + +/// Create a new `Selector` which can be used to receive messages on multiple +/// `Subject`s at once. +/// +@external(erlang, "gleam_erlang_ffi", "new_selector") +pub fn new_selector() -> Selector(payload) + +/// Receive a message that has been sent to current process using any of the +/// `Subject`s that have been added to the `Selector` with the `select*` +/// functions. +/// +/// If there is not an existing message for the `Selector` in the process' +/// mailbox or one does not arrive `within` the permitted timeout then the +/// `Error(Nil)` is returned. +/// +/// Only the process that is owner of the `Subject`s can receive a message using +/// them. If a process that does not own the a `Subject` attempts to receive +/// with it then it will not receive a message. +/// +/// To wait forever for the next message rather than for a limited amount of +/// time see the `selector_receive_forever` function. +/// +/// The `within` parameter specifies the timeout duration in milliseconds. +/// +@external(erlang, "gleam_erlang_ffi", "select") +pub fn selector_receive( + from from: Selector(payload), + within within: Int, +) -> Result(payload, Nil) + +/// Similar to the `select` function but will wait forever for a message to +/// arrive rather than timing out after a specified amount of time. +/// +@external(erlang, "gleam_erlang_ffi", "select") +pub fn selector_receive_forever(from from: Selector(payload)) -> payload + +/// Add a transformation function to a selector. When a message is received +/// using this selector the transformation function is applied to the message. +/// +/// This function can be used to change the type of messages received and may +/// be useful when combined with the `merge_selector` function. +/// +@external(erlang, "gleam_erlang_ffi", "map_selector") +pub fn map_selector(a: Selector(a), b: fn(a) -> b) -> Selector(b) + +/// Merge one selector into another, producing a selector that contains the +/// message handlers of both. +/// +/// If a subject is handled by both selectors the handler function of the +/// second selector is used. +/// +@external(erlang, "gleam_erlang_ffi", "merge_selector") +pub fn merge_selector(a: Selector(a), b: Selector(a)) -> Selector(a) + +pub type ExitMessage { + ExitMessage(pid: Pid, reason: ExitReason) +} + +pub type ExitReason { + Normal + Killed + Abnormal(reason: Dynamic) +} + +/// Add a handler for trapped exit messages. In order for these messages to be +/// sent to the process when a linked process exits the process must call the +/// `trap_exit` beforehand. +/// +pub fn select_trapped_exits( + selector: Selector(a), + handler: fn(ExitMessage) -> a, +) -> Selector(a) { + let tag = atom.create("EXIT") + let handler = fn(message: #(Atom, Pid, Dynamic)) -> a { + handler(ExitMessage(message.1, cast_exit_reason(message.2))) + } + insert_selector_handler(selector, #(tag, 3), handler) +} + +/// Discard all messages in the current process' mailbox. +/// +/// Warning: This function may cause other processes to crash if they sent a +/// message to the current process and are waiting for a response, so use with +/// caution. +/// +/// This function may be useful in tests. +/// +@external(erlang, "gleam_erlang_ffi", "flush_messages") +pub fn flush_messages() -> Nil + +/// Add a new `Subject` to the `Selector` so that its messages can be selected +/// from the receiver process inbox. +/// +/// See `select_map` to add subjects of a different message type. +/// +/// See `deselect` to remove a subject from a selector. +/// +pub fn select( + selector: Selector(payload), + for subject: Subject(payload), +) -> Selector(payload) { + select_map(selector, subject, fn(x) { x }) +} + +/// Add a new `Subject` to the `Selector` so that its messages can be selected +/// from the receiver process inbox. +/// +/// The `mapping` function provided with the `Subject` can be used to convert +/// the type of messages received using this `Subject`. This is useful for when +/// you wish to add multiple `Subject`s to a `Selector` when they have differing +/// message types. If you do not wish to transform the incoming messages in any +/// way then the `identity` function can be given. +/// +/// See `deselect` to remove a subject from a selector. +/// +pub fn select_map( + selector: Selector(payload), + for subject: Subject(message), + mapping transform: fn(message) -> payload, +) -> Selector(payload) { + let handler = fn(message: #(Reference, message)) { transform(message.1) } + case subject { + NamedSubject(name) -> insert_selector_handler(selector, #(name, 2), handler) + Subject(_, tag:) -> insert_selector_handler(selector, #(tag, 2), handler) + } +} + +/// Remove a new `Subject` from the `Selector` so that its messages will not be +/// selected from the receiver process inbox. +/// +pub fn deselect( + selector: Selector(payload), + for subject: Subject(message), +) -> Selector(payload) { + case subject { + NamedSubject(name) -> remove_selector_handler(selector, #(name, 2)) + Subject(_, tag:) -> remove_selector_handler(selector, #(tag, 2)) + } +} + +/// Add a handler to a selector for tuple messages with a given tag in the +/// first position followed by a given number of fields. +/// +/// Typically you want to use the `select` function with a `Subject` instead, +/// but this function may be useful if you need to receive messages sent from +/// other BEAM languages that do not use the `Subject` type. +/// +/// This will not select messages sent via a subject even if the message has +/// the same tag in the first position. This is because when a message is sent +/// via a subject a new tag is used that is unique and specific to that subject. +/// +pub fn select_record( + selector: Selector(payload), + tag tag: tag, + fields arity: Int, + mapping transform: fn(Dynamic) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, #(tag, arity + 1), transform) +} + +type AnythingSelectorTag { + Anything +} + +/// Add a catch-all handler to a selector that will be used when no other +/// handler in a selector is suitable for a given message. +/// +/// This may be useful for when you want to ensure that any message in the inbox +/// is handled, or when you need to handle messages from other BEAM languages +/// which do not use subjects or record format messages. +/// +pub fn select_other( + selector: Selector(payload), + mapping handler: fn(Dynamic) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, Anything, handler) +} + +@external(erlang, "gleam_erlang_ffi", "insert_selector_handler") +fn insert_selector_handler( + a: Selector(payload), + for for: tag, + mapping mapping: fn(message) -> payload, +) -> Selector(payload) + +@external(erlang, "gleam_erlang_ffi", "remove_selector_handler") +fn remove_selector_handler( + a: Selector(payload), + for for: tag, +) -> Selector(payload) + +/// Suspends the process calling this function for the specified number of +/// milliseconds. +/// +@external(erlang, "gleam_erlang_ffi", "sleep") +pub fn sleep(a: Int) -> Nil + +/// Suspends the process forever! This may be useful for suspending the main +/// process in a Gleam program when it has no more work to do but we want other +/// processes to continue to work. +/// +@external(erlang, "gleam_erlang_ffi", "sleep_forever") +pub fn sleep_forever() -> Nil + +/// Check to see whether the process for a given `Pid` is alive. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#is_process_alive-1 +/// +@external(erlang, "erlang", "is_process_alive") +pub fn is_alive(a: Pid) -> Bool + +type ProcessMonitorFlag { + Process +} + +@external(erlang, "erlang", "monitor") +fn erlang_monitor_process(a: ProcessMonitorFlag, b: Pid) -> Monitor + +pub type Monitor + +/// A message received when a monitored process exits. +/// +pub type Down { + ProcessDown(monitor: Monitor, pid: Pid, reason: ExitReason) + PortDown(monitor: Monitor, port: Port, reason: ExitReason) +} + +/// Start monitoring a process so that when the monitored process exits a +/// message is sent to the monitoring process. +/// +/// The message is only sent once, when the target process exits. If the +/// process was not alive when this function is called the message will never +/// be received. +/// +/// The down message can be received with a selector and the +/// `select_monitors` function. +/// +/// The process can be demonitored with the `demonitor_process` function. +/// +pub fn monitor(pid: Pid) -> Monitor { + erlang_monitor_process(Process, pid) +} + +/// Select for a message sent for a given monitor. +/// +/// Each monitor handler added to a selector has a select performance cost, +/// so prefer [`select_monitors`](#select_monitors) if you are select +/// for multiple monitors. +/// +/// The handler can be removed from the selector later using +/// [`deselect_specific_monitor`](#deselect_specific_monitor). +/// +pub fn select_specific_monitor( + selector: Selector(payload), + monitor: Monitor, + mapping: fn(Down) -> payload, +) { + insert_selector_handler(selector, monitor, mapping) +} + +/// Select for any messages sent for any monitors set up by the select process. +/// +/// If you want to select for a specific message then use +/// [`select_specific_monitor`](#select_specific_monitor), but this +/// function is preferred if you need to select for multiple monitors. +/// +pub fn select_monitors( + selector: Selector(payload), + mapping: fn(Down) -> payload, +) -> Selector(payload) { + insert_selector_handler(selector, #(atom.create("DOWN"), 5), fn(message) { + mapping(cast_down_message(message)) + }) +} + +@external(erlang, "gleam_erlang_ffi", "cast_down_message") +fn cast_down_message(message: Dynamic) -> Down + +@external(erlang, "gleam_erlang_ffi", "cast_exit_reason") +fn cast_exit_reason(message: Dynamic) -> ExitReason + +/// Remove the monitor for a process so that when the monitor process exits a +/// `Down` message is not sent to the monitoring process. +/// +/// If the message has already been sent it is removed from the monitoring +/// process' mailbox. +/// +pub fn demonitor_process(monitor monitor: Monitor) -> Nil { + erlang_demonitor_process(monitor) + Nil +} + +@external(erlang, "gleam_erlang_ffi", "demonitor") +fn erlang_demonitor_process(monitor: Monitor) -> DoNotLeak + +/// Remove a `Monitor` from a `Selector` prevoiusly added by +/// [`select_specific_monitor`](#select_specific_monitor). If +/// the `Monitor` is not in the `Selector` it will be returned +/// unchanged. +/// +pub fn deselect_specific_monitor( + selector: Selector(payload), + monitor: Monitor, +) -> Selector(payload) { + remove_selector_handler(selector, monitor) +} + +fn perform_call( + subject: Subject(message), + make_request: fn(Subject(reply)) -> message, + run_selector: fn(Selector(reply)) -> Result(reply, Nil), +) -> reply { + let reply_subject = new_subject() + let assert Ok(callee) = subject_owner(subject) + as "Callee subject had no owner" + + // Monitor the callee process so we can tell if it goes down (meaning we + // won't get a reply) + let monitor = monitor(callee) + + // Send the request to the process over the channel + send(subject, make_request(reply_subject)) + + // Await a reply or handle failure modes (timeout, process down, etc) + let reply = + new_selector() + |> select(reply_subject) + |> select_specific_monitor(monitor, fn(down) { + panic as { "callee exited: " <> string.inspect(down) } + }) + |> run_selector + + let assert Ok(reply) = reply as "callee did not send reply before timeout" + + // Demonitor the process and close the channels as we're done + demonitor_process(monitor) + + reply +} + +// This function is based off of Erlang's gen:do_call/4. +/// Send a message to a process and wait a given number of milliseconds for a +/// reply. +/// +/// ## Panics +/// +/// This function will panic under the following circumstances: +/// - The callee process exited prior to sending a reply. +/// - The callee process did not send a reply within the permitted amount of +/// time. +/// - The subject is a named subject but no process is registered with that +/// name. +/// +/// ## Examples +/// +/// ```gleam +/// pub type Message { +/// // This message variant is to be used with `call`. +/// // The `reply` field contains a subject that the reply message will be +/// // sent over. +/// SayHello(reply_to: Subject(String), name: String) +/// } +/// +/// // Typically we make public functions that hide the details of a process' +/// // message-based API. +/// pub fn say_hello(subject: Subject(Message), name: String) -> String { +/// // The `SayHello` message constructor is given _partially applied_ with +/// // all the arguments except the reply subject, which will be supplied by +/// // the `call` function itself before sending the message. +/// process.call(subject, 100, SayHello(_, name)) +/// } +/// +/// // This is the message handling logic used by the process that owns the +/// // subject, and so receives the messages. In a real project it would be +/// // within a process or some higher level abstraction like an actor, but for +/// // this demonstration that has been omitted. +/// pub fn handle_message(message: Message) -> Nil { +/// case message { +/// SayHello(reply:, name:) -> { +/// let data = "Hello, " <> name <> "!" +/// // The reply subject is used to send the response back. +/// // If the receiver process does not sent a reply in time then the +/// // caller will crash. +/// process.send(reply, data) +/// } +/// } +/// } +/// +/// // Here is what it looks like using the functional API to call the process. +/// pub fn run(subject: Subject(Message)) { +/// say_hello(subject, "Lucy") +/// // -> "Hello, Lucy!" +/// say_hello(subject, "Nubi") +/// // -> "Hello, Nubi!" +/// } +/// ``` +/// +pub fn call( + subject: Subject(message), + waiting timeout: Int, + sending make_request: fn(Subject(reply)) -> message, +) -> reply { + perform_call(subject, make_request, selector_receive(_, timeout)) +} + +/// Send a message to a process and wait for a reply. +/// +/// # Panics +/// +/// This function will panic under the following circumstances: +/// - The callee process exited prior to sending a reply. +/// - The subject is a named subject but no process is registered with that +/// name. +/// +pub fn call_forever( + subject: Subject(message), + make_request: fn(Subject(reply)) -> message, +) -> reply { + perform_call(subject, make_request, fn(s) { Ok(selector_receive_forever(s)) }) +} + +/// Creates a link between the calling process and another process. +/// +/// When a process crashes any linked processes will also crash. This is useful +/// to ensure that groups of processes that depend on each other all either +/// succeed or fail together. +/// +/// Returns `True` if the link was created successfully, returns `False` if the +/// process was not alive and as such could not be linked. +/// +@external(erlang, "gleam_erlang_ffi", "link") +pub fn link(pid pid: Pid) -> Bool + +@external(erlang, "erlang", "unlink") +fn erlang_unlink(pid pid: Pid) -> Bool + +/// Removes any existing link between the caller process and the target process. +/// +pub fn unlink(pid: Pid) -> Nil { + erlang_unlink(pid) + Nil +} + +pub type Timer + +@external(erlang, "erlang", "send_after") +fn pid_send_after(a: Int, b: Pid, c: #(Dynamic, msg)) -> Timer + +@external(erlang, "erlang", "send_after") +fn name_send_after(a: Int, b: Name(msg), c: #(Name(msg), msg)) -> Timer + +/// Send a message over a channel after a specified number of milliseconds. +/// +pub fn send_after(subject: Subject(msg), delay: Int, message: msg) -> Timer { + case subject { + NamedSubject(name) -> name_send_after(delay, name, #(name, message)) + Subject(owner, tag) -> pid_send_after(delay, owner, #(tag, message)) + } +} + +@external(erlang, "gleam_erlang_ffi", "identity") +fn reference_to_dynamic(reference: Reference) -> Dynamic + +@external(erlang, "erlang", "cancel_timer") +fn erlang_cancel_timer(a: Timer) -> Dynamic + +/// Values returned when a timer is cancelled. +/// +pub type Cancelled { + /// The timer could not be found. It likely has already triggered. + /// + TimerNotFound + + /// The timer was found and cancelled before it triggered. + /// + /// The amount of remaining time before the timer was due to be triggered is + /// returned in milliseconds. + /// + Cancelled(time_remaining: Int) +} + +/// Cancel a given timer, causing it not to trigger if it has not done already. +/// +pub fn cancel_timer(timer: Timer) -> Cancelled { + case decode.run(erlang_cancel_timer(timer), decode.int) { + Ok(i) -> Cancelled(i) + Error(_) -> TimerNotFound + } +} + +type KillFlag { + Kill +} + +@external(erlang, "erlang", "exit") +fn erlang_kill(to to: Pid, because because: KillFlag) -> Bool + +// Go, my pretties. Kill! Kill! +// - Bart Simpson +// +/// Send an untrappable `kill` exit signal to the target process. +/// +/// See the documentation for the Erlang [`erlang:exit`][1] function for more +/// information. +/// +/// [1]: https://erlang.org/doc/man/erlang.html#exit-1 +/// +pub fn kill(pid: Pid) -> Nil { + erlang_kill(pid, Kill) + Nil +} + +@external(erlang, "erlang", "exit") +fn erlang_send_exit(to to: Pid, because because: whatever) -> Bool + +// TODO: test +/// Sends an exit signal to a process, indicating that the process is to shut +/// down. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#exit-2 +/// +pub fn send_exit(to pid: Pid) -> Nil { + erlang_send_exit(pid, Normal) + Nil +} + +/// Sends an exit signal to a process, indicating that the process is to shut +/// down due to an abnormal reason such as a failure. +/// +/// See the [Erlang documentation][1] for more information. +/// +/// [1]: http://erlang.org/doc/man/erlang.html#exit-2 +/// +pub fn send_abnormal_exit(pid: Pid, reason: anything) -> Nil { + erlang_send_exit(pid, reason) + Nil +} + +/// Set whether the current process is to trap exit signals or not. +/// +/// When not trapping exits if a linked process crashes the exit signal +/// propagates to the process which will also crash. +/// This is the normal behaviour before this function is called. +/// +/// When trapping exits (after this function is called) if a linked process +/// crashes an exit message is sent to the process instead. These messages can +/// be handled with the `select_trapped_exits` function. +/// +@external(erlang, "gleam_erlang_ffi", "trap_exits") +pub fn trap_exits(a: Bool) -> Nil + +/// Register a process under a given name, allowing it to be looked up using +/// the `named` function. +/// +/// This function will return an error under the following conditions: +/// - The process for the pid no longer exists. +/// - The name has already been registered. +/// - The process already has a name. +/// +@external(erlang, "gleam_erlang_ffi", "register_process") +pub fn register(pid: Pid, name: Name(message)) -> Result(Nil, Nil) + +/// Un-register a process name, after which the process can no longer be looked +/// up by that name, and both the name and the process can be re-used in other +/// registrations. +/// +/// It is possible to un-register process that are not from your application, +/// including those from Erlang/OTP itself. This is not recommended and will +/// likely result in undesirable behaviour and crashes. +/// +@external(erlang, "gleam_erlang_ffi", "unregister_process") +pub fn unregister(name: Name(message)) -> Result(Nil, Nil) + +/// Look up a process by registered name, returning the pid if it exists. +/// +@external(erlang, "gleam_erlang_ffi", "process_named") +pub fn named(name: Name(a)) -> Result(Pid, Nil) diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam new file mode 100644 index 0000000..8a69d64 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam/erlang/reference.gleam @@ -0,0 +1,15 @@ +/// A unique reference value. +/// +/// It holds no particular meaning or value, but unique values are often useful +/// in programs are used heavily within both Gleam and Erlang's OTP frameworks. +/// +/// More can be read about references in the [Erlang documentation][1]. +/// +/// [1]: https://www.erlang.org/doc/efficiency_guide/advanced.html#unique_references +/// +pub type Reference + +/// Create a new unique reference. +/// +@external(erlang, "erlang", "make_ref") +pub fn new() -> Reference diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@application.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@application.erl new file mode 100644 index 0000000..f571565 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@application.erl @@ -0,0 +1,43 @@ +-module(gleam@erlang@application). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/application.gleam"). +-export([priv_directory/1]). +-export_type([start_type/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " An Erlang application is a collection of code that can be loaded into the\n" + " Erlang virtual machine and even started and stopped if they define a\n" + " start module and supervision tree. Each Gleam package is an Erlang\n" + " application.\n" +). + +-type start_type() :: normal | + {takeover, gleam@erlang@node:node_()} | + {failover, gleam@erlang@node:node_()}. + +-file("src/gleam/erlang/application.gleam", 37). +?DOC( + " Returns the path of an application's `priv` directory, where extra non-Gleam\n" + " or Erlang files are typically kept. Each Gleam package is an Erlang\n" + " application.\n" + "\n" + " Returns an error if no application was found with the given name.\n" + "\n" + " # Example\n" + "\n" + " ```gleam\n" + " application.priv_directory(\"my_app\")\n" + " // -> Ok(\"/some/location/my_app/priv\")\n" + " ```\n" +). +-spec priv_directory(binary()) -> {ok, binary()} | {error, nil}. +priv_directory(Name) -> + gleam_erlang_ffi:priv_directory(Name). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@atom.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@atom.erl new file mode 100644 index 0000000..40c4fa2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@atom.erl @@ -0,0 +1,94 @@ +-module(gleam@erlang@atom). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/atom.gleam"). +-export([get/1, create/1, to_string/1, to_dynamic/1, cast_from_dynamic/1, decoder/0]). +-export_type([atom_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type atom_() :: any(). + +-file("src/gleam/erlang/atom.gleam", 27). +?DOC( + " Finds an existing atom for the given string.\n" + "\n" + " If no atom is found in the virtual machine's atom table for the string then\n" + " an error is returned.\n" +). +-spec get(binary()) -> {ok, atom_()} | {error, nil}. +get(A) -> + gleam_erlang_ffi:atom_from_string(A). + +-file("src/gleam/erlang/atom.gleam", 39). +?DOC( + " Creates an atom from a string, inserting a new value into the virtual\n" + " machine's atom table if an atom does not already exist for the given\n" + " string.\n" + "\n" + " We must be careful when using this function as there is a limit to the\n" + " number of atom that can fit in the virtual machine's atom table. Never\n" + " convert user input into atoms as filling the atom table will cause the\n" + " virtual machine to crash!\n" +). +-spec create(binary()) -> atom_(). +create(A) -> + erlang:binary_to_atom(A). + +-file("src/gleam/erlang/atom.gleam", 52). +?DOC( + " Returns a `String` corresponding to the text representation of the given\n" + " `Atom`.\n" + "\n" + " ## Examples\n" + " ```gleam\n" + " let ok_atom = create(\"ok\")\n" + " to_string(ok_atom)\n" + " // -> \"ok\"\n" + " ```\n" +). +-spec to_string(atom_()) -> binary(). +to_string(A) -> + erlang:atom_to_binary(A). + +-file("src/gleam/erlang/atom.gleam", 59). +?DOC( + " Convert an atom to a dynamic value, throwing away the type information. \n" + "\n" + " This may be useful for testing decoders.\n" +). +-spec to_dynamic(atom_()) -> gleam@dynamic:dynamic_(). +to_dynamic(A) -> + gleam_erlang_ffi:identity(A). + +-file("src/gleam/erlang/atom.gleam", 62). +-spec cast_from_dynamic(gleam@dynamic:dynamic_()) -> atom_(). +cast_from_dynamic(A) -> + gleam_erlang_ffi:identity(A). + +-file("src/gleam/erlang/atom.gleam", 71). +?DOC( + " A dynamic decoder for atoms.\n" + "\n" + " You almost certainly should not use this to work with externally defined\n" + " functions. They return known types, so you should define the external\n" + " functions with the correct types, defining wrapper functions in Erlang if\n" + " the external types cannot be mapped directly onto Gleam types.\n" +). +-spec decoder() -> gleam@dynamic@decode:decoder(atom_()). +decoder() -> + gleam@dynamic@decode:new_primitive_decoder( + <<"Atom"/utf8>>, + fun(Data) -> case erlang:is_atom(Data) of + true -> + {ok, gleam_erlang_ffi:identity(Data)}; + + false -> + {error, erlang:binary_to_atom(<<"nil"/utf8>>)} + end end + ). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl new file mode 100644 index 0000000..15b7bcf --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@charlist.erl @@ -0,0 +1,42 @@ +-module(gleam@erlang@charlist). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/charlist.gleam"). +-export([to_string/1, from_string/1]). +-export_type([charlist/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A charlist is a list of integers where all the integers are valid code\n" + " points.\n" + "\n" + " In practice, you will not come across them often, except perhaps when\n" + " interfacing with Erlang, in particular when using older libraries that do\n" + " not accept binaries as arguments.\n" +). + +-type charlist() :: any(). + +-file("src/gleam/erlang/charlist.gleam", 18). +?DOC( + " Convert a charlist to a string using Erlang's\n" + " `unicode:characters_to_binary`.\n" +). +-spec to_string(charlist()) -> binary(). +to_string(A) -> + unicode:characters_to_binary(A). + +-file("src/gleam/erlang/charlist.gleam", 24). +?DOC( + " Convert a string to a charlist using Erlang's\n" + " `unicode:characters_to_list`.\n" +). +-spec from_string(binary()) -> charlist(). +from_string(A) -> + unicode:characters_to_list(A). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@node.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@node.erl new file mode 100644 index 0000000..97e3ce5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@node.erl @@ -0,0 +1,80 @@ +-module(gleam@erlang@node). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/node.gleam"). +-export([self/0, visible/0, connect/1, name/1]). +-export_type([node_/0, connect_error/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Multiple Erlang VM instances can form a cluster to make a distributed\n" + " Erlang system, talking directly to each other using messages rather than\n" + " other communication protocols like HTTP. In a distributed Erlang system\n" + " each virtual machine is called a _node_. This module provides Node related\n" + " types and functions to be used as a foundation by other packages providing\n" + " more specialised functionality.\n" + "\n" + " For more information on distributed Erlang systems see the Erlang\n" + " documentation: .\n" +). + +-type node_() :: any(). + +-type connect_error() :: failed_to_connect | local_node_is_not_alive. + +-file("src/gleam/erlang/node.gleam", 18). +?DOC(" Return the current node.\n"). +-spec self() -> node_(). +self() -> + erlang:node(). + +-file("src/gleam/erlang/node.gleam", 31). +?DOC( + " Return a list of all visible nodes in the cluster, not including the current\n" + " node.\n" + "\n" + " The current node can be included by calling `self()` and prepending the\n" + " result.\n" + "\n" + " ```gleam\n" + " let all_nodes = [node.self(), ..node.visible()]\n" + " ```\n" +). +-spec visible() -> list(node_()). +visible() -> + erlang:nodes(). + +-file("src/gleam/erlang/node.gleam", 52). +?DOC( + " Establish a connection to a node, so the nodes can send messages to each\n" + " other and any other connected nodes.\n" + "\n" + " Returns `Error(FailedToConnect)` if the node is not reachable.\n" + "\n" + " Returns `Error(LocalNodeIsNotAlive)` if the local node is not alive, meaning\n" + " it is not running in distributed mode.\n" +). +-spec connect(gleam@erlang@atom:atom_()) -> {ok, node_()} | + {error, connect_error()}. +connect(Node) -> + gleam_erlang_ffi:connect_node(Node). + +-file("src/gleam/erlang/node.gleam", 63). +?DOC( + " Get the atom name of a node.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert name(my_node) == atom.create(\"app1@localhost\")\n" + " ```\n" +). +-spec name(node_()) -> gleam@erlang@atom:atom_(). +name(Node) -> + gleam_erlang_ffi:identity(Node). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@port.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@port.erl new file mode 100644 index 0000000..65b8ca2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@port.erl @@ -0,0 +1,8 @@ +-module(gleam@erlang@port). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/port.gleam"). +-export_type([port_/0]). + +-type port_() :: any(). + + diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@process.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@process.erl new file mode 100644 index 0000000..53b69bf --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@process.erl @@ -0,0 +1,868 @@ +-module(gleam@erlang@process). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/process.gleam"). +-export([self/0, spawn/1, spawn_unlinked/1, unsafely_create_subject/2, new_name/1, named_subject/1, subject_name/1, 'receive'/2, receive_forever/1, new_selector/0, selector_receive/2, selector_receive_forever/1, map_selector/2, merge_selector/2, flush_messages/0, select_map/3, select/2, select_record/4, select_other/2, deselect/2, sleep/1, sleep_forever/0, is_alive/1, monitor/1, select_specific_monitor/3, select_monitors/2, select_trapped_exits/2, demonitor_process/1, deselect_specific_monitor/2, link/1, unlink/1, send_after/3, new_subject/0, cancel_timer/1, kill/1, send_exit/1, send_abnormal_exit/2, trap_exits/1, register/2, unregister/1, named/1, subject_owner/1, send/2, call/3, call_forever/2]). +-export_type([pid_/0, subject/1, name/1, do_not_leak/0, selector/1, exit_message/0, exit_reason/0, anything_selector_tag/0, process_monitor_flag/0, monitor/0, down/0, timer/0, cancelled/0, kill_flag/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type pid_() :: any(). + +-opaque subject(DVB) :: {subject, pid_(), gleam@dynamic:dynamic_()} | + {named_subject, name(DVB)}. + +-type name(DVC) :: any() | {gleam_phantom, DVC}. + +-type do_not_leak() :: any(). + +-type selector(DVD) :: any() | {gleam_phantom, DVD}. + +-type exit_message() :: {exit_message, pid_(), exit_reason()}. + +-type exit_reason() :: normal | killed | {abnormal, gleam@dynamic:dynamic_()}. + +-type anything_selector_tag() :: anything. + +-type process_monitor_flag() :: process. + +-type monitor() :: any(). + +-type down() :: {process_down, monitor(), pid_(), exit_reason()} | + {port_down, monitor(), gleam@erlang@port:port_(), exit_reason()}. + +-type timer() :: any(). + +-type cancelled() :: timer_not_found | {cancelled, integer()}. + +-type kill_flag() :: kill. + +-file("src/gleam/erlang/process.gleam", 17). +?DOC(" Get the `Pid` for the current process.\n"). +-spec self() -> pid_(). +self() -> + erlang:self(). + +-file("src/gleam/erlang/process.gleam", 36). +?DOC( + " Create a new Erlang process that runs concurrently to the creator. In other\n" + " languages this might be called a fibre, a green thread, or a coroutine.\n" + "\n" + " The child process is linked to the creator process. When a process\n" + " terminates an exit signal is sent to all other processes that are linked to\n" + " it, causing the process to either terminate or have to handle the signal.\n" + " If you want an unlinked process use the `spawn_unlinked` function.\n" + "\n" + " More can be read about processes and links in the [Erlang documentation][1].\n" + "\n" + " [1]: https://www.erlang.org/doc/reference_manual/processes.html\n" + "\n" + " This function starts processes via the Erlang `proc_lib` module, and as\n" + " such they benefit from the functionality described in the\n" + " [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html).\n" +). +-spec spawn(fun(() -> any())) -> pid_(). +spawn(Running) -> + proc_lib:spawn_link(Running). + +-file("src/gleam/erlang/process.gleam", 53). +?DOC( + " Create a new Erlang process that runs concurrently to the creator. In other\n" + " languages this might be called a fibre, a green thread, or a coroutine.\n" + "\n" + " Typically you want to create a linked process using the `spawn` function,\n" + " but creating an unlinked process may be occasionally useful.\n" + "\n" + " More can be read about processes and links in the [Erlang documentation][1].\n" + "\n" + " [1]: https://www.erlang.org/doc/reference_manual/processes.html\n" + "\n" + " This function starts processes via the Erlang `proc_lib` module, and as\n" + " such they benefit from the functionality described in the\n" + " [`proc_lib` documentation](https://www.erlang.org/doc/apps/stdlib/proc_lib.html).\n" +). +-spec spawn_unlinked(fun(() -> any())) -> pid_(). +spawn_unlinked(A) -> + proc_lib:spawn(A). + +-file("src/gleam/erlang/process.gleam", 90). +?DOC(false). +-spec unsafely_create_subject(pid_(), gleam@dynamic:dynamic_()) -> subject(any()). +unsafely_create_subject(Owner, Tag) -> + {subject, Owner, Tag}. + +-file("src/gleam/erlang/process.gleam", 136). +?DOC( + " Generate a new name that a process can register itself with using the\n" + " `register` function, and other processes can send messages to using\n" + " `named_subject`.\n" + "\n" + " The string argument is a prefix for the Erlang name. A unique suffix is\n" + " added to the prefix to make the name, removing the possibility of name\n" + " collisions.\n" + "\n" + " ## Safe use\n" + "\n" + " Use this function to create all the names your program needs when it\n" + " starts. **Never call this function dynamically** such as within a loop or\n" + " within a process within a supervision tree.\n" + "\n" + " Each time this function is called a new atom will be generated. Generating\n" + " too many atoms will result in the atom table getting filled and causing the\n" + " entire virtual machine to crash.\n" +). +-spec new_name(binary()) -> name(any()). +new_name(Prefix) -> + gleam_erlang_ffi:new_name(Prefix). + +-file("src/gleam/erlang/process.gleam", 143). +?DOC( + " Create a subject for a name, which can be used to send and receive messages.\n" + "\n" + " All subjects created for the same name behave identically and can be used\n" + " interchangably.\n" +). +-spec named_subject(name(DVK)) -> subject(DVK). +named_subject(Name) -> + {named_subject, Name}. + +-file("src/gleam/erlang/process.gleam", 149). +?DOC(" Get the name of a subject, returning an error if it doesn't have one.\n"). +-spec subject_name(subject(DVN)) -> {ok, name(DVN)} | {error, nil}. +subject_name(Subject) -> + case Subject of + {named_subject, Name} -> + {ok, Name}; + + {subject, _, _} -> + {error, nil} + end. + +-file("src/gleam/erlang/process.gleam", 247). +?DOC( + " Receive a message that has been sent to current process using the `Subject`.\n" + "\n" + " If there is not an existing message for the `Subject` in the process'\n" + " mailbox or one does not arrive `within` the permitted timeout then the\n" + " `Error(Nil)` is returned.\n" + "\n" + " Only the process that is owner of the `Subject` can receive a message using\n" + " it. If a process that does not own the `Subject` attempts to receive with it\n" + " then it will not receive a message.\n" + "\n" + " To wait for messages from multiple `Subject`s at the same time see the\n" + " `Selector` type.\n" + "\n" + " The `within` parameter specifies the timeout duration in milliseconds.\n" + "\n" + " ## Panics\n" + "\n" + " This function will panic if a process tries to receive with a non-named\n" + " subject that it does not own.\n" +). +-spec 'receive'(subject(DWB), integer()) -> {ok, DWB} | {error, nil}. +'receive'(Subject, Timeout) -> + case Subject of + {named_subject, _} -> + gleam_erlang_ffi:'receive'(Subject, Timeout); + + {subject, Owner, _} -> + case Owner =:= erlang:self() of + true -> + gleam_erlang_ffi:'receive'(Subject, Timeout); + + false -> + erlang:error(#{gleam_error => panic, + message => <<"Cannot receive with a subject owned by another process"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"receive"/utf8>>, + line => 257}) + end + end. + +-file("src/gleam/erlang/process.gleam", 272). +?DOC( + " Receive a message that has been sent to current process using the `Subject`.\n" + "\n" + " Same as `receive` but waits forever and returns the message as is.\n" +). +-spec receive_forever(subject(DWJ)) -> DWJ. +receive_forever(Subject) -> + gleam_erlang_ffi:'receive'(Subject). + +-file("src/gleam/erlang/process.gleam", 301). +?DOC( + " Create a new `Selector` which can be used to receive messages on multiple\n" + " `Subject`s at once.\n" +). +-spec new_selector() -> selector(any()). +new_selector() -> + gleam_erlang_ffi:new_selector(). + +-file("src/gleam/erlang/process.gleam", 321). +?DOC( + " Receive a message that has been sent to current process using any of the\n" + " `Subject`s that have been added to the `Selector` with the `select*`\n" + " functions.\n" + "\n" + " If there is not an existing message for the `Selector` in the process'\n" + " mailbox or one does not arrive `within` the permitted timeout then the\n" + " `Error(Nil)` is returned.\n" + "\n" + " Only the process that is owner of the `Subject`s can receive a message using\n" + " them. If a process that does not own the a `Subject` attempts to receive\n" + " with it then it will not receive a message.\n" + "\n" + " To wait forever for the next message rather than for a limited amount of\n" + " time see the `selector_receive_forever` function.\n" + "\n" + " The `within` parameter specifies the timeout duration in milliseconds.\n" +). +-spec selector_receive(selector(DWN), integer()) -> {ok, DWN} | {error, nil}. +selector_receive(From, Within) -> + gleam_erlang_ffi:select(From, Within). + +-file("src/gleam/erlang/process.gleam", 330). +?DOC( + " Similar to the `select` function but will wait forever for a message to\n" + " arrive rather than timing out after a specified amount of time.\n" +). +-spec selector_receive_forever(selector(DWR)) -> DWR. +selector_receive_forever(From) -> + gleam_erlang_ffi:select(From). + +-file("src/gleam/erlang/process.gleam", 339). +?DOC( + " Add a transformation function to a selector. When a message is received\n" + " using this selector the transformation function is applied to the message.\n" + "\n" + " This function can be used to change the type of messages received and may\n" + " be useful when combined with the `merge_selector` function.\n" +). +-spec map_selector(selector(DWT), fun((DWT) -> DWV)) -> selector(DWV). +map_selector(A, B) -> + gleam_erlang_ffi:map_selector(A, B). + +-file("src/gleam/erlang/process.gleam", 348). +?DOC( + " Merge one selector into another, producing a selector that contains the\n" + " message handlers of both.\n" + "\n" + " If a subject is handled by both selectors the handler function of the\n" + " second selector is used.\n" +). +-spec merge_selector(selector(DWX), selector(DWX)) -> selector(DWX). +merge_selector(A, B) -> + gleam_erlang_ffi:merge_selector(A, B). + +-file("src/gleam/erlang/process.gleam", 384). +?DOC( + " Discard all messages in the current process' mailbox.\n" + "\n" + " Warning: This function may cause other processes to crash if they sent a\n" + " message to the current process and are waiting for a response, so use with\n" + " caution.\n" + "\n" + " This function may be useful in tests.\n" +). +-spec flush_messages() -> nil. +flush_messages() -> + gleam_erlang_ffi:flush_messages(). + +-file("src/gleam/erlang/process.gleam", 411). +?DOC( + " Add a new `Subject` to the `Selector` so that its messages can be selected\n" + " from the receiver process inbox.\n" + "\n" + " The `mapping` function provided with the `Subject` can be used to convert\n" + " the type of messages received using this `Subject`. This is useful for when\n" + " you wish to add multiple `Subject`s to a `Selector` when they have differing\n" + " message types. If you do not wish to transform the incoming messages in any\n" + " way then the `identity` function can be given.\n" + "\n" + " See `deselect` to remove a subject from a selector.\n" +). +-spec select_map(selector(DXI), subject(DXK), fun((DXK) -> DXI)) -> selector(DXI). +select_map(Selector, Subject, Transform) -> + Handler = fun(Message) -> Transform(erlang:element(2, Message)) end, + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Name, 2}, + Handler + ); + + {subject, _, Tag} -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Tag, 2}, + Handler + ) + end. + +-file("src/gleam/erlang/process.gleam", 393). +?DOC( + " Add a new `Subject` to the `Selector` so that its messages can be selected\n" + " from the receiver process inbox.\n" + "\n" + " See `select_map` to add subjects of a different message type.\n" + "\n" + " See `deselect` to remove a subject from a selector.\n" +). +-spec select(selector(DXE), subject(DXE)) -> selector(DXE). +select(Selector, Subject) -> + select_map(Selector, Subject, fun(X) -> X end). + +-file("src/gleam/erlang/process.gleam", 447). +?DOC( + " Add a handler to a selector for tuple messages with a given tag in the\n" + " first position followed by a given number of fields.\n" + "\n" + " Typically you want to use the `select` function with a `Subject` instead,\n" + " but this function may be useful if you need to receive messages sent from\n" + " other BEAM languages that do not use the `Subject` type.\n" + "\n" + " This will not select messages sent via a subject even if the message has\n" + " the same tag in the first position. This is because when a message is sent\n" + " via a subject a new tag is used that is unique and specific to that subject.\n" +). +-spec select_record( + selector(DXS), + any(), + integer(), + fun((gleam@dynamic:dynamic_()) -> DXS) +) -> selector(DXS). +select_record(Selector, Tag, Arity, Transform) -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {Tag, Arity + 1}, + Transform + ). + +-file("src/gleam/erlang/process.gleam", 467). +?DOC( + " Add a catch-all handler to a selector that will be used when no other\n" + " handler in a selector is suitable for a given message.\n" + "\n" + " This may be useful for when you want to ensure that any message in the inbox\n" + " is handled, or when you need to handle messages from other BEAM languages\n" + " which do not use subjects or record format messages.\n" +). +-spec select_other(selector(DXW), fun((gleam@dynamic:dynamic_()) -> DXW)) -> selector(DXW). +select_other(Selector, Handler) -> + gleam_erlang_ffi:insert_selector_handler(Selector, anything, Handler). + +-file("src/gleam/erlang/process.gleam", 426). +?DOC( + " Remove a new `Subject` from the `Selector` so that its messages will not be\n" + " selected from the receiver process inbox.\n" +). +-spec deselect(selector(DXN), subject(any())) -> selector(DXN). +deselect(Selector, Subject) -> + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:remove_selector_handler(Selector, {Name, 2}); + + {subject, _, Tag} -> + gleam_erlang_ffi:remove_selector_handler(Selector, {Tag, 2}) + end. + +-file("src/gleam/erlang/process.gleam", 491). +?DOC( + " Suspends the process calling this function for the specified number of\n" + " milliseconds.\n" +). +-spec sleep(integer()) -> nil. +sleep(A) -> + gleam_erlang_ffi:sleep(A). + +-file("src/gleam/erlang/process.gleam", 498). +?DOC( + " Suspends the process forever! This may be useful for suspending the main\n" + " process in a Gleam program when it has no more work to do but we want other\n" + " processes to continue to work.\n" +). +-spec sleep_forever() -> nil. +sleep_forever() -> + gleam_erlang_ffi:sleep_forever(). + +-file("src/gleam/erlang/process.gleam", 507). +?DOC( + " Check to see whether the process for a given `Pid` is alive.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#is_process_alive-1\n" +). +-spec is_alive(pid_()) -> boolean(). +is_alive(A) -> + erlang:is_process_alive(A). + +-file("src/gleam/erlang/process.gleam", 537). +?DOC( + " Start monitoring a process so that when the monitored process exits a\n" + " message is sent to the monitoring process.\n" + "\n" + " The message is only sent once, when the target process exits. If the\n" + " process was not alive when this function is called the message will never\n" + " be received.\n" + "\n" + " The down message can be received with a selector and the\n" + " `select_monitors` function.\n" + "\n" + " The process can be demonitored with the `demonitor_process` function.\n" +). +-spec monitor(pid_()) -> monitor(). +monitor(Pid) -> + erlang:monitor(process, Pid). + +-file("src/gleam/erlang/process.gleam", 550). +?DOC( + " Select for a message sent for a given monitor.\n" + "\n" + " Each monitor handler added to a selector has a select performance cost,\n" + " so prefer [`select_monitors`](#select_monitors) if you are select\n" + " for multiple monitors.\n" + "\n" + " The handler can be removed from the selector later using\n" + " [`deselect_specific_monitor`](#deselect_specific_monitor).\n" +). +-spec select_specific_monitor(selector(DYI), monitor(), fun((down()) -> DYI)) -> selector(DYI). +select_specific_monitor(Selector, Monitor, Mapping) -> + gleam_erlang_ffi:insert_selector_handler(Selector, Monitor, Mapping). + +-file("src/gleam/erlang/process.gleam", 564). +?DOC( + " Select for any messages sent for any monitors set up by the select process.\n" + "\n" + " If you want to select for a specific message then use \n" + " [`select_specific_monitor`](#select_specific_monitor), but this\n" + " function is preferred if you need to select for multiple monitors.\n" +). +-spec select_monitors(selector(DYL), fun((down()) -> DYL)) -> selector(DYL). +select_monitors(Selector, Mapping) -> + gleam_erlang_ffi:insert_selector_handler( + Selector, + {erlang:binary_to_atom(<<"DOWN"/utf8>>), 5}, + fun(Message) -> Mapping(gleam_erlang_ffi:cast_down_message(Message)) end + ). + +-file("src/gleam/erlang/process.gleam", 364). +?DOC( + " Add a handler for trapped exit messages. In order for these messages to be\n" + " sent to the process when a linked process exits the process must call the\n" + " `trap_exit` beforehand.\n" +). +-spec select_trapped_exits(selector(DXB), fun((exit_message()) -> DXB)) -> selector(DXB). +select_trapped_exits(Selector, Handler) -> + Tag = erlang:binary_to_atom(<<"EXIT"/utf8>>), + Handler@1 = fun(Message) -> + Handler( + {exit_message, + erlang:element(2, Message), + gleam_erlang_ffi:cast_exit_reason(erlang:element(3, Message))} + ) + end, + gleam_erlang_ffi:insert_selector_handler(Selector, {Tag, 3}, Handler@1). + +-file("src/gleam/erlang/process.gleam", 585). +?DOC( + " Remove the monitor for a process so that when the monitor process exits a\n" + " `Down` message is not sent to the monitoring process.\n" + "\n" + " If the message has already been sent it is removed from the monitoring\n" + " process' mailbox.\n" +). +-spec demonitor_process(monitor()) -> nil. +demonitor_process(Monitor) -> + gleam_erlang_ffi:demonitor(Monitor), + nil. + +-file("src/gleam/erlang/process.gleam", 598). +?DOC( + " Remove a `Monitor` from a `Selector` prevoiusly added by\n" + " [`select_specific_monitor`](#select_specific_monitor). If\n" + " the `Monitor` is not in the `Selector` it will be returned\n" + " unchanged.\n" +). +-spec deselect_specific_monitor(selector(DYO), monitor()) -> selector(DYO). +deselect_specific_monitor(Selector, Monitor) -> + gleam_erlang_ffi:remove_selector_handler(Selector, Monitor). + +-file("src/gleam/erlang/process.gleam", 729). +?DOC( + " Creates a link between the calling process and another process.\n" + "\n" + " When a process crashes any linked processes will also crash. This is useful\n" + " to ensure that groups of processes that depend on each other all either\n" + " succeed or fail together.\n" + "\n" + " Returns `True` if the link was created successfully, returns `False` if the\n" + " process was not alive and as such could not be linked.\n" +). +-spec link(pid_()) -> boolean(). +link(Pid) -> + gleam_erlang_ffi:link(Pid). + +-file("src/gleam/erlang/process.gleam", 736). +?DOC(" Removes any existing link between the caller process and the target process.\n"). +-spec unlink(pid_()) -> nil. +unlink(Pid) -> + erlang:unlink(Pid), + nil. + +-file("src/gleam/erlang/process.gleam", 751). +?DOC(" Send a message over a channel after a specified number of milliseconds.\n"). +-spec send_after(subject(DZK), integer(), DZK) -> timer(). +send_after(Subject, Delay, Message) -> + case Subject of + {named_subject, Name} -> + erlang:send_after(Delay, Name, {Name, Message}); + + {subject, Owner, Tag} -> + erlang:send_after(Delay, Owner, {Tag, Message}) + end. + +-file("src/gleam/erlang/process.gleam", 158). +?DOC(" Create a new `Subject` owned by the current process.\n"). +-spec new_subject() -> subject(any()). +new_subject() -> + {subject, erlang:self(), gleam_erlang_ffi:identity(erlang:make_ref())}. + +-file("src/gleam/erlang/process.gleam", 781). +?DOC(" Cancel a given timer, causing it not to trigger if it has not done already.\n"). +-spec cancel_timer(timer()) -> cancelled(). +cancel_timer(Timer) -> + case gleam@dynamic@decode:run( + erlang:cancel_timer(Timer), + {decoder, fun gleam@dynamic@decode:decode_int/1} + ) of + {ok, I} -> + {cancelled, I}; + + {error, _} -> + timer_not_found + end. + +-file("src/gleam/erlang/process.gleam", 805). +?DOC( + " Send an untrappable `kill` exit signal to the target process.\n" + "\n" + " See the documentation for the Erlang [`erlang:exit`][1] function for more\n" + " information.\n" + "\n" + " [1]: https://erlang.org/doc/man/erlang.html#exit-1\n" +). +-spec kill(pid_()) -> nil. +kill(Pid) -> + erlang:exit(Pid, kill), + nil. + +-file("src/gleam/erlang/process.gleam", 821). +?DOC( + " Sends an exit signal to a process, indicating that the process is to shut\n" + " down.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#exit-2\n" +). +-spec send_exit(pid_()) -> nil. +send_exit(Pid) -> + erlang:exit(Pid, normal), + nil. + +-file("src/gleam/erlang/process.gleam", 833). +?DOC( + " Sends an exit signal to a process, indicating that the process is to shut\n" + " down due to an abnormal reason such as a failure.\n" + "\n" + " See the [Erlang documentation][1] for more information.\n" + "\n" + " [1]: http://erlang.org/doc/man/erlang.html#exit-2\n" +). +-spec send_abnormal_exit(pid_(), any()) -> nil. +send_abnormal_exit(Pid, Reason) -> + erlang:exit(Pid, Reason), + nil. + +-file("src/gleam/erlang/process.gleam", 849). +?DOC( + " Set whether the current process is to trap exit signals or not.\n" + "\n" + " When not trapping exits if a linked process crashes the exit signal\n" + " propagates to the process which will also crash.\n" + " This is the normal behaviour before this function is called.\n" + "\n" + " When trapping exits (after this function is called) if a linked process\n" + " crashes an exit message is sent to the process instead. These messages can\n" + " be handled with the `select_trapped_exits` function.\n" +). +-spec trap_exits(boolean()) -> nil. +trap_exits(A) -> + gleam_erlang_ffi:trap_exits(A). + +-file("src/gleam/erlang/process.gleam", 860). +?DOC( + " Register a process under a given name, allowing it to be looked up using\n" + " the `named` function.\n" + "\n" + " This function will return an error under the following conditions:\n" + " - The process for the pid no longer exists.\n" + " - The name has already been registered.\n" + " - The process already has a name.\n" +). +-spec register(pid_(), name(any())) -> {ok, nil} | {error, nil}. +register(Pid, Name) -> + gleam_erlang_ffi:register_process(Pid, Name). + +-file("src/gleam/erlang/process.gleam", 871). +?DOC( + " Un-register a process name, after which the process can no longer be looked\n" + " up by that name, and both the name and the process can be re-used in other\n" + " registrations.\n" + "\n" + " It is possible to un-register process that are not from your application,\n" + " including those from Erlang/OTP itself. This is not recommended and will\n" + " likely result in undesirable behaviour and crashes.\n" +). +-spec unregister(name(any())) -> {ok, nil} | {error, nil}. +unregister(Name) -> + gleam_erlang_ffi:unregister_process(Name). + +-file("src/gleam/erlang/process.gleam", 876). +?DOC(" Look up a process by registered name, returning the pid if it exists.\n"). +-spec named(name(any())) -> {ok, pid_()} | {error, nil}. +named(Name) -> + gleam_erlang_ffi:process_named(Name). + +-file("src/gleam/erlang/process.gleam", 168). +?DOC( + " Get the owner process for a subject, which is the process that will\n" + " receive any messages sent using the subject.\n" + "\n" + " If the subject was created from a name and no process is currently\n" + " registered with that name then this function will return an error.\n" +). +-spec subject_owner(subject(any())) -> {ok, pid_()} | {error, nil}. +subject_owner(Subject) -> + case Subject of + {named_subject, Name} -> + gleam_erlang_ffi:process_named(Name); + + {subject, Pid, _} -> + {ok, Pid} + end. + +-file("src/gleam/erlang/process.gleam", 214). +?DOC( + " Send a message to a process using a `Subject`. The message must be of the\n" + " type that the `Subject` accepts.\n" + "\n" + " This function does not wait for the `Subject` owner process to call the\n" + " `receive` function, instead it returns once the message has been placed in\n" + " the process' mailbox.\n" + " \n" + " # Named Subjects\n" + " \n" + " If this function is called on a named subject for which a process has not been \n" + " registered, it will simply drop the message as there's no mailbox to send it to.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic when sending to a named subject if no process is\n" + " currently registed under that name.\n" + "\n" + " # Ordering\n" + "\n" + " If process P1 sends two messages to process P2 it is guaranteed that process\n" + " P1 will receive the messages in the order they were sent.\n" + "\n" + " If you wish to receive the messages in a different order you can send them\n" + " on two different subjects and the receiver function can call the `receive`\n" + " function for each subject in the desired order, or you can write some Erlang\n" + " code to perform a selective receive.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let subject = new_subject()\n" + " send(subject, \"Hello, Joe!\")\n" + " ```\n" +). +-spec send(subject(DVZ), DVZ) -> nil. +send(Subject, Message) -> + case Subject of + {subject, Pid, Tag} -> + erlang:send(Pid, {Tag, Message}); + + {named_subject, Name} -> + Pid@2 = case gleam_erlang_ffi:process_named(Name) of + {ok, Pid@1} -> Pid@1; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Sending to unregistered name"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"send"/utf8>>, + line => 220, + value => _assert_fail, + start => 8194, + 'end' => 8226, + pattern_start => 8205, + pattern_end => 8212}) + end, + erlang:send(Pid@2, {Name, Message}) + end, + nil. + +-file("src/gleam/erlang/process.gleam", 605). +-spec perform_call( + subject(DYR), + fun((subject(DYT)) -> DYR), + fun((selector(DYT)) -> {ok, DYT} | {error, nil}) +) -> DYT. +perform_call(Subject, Make_request, Run_selector) -> + Reply_subject = new_subject(), + Callee@1 = case subject_owner(Subject) of + {ok, Callee} -> Callee; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Callee subject had no owner"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 611, + value => _assert_fail, + start => 21173, + 'end' => 21219, + pattern_start => 21184, + pattern_end => 21194}) + end, + Monitor = monitor(Callee@1), + send(Subject, Make_request(Reply_subject)), + Reply = begin + _pipe = gleam_erlang_ffi:new_selector(), + _pipe@1 = select(_pipe, Reply_subject), + _pipe@2 = select_specific_monitor( + _pipe@1, + Monitor, + fun(Down) -> erlang:error(#{gleam_error => panic, + message => (<<"callee exited: "/utf8, + (gleam@string:inspect(Down))/binary>>), + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 626}) end + ), + Run_selector(_pipe@2) + end, + Reply@2 = case Reply of + {ok, Reply@1} -> Reply@1; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"callee did not send reply before timeout"/utf8>>, + file => <>, + module => <<"gleam/erlang/process"/utf8>>, + function => <<"perform_call"/utf8>>, + line => 630, + value => _assert_fail@1, + start => 21766, + 'end' => 21794, + pattern_start => 21777, + pattern_end => 21786}) + end, + demonitor_process(Monitor), + Reply@2. + +-file("src/gleam/erlang/process.gleam", 695). +?DOC( + " Send a message to a process and wait a given number of milliseconds for a\n" + " reply.\n" + "\n" + " ## Panics\n" + "\n" + " This function will panic under the following circumstances:\n" + " - The callee process exited prior to sending a reply.\n" + " - The callee process did not send a reply within the permitted amount of\n" + " time.\n" + " - The subject is a named subject but no process is registered with that\n" + " name.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " pub type Message {\n" + " // This message variant is to be used with `call`.\n" + " // The `reply` field contains a subject that the reply message will be\n" + " // sent over.\n" + " SayHello(reply_to: Subject(String), name: String)\n" + " }\n" + " \n" + " // Typically we make public functions that hide the details of a process'\n" + " // message-based API.\n" + " pub fn say_hello(subject: Subject(Message), name: String) -> String {\n" + " // The `SayHello` message constructor is given _partially applied_ with\n" + " // all the arguments except the reply subject, which will be supplied by\n" + " // the `call` function itself before sending the message.\n" + " process.call(subject, 100, SayHello(_, name))\n" + " }\n" + "\n" + " // This is the message handling logic used by the process that owns the\n" + " // subject, and so receives the messages. In a real project it would be\n" + " // within a process or some higher level abstraction like an actor, but for\n" + " // this demonstration that has been omitted.\n" + " pub fn handle_message(message: Message) -> Nil {\n" + " case message {\n" + " SayHello(reply:, name:) -> {\n" + " let data = \"Hello, \" <> name <> \"!\"\n" + " // The reply subject is used to send the response back.\n" + " // If the receiver process does not sent a reply in time then the\n" + " // caller will crash.\n" + " process.send(reply, data)\n" + " }\n" + " }\n" + " }\n" + "\n" + " // Here is what it looks like using the functional API to call the process.\n" + " pub fn run(subject: Subject(Message)) {\n" + " say_hello(subject, \"Lucy\")\n" + " // -> \"Hello, Lucy!\"\n" + " say_hello(subject, \"Nubi\")\n" + " // -> \"Hello, Nubi!\"\n" + " }\n" + " ```\n" +). +-spec call(subject(DYY), integer(), fun((subject(DZA)) -> DYY)) -> DZA. +call(Subject, Timeout, Make_request) -> + perform_call( + Subject, + Make_request, + fun(_capture) -> gleam_erlang_ffi:select(_capture, Timeout) end + ). + +-file("src/gleam/erlang/process.gleam", 712). +?DOC( + " Send a message to a process and wait for a reply.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic under the following circumstances:\n" + " - The callee process exited prior to sending a reply.\n" + " - The subject is a named subject but no process is registered with that\n" + " name.\n" +). +-spec call_forever(subject(DZC), fun((subject(DZE)) -> DZC)) -> DZE. +call_forever(Subject, Make_request) -> + perform_call( + Subject, + Make_request, + fun(S) -> {ok, gleam_erlang_ffi:select(S)} end + ). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@reference.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@reference.erl new file mode 100644 index 0000000..a393fa6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam@erlang@reference.erl @@ -0,0 +1,21 @@ +-module(gleam@erlang@reference). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/erlang/reference.gleam"). +-export([new/0]). +-export_type([reference_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type reference_() :: any(). + +-file("src/gleam/erlang/reference.gleam", 15). +?DOC(" Create a new unique reference.\n"). +-spec new() -> reference_(). +new() -> + erlang:make_ref(). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang.app.src b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang.app.src new file mode 100644 index 0000000..28f3f46 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang.app.src @@ -0,0 +1,16 @@ +{application, gleam_erlang, [ + {vsn, "1.3.0"}, + {applications, [gleam_stdlib]}, + {description, "Types and functions for programs running on Erlang!"}, + {modules, [gleam@erlang@application, + gleam@erlang@atom, + gleam@erlang@charlist, + gleam@erlang@node, + gleam@erlang@port, + gleam@erlang@process, + gleam@erlang@reference, + gleam_erlang@@main, + gleam_erlang_ffi, + gleam_erlang_test_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl new file mode 100644 index 0000000..b1ad710 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_erlang/src/gleam_erlang_ffi.erl @@ -0,0 +1,164 @@ +-module(gleam_erlang_ffi). +-export([ + atom_from_string/1, sleep/1, sleep_forever/0, demonitor/1, new_selector/0, + link/1, insert_selector_handler/3, remove_selector_handler/2, select/1, + select/2, trap_exits/1, map_selector/2, merge_selector/2, flush_messages/0, + priv_directory/1, connect_node/1, register_process/2, unregister_process/1, + process_named/1, identity/1, 'receive'/1, 'receive'/2, new_name/1, + cast_down_message/1, cast_exit_reason/1 +]). + +-spec atom_from_string(binary()) -> {ok, atom()} | {error, nil}. +atom_from_string(S) -> + try {ok, binary_to_existing_atom(S)} + catch error:badarg -> {error, nil} + end. + +new_name(Prefix) -> + Suffix = integer_to_binary(erlang:unique_integer([positive])), + Name = <>, + binary_to_atom(Name). + +sleep(Microseconds) -> + timer:sleep(Microseconds), + nil. + +sleep_forever() -> + timer:sleep(infinity), + nil. + +new_selector() -> + {selector, #{}}. + +map_selector({selector, Handlers}, Fn) -> + MappedHandlers = maps:map(fun(_Tag, Handler) -> + fun(Message) -> Fn(Handler(Message)) end + end, Handlers), + {selector, MappedHandlers}. + +merge_selector({selector, HandlersA}, {selector, HandlersB}) -> + {selector, maps:merge(HandlersA, HandlersB)}. + +insert_selector_handler({selector, Handlers}, Tag, Fn) -> + {selector, Handlers#{Tag => Fn}}. + +remove_selector_handler({selector, Handlers}, Tag) -> + {selector, maps:remove(Tag, Handlers)}. + +select(Selector) -> + {ok, Message} = select(Selector, infinity), + Message. + +select({selector, Handlers}, Timeout) -> + AnythingHandler = maps:get(anything, Handlers, undefined), + receive + % Monitored process down messages. + % This is special cased so we can selectively receive based on the + % reference as well as the record tag. + {'DOWN', Ref, _, _, _} = Message when is_map_key(Ref, Handlers) -> + Fn = maps:get(Ref, Handlers), + {ok, Fn(cast_down_message(Message))}; + + Msg when is_map_key({element(1, Msg), tuple_size(Msg)}, Handlers) -> + Fn = maps:get({element(1, Msg), tuple_size(Msg)}, Handlers), + {ok, Fn(Msg)}; + + Msg when AnythingHandler =/= undefined -> + {ok, AnythingHandler(Msg)} + after Timeout -> + {error, nil} + end. + +cast_exit_reason(normal) -> normal; +cast_exit_reason(killed) -> killed; +cast_exit_reason(Other) -> {abnormal, Other}. + +cast_down_message({'DOWN', Ref, process, Pid, Reason}) -> + {process_down, Ref, Pid, cast_exit_reason(Reason)}; +cast_down_message({'DOWN', Ref, port, Pid, Reason}) -> + {port_down, Ref, Pid, cast_exit_reason(Reason)}. + + +'receive'({subject, _Pid, Ref}) -> + receive + {Ref, Message} -> Message + end; +'receive'({named_subject, Name}) -> + receive + {Name, Message} -> Message + end. + +'receive'({subject, _Pid, Ref}, Timeout) -> + receive + {Ref, Message} -> {ok, Message} + after Timeout -> + {error, nil} + end; +'receive'({named_subject, Name}, Timeout) -> + receive + {Name, Message} -> {ok, Message} + after Timeout -> + {error, nil} + end. + +demonitor(Reference) -> + erlang:demonitor(Reference, [flush]). + +link(Pid) -> + try + erlang:link(Pid) + catch + error:_ -> false + end. + +trap_exits(ShouldTrap) -> + erlang:process_flag(trap_exit, ShouldTrap), + nil. + +flush_messages() -> + receive _Message -> flush_messages() + after 0 -> nil + end. + +priv_directory(Name) -> + try erlang:binary_to_existing_atom(Name) of + Atom -> + case code:priv_dir(Atom) of + {error, _} -> {error, nil}; + Path -> {ok, unicode:characters_to_binary(Path)} + end + catch + error:badarg -> {error, nil} + end. + +connect_node(Node) -> + case net_kernel:connect_node(Node) of + true -> {ok, Node}; + false -> {error, failed_to_connect}; + ignored -> {error, local_node_is_not_alive} + end. + +register_process(Pid, Name) -> + try + true = erlang:register(Name, Pid), + {ok, nil} + catch + error:badarg -> {error, nil} + end. + +unregister_process(Name) -> + try + true = erlang:unregister(Name), + {ok, nil} + catch + error:badarg -> {error, nil} + end. + +process_named(Name) -> + case erlang:whereis(Name) of + Pid when is_pid(Pid) -> {ok, Pid}; + _ -> {error, nil} + end. + +identity(X) -> + X. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/LICENCE b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/LICENCE new file mode 100644 index 0000000..c1dabd0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2018, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/README.md b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/README.md new file mode 100644 index 0000000..2951442 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/README.md @@ -0,0 +1,34 @@ +# stdlib + +[![Package Version](https://img.shields.io/hexpm/v/gleam_stdlib)](https://hex.pm/packages/gleam_stdlib) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_stdlib/) +[![Discord chat](https://img.shields.io/discord/768594524158427167?color=blue)](https://discord.gg/Fm8Pwmy) + +Gleam's standard library! +Documentation available on [HexDocs](https://hexdocs.pm/gleam_stdlib/). + +## Installation + +Add `gleam_stdlib` to your Gleam project. + +```sh +gleam add gleam_stdlib@1 +``` +```gleam +import gleam/io + +pub fn greet(name: String) -> Nil { + io.println("Hello " <> name <> "!") +} +``` + +## Targets + +Gleam's standard library supports both targets: Erlang and JavaScript. + +### Compatibility + +This library is compatible with all versions of Erlang/OTP 26 and higher, +as well as all NodeJS, Deno, Bun, and major browsers that are currently +supported by their maintainers. If you have a compatibility issue with +any platform open an issue and we'll see what we can do to help. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/gleam.toml b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/gleam.toml new file mode 100644 index 0000000..b641806 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/gleam.toml @@ -0,0 +1,14 @@ +name = "gleam_stdlib" +version = "1.0.0" +gleam = ">= 1.14.0" +licences = ["Apache-2.0"] +description = "A standard library for the Gleam programming language" + +repository = { type = "github", user = "gleam-lang", repo = "stdlib" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[javascript.deno] +allow_read = ["./"] diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl new file mode 100644 index 0000000..b1135f2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl @@ -0,0 +1,5 @@ +-record(decode_error, { + expected :: binary(), + found :: binary(), + path :: list(binary()) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl new file mode 100644 index 0000000..a2fcfc6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl @@ -0,0 +1,4 @@ +-record(decoder, { + function :: fun((gleam@dynamic:dynamic_()) -> {any(), + list(gleam@dynamic@decode:decode_error())}) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@set_Set.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@set_Set.hrl new file mode 100644 index 0000000..51fb778 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@set_Set.hrl @@ -0,0 +1 @@ +-record(set, {dict :: gleam@dict:dict(any(), list(nil))}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl new file mode 100644 index 0000000..50150f4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl @@ -0,0 +1,9 @@ +-record(uri, { + scheme :: gleam@option:option(binary()), + userinfo :: gleam@option:option(binary()), + host :: gleam@option:option(binary()), + port :: gleam@option:option(integer()), + path :: binary(), + 'query' :: gleam@option:option(binary()), + fragment :: gleam@option:option(binary()) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/dict.mjs b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/dict.mjs new file mode 100644 index 0000000..3db3b5f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/dict.mjs @@ -0,0 +1,710 @@ +/** + * This file uses jsdoc to annotate types. + * These types can be checked using the typescript compiler with "checkjs" option. + */ + +import { isEqual, Result$Error, Result$Ok } from "./gleam.mjs"; + +// -- HASH -------------------------------------------------------------------- + +const referenceMap = /* @__PURE__ */ new WeakMap(); +const tempDataView = /* @__PURE__ */ new DataView( + /* @__PURE__ */ new ArrayBuffer(8), +); +let referenceUID = 0; +/** + * hash the object by reference using a weak map and incrementing uid + * @param {any} o + * @returns {number} + */ +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== undefined) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 0x7fffffff) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} + +/** + * merge two hashes in an order sensitive way + * @param {number} a + * @param {number} b + * @returns {number} + */ +function hashMerge(a, b) { + return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0; +} + +/** + * standard string hash popularised by Java + * @param {string} s + * @returns {number} + */ +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0; i < len; i++) { + hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0; + } + return hash; +} + +/** + * hash a number by converting to two integers and do some jumbling + * @param {number} n + * @returns {number} + */ +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j; +} + +/** + * hash a BigInt by converting it to a string and hashing that + * @param {BigInt} n + * @returns {number} + */ +function hashBigInt(n) { + return hashString(n.toString()); +} + +/** + * hash any js object + * @param {any} o + * @returns {number} + */ +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code = o.hashCode(o); + if (typeof code === "number") { + return code; + } + } catch {} + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0; i < o.length; i++) { + h = (Math.imul(31, h) + getHash(o[i])) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = (h + getHash(v)) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = (h + hashMerge(getHash(v), getHash(k))) | 0; + }); + } else { + const keys = Object.keys(o); + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + const v = o[k]; + h = (h + hashMerge(getHash(v), hashString(k))) | 0; + } + } + return h; +} + +/** + * hash any js value + * @param {any} u + * @returns {number} + */ +export function getHash(u) { + if (u === null) return 0x42108422; + if (u === undefined) return 0x42108423; + if (u === true) return 0x42108421; + if (u === false) return 0x42108420; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; // should be unreachable + } +} + +// -- DICT -------------------------------------------------------------------- + +/** + * An implementation of the CHAMP data structure, an optimised HAMT. + * + * See: M. J. Steindorfer, J.J. Vinju (2015). Optimizing Hash-Array Mapped Tries for + Fast and Lean Immutable JVM Collections. Available: https://michael.steindorfer.name/publications/oopsla15.pdf + */ +export default class Dict { + constructor(size, root) { + this.size = size; + this.root = root; + } +} + +/// The power-of-2 branching factor for the dict. For example, a value of `5` indicates a 32-ary tree. +const bits = 5; +const mask = (1 << bits) - 1; + +/// This symbol is used internally to avoid constructing results. +const noElementMarker = Symbol(); + +/// This symbol is used to store the "generation" on a node. +/// Using a symbol makes the property not enumerable, which means the generation +/// will be ignored during equality checks. +const generationKey = Symbol(); + +// Some commonly used constants throughout the code. +const emptyNode = /* @__PURE__ */ newNode(0); +const emptyDict = /* @__PURE__ */ new Dict(0, emptyNode); +const errorNil = /* @__PURE__ */ Result$Error(undefined); + +function makeNode(generation, datamap, nodemap, data) { + // The order of fields is important, as they define the order `isEqual` will + // compare our fields. Putting the bitmaps first means that equality can + // early-out if the bitmaps are not equal. + return { + // A node is a high-arity (32 in practice) hybrid tree node. + // Hybrid means that it stores data directly as well as pointers to child nodes. + // + // Each node contains 2 bitmaps: + // - The datamap has a bit set if that slot in the node contains direct data + // - The nodemap has a bit set if that slot in the node contains another node. + // + // Both are exclusive to on another, so datamap & nodemap == 0. + // + // Every key/hash value directly correlates to a specific bit by using a trie + // suffix (least significant bits first) encoding. + // For example, if the last 5 bits of the hash are 1101, the bit to check for + // that value is the 13th bit. + datamap, + nodemap, + // The slots itself are stored in a single contiguous array that contains + // both direct k/v-pairs and child nodes. + // + // The direct children come first, followed by the child nodes in _reverse order_: + // + // 7654321 + // datamap: 1000100 + // nodemap: 10011 + // data: [key3, value3, key7, value7, child5, child2, child1] + // -------------------------> <--------------------- + // datamap nodemap + // + // Every `1` bit in the datamap corresponds to a pair of [key, value] entries, + // and every `1` bit in the nodemap corresponds to a child node entry. + // + // Children are stored in reverse order to avoid having to store or calculate an + // "offset" value to skip over the direct children. + data, + // The generation is used to track which nodes need to be copied during transient updates. + // Using a symbol here makes `isEqual` ignore this field. + [generationKey]: generation, + }; +} + +function newNode(generation) { + return makeNode(generation, 0, 0, []); +} + +/** + * Copies a node and its data array if it's from another generation, making it safe + * to mutate the node. + */ +function copyNode(node, generation) { + if (node[generationKey] === generation) { + return node; + } + + const newData = node.data.slice(0); + return makeNode(generation, node.datamap, node.nodemap, newData); +} + +/** + * Copies a node if needed and sets a new value. + */ +function copyAndSet(node, generation, idx, val) { + if (node.data[idx] === val) { + return node; + } + + // Using copyNode is faster than a specialised implementation. + node = copyNode(node, generation); + node.data[idx] = val; + return node; +} + +/** + * Copies a node if needed, and then inserts a new key-value pair. + */ +function copyAndInsertPair(node, generation, bit, idx, key, val) { + const data = node.data; + const length = data.length; + + // the fastest way to insert a pair is to always copy. + const newData = new Array(length + 2); + + let readIndex = 0; + let writeIndex = 0; + + while (readIndex < idx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = key; + newData[writeIndex++] = val; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap | bit, node.nodemap, newData); +} + +function copyAndRemovePair(node, generation, bit, idx) { + node = copyNode(node, generation); + + const data = node.data; + const length = data.length; + for (let w = idx, r = idx + 2; r < length; ++r, ++w) { + data[w] = data[r]; + } + data.pop(); + data.pop(); + + node.datamap ^= bit; + return node; +} + +export function make() { + return emptyDict; +} + +export function from(iterable) { + let transient = toTransient(emptyDict); + for (const [key, value] of iterable) { + transient = destructiveTransientInsert(key, value, transient); + } + return fromTransient(transient); +} + +export function size(dict) { + return dict.size; +} + +export function get(dict, key) { + const result = lookup(dict.root, key, getHash(key)); + return result !== noElementMarker ? Result$Ok(result) : errorNil; +} + +export function has(dict, key) { + return lookup(dict.root, key, getHash(key)) !== noElementMarker; +} + +function lookup(node, key, hash) { + for (let shift = 0; shift < 32; shift += bits) { + const data = node.data; + const bit = hashbit(hash, shift); + + if (node.nodemap & bit) { + // we found our hash inside the nodemap, so we can continue our search there. + node = data[data.length - 1 - index(node.nodemap, bit)]; + } else if (node.datamap & bit) { + // we store this hash directly! + // + // this also means that there are no other values with the same + // hash prefix in this dict. + // + // We still need to check if the key matches, but if it does we know for + // sure that this is the correct value, and if it doesn't that we don't + // contain the value in question. + const dataidx = Math.imul(index(node.datamap, bit), 2); + return isEqual(key, data[dataidx]) ? data[dataidx + 1] : noElementMarker; + } else { + // if the hash bit is not set in neither bitmaps, we immediately know that + // this key cannot be inside this dict. + return noElementMarker; + } + } + + // our shift has exceeded 32 bits. Everything that follows is + // implicitely an overflow node and only contains direct children. + const overflow = node.data; + for (let i = 0; i < overflow.length; i += 2) { + if (isEqual(key, overflow[i])) { + return overflow[i + 1]; + } + } + + return noElementMarker; +} + +/** + * We use "transient" values to allow for safer internal mutations of the data + * structure. This is an optimisation only. No mutable API is exposed to the user. + * + * Transients are to be treated as having a linear (single-use, think rust) type. + * A transient value becomes invalid as soon as it's passed to one of the functions. + * + * Internally, we track a "generation" value on each node. If the generation + * doesn't match the one for the current transient, we have to copy - the node + * could still be referenced by another dict instance! + * After that, no other references than the transient one exists, so it's safe + * to mutate in place. + */ +export function toTransient(dict) { + return { + generation: nextGeneration(dict), + root: dict.root, + size: dict.size, + dict: dict, + }; +} + +/** + * Consume a transient, producing a normal Dict again. + */ +export function fromTransient(transient) { + if (transient.root === transient.dict.root) { + return transient.dict; + } + + return new Dict(transient.size, transient.root); +} + +/** + * Find and allocate the next generation id. + * + * @template K,V + * @param {Dict} dict + * @returns {number} + */ +function nextGeneration(dict) { + const root = dict.root; + if (root[generationKey] < Number.MAX_SAFE_INTEGER) { + return root[generationKey] + 1; + } + + // we have reached MAX_SAFE_INTEGER generations - + // at this point, we have to walk the dictionary once to reset the counter + // on every node. This is safe since it's part of the contract for transient + // that only one of them exists at any given time. + // + const queue = [root]; + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + + // reset the generation to 0 + node[generationKey] = 0; + + // queue all other referenced nodes + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const nodeStart = data.length - popcount(node.nodemap); + for (let i = nodeStart; i < node.data.length; ++i) { + queue.push(node.data[i]); + } + } + + return 1; +} + +/// Insert is the second-most performance-sensitive operation. +/// We use a global "transient" value here to avoid doing a memory allocation. +const globalTransient = /* @__PURE__ */ toTransient(emptyDict); + +export function insert(dict, key, value) { + globalTransient.generation = nextGeneration(dict); + globalTransient.size = dict.size; + + const hash = getHash(key); + const root = insertIntoNode(globalTransient, dict.root, key, value, hash, 0); + if (root === dict.root) { + return dict; + } + + return new Dict(globalTransient.size, root); +} + +/** + * Consume a transient, writing a new key/value pair into the dictionary it + * represents. If the key already exists, it will be overwritten. + * + * Returns a new transient. + */ +export function destructiveTransientInsert(key, value, transient) { + const hash = getHash(key); + transient.root = insertIntoNode(transient, transient.root, key, value, hash, 0); + return transient; +} + +/** + * Consume a transient, writing a new key/value pair if the key doesn't exist or updating + * the existing value with a function if it does. + * + * Returns a new transient. + */ +export function destructiveTransientUpdateWith(key, fun, value, transient) { + const hash = getHash(key); + + const existing = lookup(transient.root, key, hash); + if (existing !== noElementMarker) { + value = fun(existing); + } + transient.root = insertIntoNode(transient, transient.root, key, value, hash, 0); + return transient; +} + +function insertIntoNode(transient, node, key, value, hash, shift) { + const data = node.data; + const generation = transient.generation; + + // 1. Overflow Node + // overflow nodes only contain key/value-pairs. we walk the data linearly trying to find a match. + if (shift > 32) { + for (let i = 0; i < data.length; i += 2) { + if (isEqual(key, data[i])) { + return copyAndSet(node, generation, i + 1, value); + } + } + + transient.size += 1; + return copyAndInsertPair(node, generation, 0, data.length, key, value); + } + + const bit = hashbit(hash, shift); + + // 2. Child Node + // We have to check first if there is already a child node we have to traverse to. + if (node.nodemap & bit) { + const nodeidx = data.length - 1 - index(node.nodemap, bit); + + let child = data[nodeidx]; + child = insertIntoNode(transient, child, key, value, hash, shift + bits); + return copyAndSet(node, generation, nodeidx, child); + } + + // 3. New Data Node + // No child node and no data node exists yet, so we can potentially just insert a new value. + const dataidx = Math.imul(index(node.datamap, bit), 2); + if ((node.datamap & bit) === 0) { + transient.size += 1; + return copyAndInsertPair(node, generation, bit, dataidx, key, value); + } + + // 4. Existing Data Node + // We have a match that we can update, or remove. + if (isEqual(key, data[dataidx])) { + return copyAndSet(node, generation, dataidx + 1, value); + } + + // 5. Collision + // There is no child node, but a data node with the same hash, but with a different key. + // To resolve this, we push both nodes down one level. + const childShift = shift + bits; + + let child = emptyNode; + child = insertIntoNode(transient, child, key, value, hash, childShift); + + const key2 = data[dataidx]; + const value2 = data[dataidx + 1]; + const hash2 = getHash(key2); + child = insertIntoNode(transient, child, key2, value2, hash2, childShift); + + // we inserted 2 elements, but implicitely deleted the one we pushed down from the datamap. + transient.size -= 1; + + // remove the old data pair, and insert the new child node. + const length = data.length; + const nodeidx = length - 1 - index(node.nodemap, bit); + + // writing these loops in javascript instead of a combination of splices + // turns out to be faster. Copying always turned out to be faster. + const newData = new Array(length - 1); + + let readIndex = 0; + let writeIndex = 0; + + // [0..dataidx, skip 2 elements, ..nodeidx, newChild, ..rest] + while (readIndex < dataidx) newData[writeIndex++] = data[readIndex++]; + readIndex += 2; + while (readIndex <= nodeidx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = child; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap ^ bit, node.nodemap | bit, newData); +} + +/** + * Consume a transient, removing a key if it exists. + * Returns a new transient. + */ +export function destructiveTransientDelete(key, transient) { + const hash = getHash(key); + transient.root = deleteFromNode(transient, transient.root, key, hash, 0); + return transient; +} + +function deleteFromNode(transient, node, key, hash, shift) { + const data = node.data; + const generation = transient.generation; + + // 1. Overflow Node + // overflow nodes only contain key/value-pairs. we walk the data linearly trying to find a match. + if (shift > 32) { + for (let i = 0; i < data.length; i += 2) { + if (isEqual(key, data[i])) { + transient.size -= 1; + return copyAndRemovePair(node, generation, 0, i); + } + } + + return node; + } + + const bit = hashbit(hash, shift); + const dataidx = Math.imul(index(node.datamap, bit), 2); + + // 2. Child Node + // We have to check first if there is already a child node we have to traverse to. + if ((node.nodemap & bit) !== 0) { + const nodeidx = data.length - 1 - index(node.nodemap, bit); + + let child = data[nodeidx]; + child = deleteFromNode(transient, child, key, hash, shift + bits); + + // the node did change, so let's copy to incorporate that change. + if (child.nodemap !== 0 || child.data.length > 2) { + return copyAndSet(node, generation, nodeidx, child); + } + + // this node only has a single data (k/v-pair) child. + // to restore the CHAMP invariant, we "pull" that pair up into ourselves. + // this ensures that every tree stays in its single optimal representation, + // and allows dicts to be structurally compared. + const length = data.length; + const newData = new Array(length + 1); + + let readIndex = 0; + let writeIndex = 0; + + while (readIndex < dataidx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = child.data[0]; + newData[writeIndex++] = child.data[1]; + while (readIndex < nodeidx) newData[writeIndex++] = data[readIndex++]; + readIndex++; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap | bit, node.nodemap ^ bit, newData); + } + + // 3. Data Node + // There is no data entry here, or it is a prefix for a different key + if ((node.datamap & bit) === 0 || !isEqual(key, data[dataidx])) { + return node; + } + + // we found a data entry that we can delete. + transient.size -= 1; + return copyAndRemovePair(node, generation, bit, dataidx); +} + +export function map(dict, fun) { + // map can never modify the structure, so we can walk the dictionary directly, + // but still move to a new generation to make sure we get a new copy of every node. + const generation = nextGeneration(dict); + const root = copyNode(dict.root, generation); + const queue = [root]; + + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + const data = node.data; + // every node contains popcount(datamap) direct entries + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const edgesStart = data.length - popcount(node.nodemap); + for (let i = 0; i < edgesStart; i += 2) { + // we copied the node while queueing it, so direct mutation here is safe. + data[i + 1] = fun(data[i], data[i + 1]); + } + // the remaining entries are other nodes we can queue + for (let i = edgesStart; i < data.length; ++i) { + // copy the node first to make it safe to mutate + data[i] = copyNode(data[i], generation); + queue.push(data[i]); + } + } + + return new Dict(dict.size, root); +} + +export function fold(dict, state, fun) { + const queue = [dict.root]; + + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + const data = node.data; + // every node contains popcount(datamap) direct entries + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const edgesStart = data.length - popcount(node.nodemap); + for (let i = 0; i < edgesStart; i += 2) { + state = fun(state, data[i], data[i + 1]); + } + // the remaining entries are child nodes we can queue. + for (let i = edgesStart; i < data.length; ++i) { + queue.push(data[i]); + } + } + + return state; +} + +/** + * How many `1` bits are set in a 32-bit integer. + */ +function popcount(n) { + n -= (n >>> 1) & 0x55555555; + n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); + return Math.imul((n + (n >>> 4)) & 0x0f0f0f0f, 0x01010101) >>> 24; +} + +/** + * Given a population bitmap and a bit selected from that map, returns + * how many less significant 1 bits there are. + * + * For example, index(10101, 100) returns 1, since there is a single less + * significant `1` bit. This translates to the 0-based "index" of that bit. + */ +function index(bitmap, bit) { + return popcount(bitmap & (bit - 1)); +} + +/** + * Extracts a single slice of the hash, and returns a bitmask for the resulting value. + * For example, if the slice returns 5, this function returns 10000 = 1 << 5. + */ +function hashbit(hash, shift) { + return 1 << ((hash >>> shift) & mask); +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bit_array.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bit_array.gleam new file mode 100644 index 0000000..1fefa8b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bit_array.gleam @@ -0,0 +1,280 @@ +//// BitArrays are a sequence of binary data of any length. + +import gleam/int +import gleam/order +import gleam/string + +/// Converts a UTF-8 `String` type into a `BitArray`. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_from_string") +pub fn from_string(x: String) -> BitArray + +/// Returns an integer which is the number of bits in the bit array. +/// +@external(erlang, "erlang", "bit_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_bit_size") +pub fn bit_size(x: BitArray) -> Int + +/// Returns an integer which is the number of bytes in the bit array. +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_byte_size") +pub fn byte_size(x: BitArray) -> Int + +/// Pads a bit array with zeros so that it is a whole number of bytes. +/// +@external(erlang, "gleam_stdlib", "bit_array_pad_to_bytes") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_pad_to_bytes") +pub fn pad_to_bytes(x: BitArray) -> BitArray + +/// Creates a new bit array by joining two bit arrays. +/// +/// ## Examples +/// +/// ```gleam +/// assert append(to: from_string("butter"), suffix: from_string("fly")) +/// == from_string("butterfly") +/// ``` +/// +pub fn append(to first: BitArray, suffix second: BitArray) -> BitArray { + concat([first, second]) +} + +/// Extracts a sub-section of a bit array. +/// +/// The slice will start at given position and continue up to specified +/// length. +/// A negative length can be used to extract bytes at the end of a bit array. +/// +/// This function runs in constant time. +/// +@external(erlang, "gleam_stdlib", "bit_array_slice") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_slice") +pub fn slice( + from string: BitArray, + at position: Int, + take length: Int, +) -> Result(BitArray, Nil) + +/// Tests to see whether a bit array is valid UTF-8. +/// +pub fn is_utf8(bits: BitArray) -> Bool { + is_utf8_loop(bits) +} + +@target(erlang) +fn is_utf8_loop(bits: BitArray) -> Bool { + case bits { + <<>> -> True + <<_:utf8, rest:bytes>> -> is_utf8_loop(rest) + _ -> False + } +} + +@target(javascript) +fn is_utf8_loop(bits: BitArray) -> Bool { + case to_string(bits) { + Ok(_) -> True + Error(_) -> False + } +} + +/// Converts a bit array to a string. +/// +/// Returns an error if the bit array is invalid UTF-8 data. +/// +@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_string") +pub fn to_string(bits: BitArray) -> Result(String, Nil) { + case is_utf8(bits) { + True -> Ok(unsafe_to_string(bits)) + False -> Error(Nil) + } +} + +@external(erlang, "gleam_stdlib", "identity") +fn unsafe_to_string(a: BitArray) -> String + +/// Creates a new bit array by joining multiple binaries. +/// +/// ## Examples +/// +/// ```gleam +/// assert concat([from_string("butter"), from_string("fly")]) +/// == from_string("butterfly") +/// ``` +/// +@external(erlang, "gleam_stdlib", "bit_array_concat") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_concat") +pub fn concat(bit_arrays: List(BitArray)) -> BitArray + +/// Encodes a BitArray into a base 64 encoded string. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +@external(erlang, "gleam_stdlib", "base64_encode") +@external(javascript, "../gleam_stdlib.mjs", "base64_encode") +pub fn base64_encode(input: BitArray, padding: Bool) -> String + +/// Decodes a base 64 encoded string into a `BitArray`. +/// +pub fn base64_decode(encoded: String) -> Result(BitArray, Nil) { + let padded = case byte_size(from_string(encoded)) % 4 { + 0 -> encoded + n -> string.append(encoded, string.repeat("=", 4 - n)) + } + decode64(padded) +} + +@external(erlang, "gleam_stdlib", "base64_decode") +@external(javascript, "../gleam_stdlib.mjs", "base64_decode") +fn decode64(a: String) -> Result(BitArray, Nil) + +/// Encodes a `BitArray` into a base 64 encoded string with URL and filename +/// safe alphabet. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +pub fn base64_url_encode(input: BitArray, padding: Bool) -> String { + input + |> base64_encode(padding) + |> string.replace("+", "-") + |> string.replace("/", "_") +} + +/// Decodes a base 64 encoded string with URL and filename safe alphabet into a +/// `BitArray`. +/// +pub fn base64_url_decode(encoded: String) -> Result(BitArray, Nil) { + encoded + |> string.replace("-", "+") + |> string.replace("_", "/") + |> base64_decode() +} + +/// Encodes a `BitArray` into a base 16 encoded string. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +@external(erlang, "gleam_stdlib", "base16_encode") +@external(javascript, "../gleam_stdlib.mjs", "base16_encode") +pub fn base16_encode(input: BitArray) -> String + +/// Decodes a base 16 encoded string into a `BitArray`. +/// +@external(erlang, "gleam_stdlib", "base16_decode") +@external(javascript, "../gleam_stdlib.mjs", "base16_decode") +pub fn base16_decode(input: String) -> Result(BitArray, Nil) + +/// Converts a bit array to a string containing the decimal value of each byte. +/// +/// Use this over `string.inspect` when you have a bit array you want printed +/// in the array syntax even if it is valid UTF-8. +/// +/// ## Examples +/// +/// ```gleam +/// assert inspect(<<0, 20, 0x20, 255>>) == "<<0, 20, 32, 255>>" +/// ``` +/// +/// ```gleam +/// assert inspect(<<100, 5:3>>) == "<<100, 5:size(3)>>" +/// ``` +/// +pub fn inspect(input: BitArray) -> String { + inspect_loop(input, "<<") <> ">>" +} + +fn inspect_loop(input: BitArray, accumulator: String) -> String { + case input { + <<>> -> accumulator + + <> -> accumulator <> int.to_string(x) <> ":size(1)" + <> -> accumulator <> int.to_string(x) <> ":size(2)" + <> -> accumulator <> int.to_string(x) <> ":size(3)" + <> -> accumulator <> int.to_string(x) <> ":size(4)" + <> -> accumulator <> int.to_string(x) <> ":size(5)" + <> -> accumulator <> int.to_string(x) <> ":size(6)" + <> -> accumulator <> int.to_string(x) <> ":size(7)" + + <> -> { + let suffix = case rest { + <<>> -> "" + _ -> ", " + } + + let accumulator = accumulator <> int.to_string(x) <> suffix + inspect_loop(rest, accumulator) + } + + _ -> accumulator + } +} + +/// Compare two bit arrays as sequences of bytes. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(<<1>>, <<2>>) == Lt +/// ``` +/// +/// ```gleam +/// assert compare(<<"AB":utf8>>, <<"AA":utf8>>) == Gt +/// ``` +/// +/// ```gleam +/// assert compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>) == Eq +/// ``` +/// +pub fn compare(a: BitArray, with b: BitArray) -> order.Order { + case a, b { + <>, <> -> + case first_byte, second_byte { + f, s if f > s -> order.Gt + f, s if f < s -> order.Lt + _, _ -> compare(first_rest, second_rest) + } + + <<>>, <<>> -> order.Eq + // First has more items, example: "AB" > "A": + _, <<>> -> order.Gt + // Second has more items, example: "A" < "AB": + <<>>, _ -> order.Lt + // This happens when there's unusually sized elements. + // Handle these special cases via custom erlang function. + first, second -> + case bit_array_to_int_and_size(first), bit_array_to_int_and_size(second) { + #(a, _), #(b, _) if a > b -> order.Gt + #(a, _), #(b, _) if a < b -> order.Lt + #(_, size_a), #(_, size_b) if size_a > size_b -> order.Gt + #(_, size_a), #(_, size_b) if size_a < size_b -> order.Lt + _, _ -> order.Eq + } + } +} + +@external(erlang, "gleam_stdlib", "bit_array_to_int_and_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_int_and_size") +fn bit_array_to_int_and_size(a: BitArray) -> #(Int, Int) + +/// Checks whether the first `BitArray` starts with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert starts_with(<<1, 2, 3, 4>>, <<1, 2>>) +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "bit_array_starts_with") +pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { + let prefix_size = bit_size(prefix) + + case bits { + <> if pref == prefix -> True + _ -> False + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bool.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bool.gleam new file mode 100644 index 0000000..92147fa --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bool.gleam @@ -0,0 +1,298 @@ +//// A type with two possible values, `True` and `False`. Used to indicate whether +//// things are... true or false! +//// +//// It is often clearer and offers more type safety to define a custom type +//// than to use `Bool`. For example, rather than having a `is_teacher: Bool` +//// field consider having a `role: SchoolRole` field where `SchoolRole` is a custom +//// type that can be either `Student` or `Teacher`. + +/// Returns the and of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `&&` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert and(True, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, False) +/// ``` +/// +pub fn and(a: Bool, b: Bool) -> Bool { + a && b +} + +/// Returns the or of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `||` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(True, True) +/// ``` +/// +/// ```gleam +/// assert or(False, True) +/// ``` +/// +/// ```gleam +/// assert or(True, False) +/// ``` +/// +/// ```gleam +/// assert !or(False, False) +/// ``` +/// +pub fn or(a: Bool, b: Bool) -> Bool { + a || b +} + +/// Returns the opposite bool value. +/// +/// This is the same as the `!` or `not` operators in some other languages. +/// +/// ## Examples +/// +/// ```gleam +/// assert !negate(True) +/// ``` +/// +/// ```gleam +/// assert negate(False) +/// ``` +/// +pub fn negate(bool: Bool) -> Bool { + !bool +} + +/// Returns the nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert nor(False, False) +/// ``` +/// +/// ```gleam +/// assert !nor(False, True) +/// ``` +/// +/// ```gleam +/// assert !nor(True, False) +/// ``` +/// +/// ```gleam +/// assert !nor(True, True) +/// ``` +/// +pub fn nor(a: Bool, b: Bool) -> Bool { + !{ a || b } +} + +/// Returns the nand of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert nand(False, False) +/// ``` +/// +/// ```gleam +/// assert nand(False, True) +/// ``` +/// +/// ```gleam +/// assert nand(True, False) +/// ``` +/// +/// ```gleam +/// assert !nand(True, True) +/// ``` +/// +pub fn nand(a: Bool, b: Bool) -> Bool { + !{ a && b } +} + +/// Returns the exclusive or of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert !exclusive_or(False, False) +/// ``` +/// +/// ```gleam +/// assert exclusive_or(False, True) +/// ``` +/// +/// ```gleam +/// assert exclusive_or(True, False) +/// ``` +/// +/// ```gleam +/// assert !exclusive_or(True, True) +/// ``` +/// +pub fn exclusive_or(a: Bool, b: Bool) -> Bool { + a != b +} + +/// Returns the exclusive nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert exclusive_nor(False, False) +/// ``` +/// +/// ```gleam +/// assert !exclusive_nor(False, True) +/// ``` +/// +/// ```gleam +/// assert !exclusive_nor(True, False) +/// ``` +/// +/// ```gleam +/// assert exclusive_nor(True, True) +/// ``` +/// +pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { + a == b +} + +/// Returns a string representation of the given bool. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(True) == "True" +/// ``` +/// +/// ```gleam +/// assert to_string(False) == "False" +/// ``` +/// +pub fn to_string(bool: Bool) -> String { + case bool { + False -> "False" + True -> "True" + } +} + +/// Run a callback function if the given bool is `False`, otherwise return a +/// default value. +/// +/// With a `use` expression this function can simulate the early-return pattern +/// found in some other programming languages. +/// +/// In a procedural language: +/// +/// ```js +/// if (predicate) return value; +/// // ... +/// ``` +/// +/// In Gleam with a `use` expression: +/// +/// ```gleam +/// use <- guard(when: predicate, return: value) +/// // ... +/// ``` +/// +/// Like everything in Gleam `use` is an expression, so it short circuits the +/// current block, not the entire function. As a result you can assign the value +/// to a variable: +/// +/// ```gleam +/// let x = { +/// use <- guard(when: predicate, return: value) +/// // ... +/// } +/// ``` +/// +/// Note that unlike in procedural languages the `return` value is evaluated +/// even when the predicate is `False`, so it is advisable not to perform +/// expensive computation nor side-effects there. +/// +/// +/// ## Examples +/// +/// ```gleam +/// let name = "" +/// use <- guard(when: name == "", return: "Welcome!") +/// "Hello, " <> name +/// // -> "Welcome!" +/// ``` +/// +/// ```gleam +/// let name = "Kamaka" +/// use <- guard(when: name == "", return: "Welcome!") +/// "Hello, " <> name +/// // -> "Hello, Kamaka" +/// ``` +/// +pub fn guard( + when requirement: Bool, + return consequence: a, + otherwise alternative: fn() -> a, +) -> a { + case requirement { + True -> consequence + False -> alternative() + } +} + +/// Runs a callback function if the given bool is `True`, otherwise runs an +/// alternative callback function. +/// +/// Useful when further computation should be delayed regardless of the given +/// bool's value. +/// +/// See [`guard`](#guard) for more info. +/// +/// ## Examples +/// +/// ```gleam +/// let name = "Kamaka" +/// let inquiry = fn() { "How may we address you?" } +/// use <- lazy_guard(when: name == "", return: inquiry) +/// "Hello, " <> name +/// // -> "Hello, Kamaka" +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// let name = "" +/// let greeting = fn() { "Hello, " <> name } +/// use <- lazy_guard(when: name == "", otherwise: greeting) +/// let number = int.random(99) +/// let name = "User " <> int.to_string(number) +/// "Welcome, " <> name +/// // -> "Welcome, User 54" +/// ``` +/// +pub fn lazy_guard( + when requirement: Bool, + return consequence: fn() -> a, + otherwise alternative: fn() -> a, +) -> a { + case requirement { + True -> consequence() + False -> alternative() + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam new file mode 100644 index 0000000..e712fa9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam @@ -0,0 +1,190 @@ +//// `BytesTree` is a type used for efficiently building binary content to be +//// written to a file or a socket. Internally it is represented as a tree so to +//// append or prepend to a bytes tree is a constant time operation that +//// allocates a new node in the tree without copying any of the content. When +//// writing to an output stream the tree is traversed and the content is sent +//// directly rather than copying it into a single buffer beforehand. +//// +//// If we append one bit array to another the bit arrays must be copied to a +//// new location in memory so that they can sit together. This behaviour +//// enables efficient reading of the data but copying can be expensive, +//// especially if we want to join many bit arrays together. +//// +//// BytesTree is different in that it can be joined together in constant +//// time using minimal memory, and then can be efficiently converted to a +//// bit array using the `to_bit_array` function. +//// +//// Byte trees are always byte aligned, so that a number of bits that is not +//// divisible by 8 will be padded with 0s. +//// +//// On Erlang this type is compatible with Erlang's iolists. + +import gleam/bit_array +import gleam/list +import gleam/string_tree.{type StringTree} + +pub opaque type BytesTree { + Bytes(BitArray) + Text(StringTree) + Many(List(BytesTree)) +} + +/// Create an empty `BytesTree`. Useful as the start of a pipe chaining many +/// trees together. +/// +pub fn new() -> BytesTree { + concat([]) +} + +/// Prepends a bit array to the start of a bytes tree. +/// +/// Runs in constant time. +/// +pub fn prepend(to second: BytesTree, prefix first: BitArray) -> BytesTree { + append_tree(from_bit_array(first), second) +} + +/// Appends a bit array to the end of a bytes tree. +/// +/// Runs in constant time. +/// +pub fn append(to first: BytesTree, suffix second: BitArray) -> BytesTree { + append_tree(first, from_bit_array(second)) +} + +/// Prepends a bytes tree onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_tree(to second: BytesTree, prefix first: BytesTree) -> BytesTree { + append_tree(first, second) +} + +/// Appends a bytes tree onto the end of another. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "iodata_append") +pub fn append_tree(to first: BytesTree, suffix second: BytesTree) -> BytesTree { + case second { + Many(trees) -> Many([first, ..trees]) + Text(_) | Bytes(_) -> Many([first, second]) + } +} + +/// Prepends a string onto the start of a bytes tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn prepend_string(to second: BytesTree, prefix first: String) -> BytesTree { + append_tree(from_string(first), second) +} + +/// Appends a string onto the end of a bytes tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn append_string(to first: BytesTree, suffix second: String) -> BytesTree { + append_tree(first, from_string(second)) +} + +/// Joins a list of bytes trees into a single one. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +pub fn concat(trees: List(BytesTree)) -> BytesTree { + Many(trees) +} + +/// Joins a list of bit arrays into a single bytes tree. +/// +/// Runs in constant time. +/// +pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesTree { + bits + |> list.map(from_bit_array) + |> concat() +} + +/// Creates a new bytes tree from a string. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string(string: String) -> BytesTree { + Text(string_tree.from_string(string)) +} + +/// Creates a new bytes tree from a string tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string_tree(tree: string_tree.StringTree) -> BytesTree { + Text(tree) +} + +/// Creates a new bytes tree from a bit array. +/// +/// Runs in constant time. +/// +pub fn from_bit_array(bits: BitArray) -> BytesTree { + bits + |> bit_array.pad_to_bytes + |> wrap_list +} + +@external(erlang, "gleam_stdlib", "wrap_list") +fn wrap_list(bits: BitArray) -> BytesTree { + Bytes(bits) +} + +/// Turns a bytes tree into a bit array. +/// +/// Runs in linear time. +/// +/// When running on Erlang this function is implemented natively by the +/// virtual machine and is highly optimised. +/// +@external(erlang, "erlang", "list_to_bitstring") +pub fn to_bit_array(tree: BytesTree) -> BitArray { + [[tree]] + |> to_list([]) + |> list.reverse + |> bit_array.concat +} + +fn to_list(stack: List(List(BytesTree)), acc: List(BitArray)) -> List(BitArray) { + case stack { + [] -> acc + + [[], ..remaining_stack] -> to_list(remaining_stack, acc) + + [[Bytes(bits), ..rest], ..remaining_stack] -> + to_list([rest, ..remaining_stack], [bits, ..acc]) + + [[Text(tree), ..rest], ..remaining_stack] -> { + let bits = bit_array.from_string(string_tree.to_string(tree)) + to_list([rest, ..remaining_stack], [bits, ..acc]) + } + + [[Many(trees), ..rest], ..remaining_stack] -> + to_list([trees, rest, ..remaining_stack], acc) + } +} + +/// Returns the size of the bytes tree's content in bytes. +/// +/// Runs in linear time. +/// +@external(erlang, "erlang", "iolist_size") +pub fn byte_size(tree: BytesTree) -> Int { + [[tree]] + |> to_list([]) + |> list.fold(0, fn(acc, bits) { bit_array.byte_size(bits) + acc }) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dict.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dict.gleam new file mode 100644 index 0000000..4a81207 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dict.gleam @@ -0,0 +1,582 @@ +import gleam/option.{type Option} + +/// A dictionary of keys and values. +/// +/// Any type can be used for the keys and values of a dict, but all the keys +/// must be of the same type and all the values must be of the same type. +/// +/// Each key can only be present in a dict once. +/// +/// Dicts are not ordered in any way, and any unintentional ordering is not to +/// be relied upon in your code as it may change in future versions of Erlang +/// or Gleam. +/// +/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more +/// information. +/// +pub type Dict(key, value) + +/// "TransientDict" is a mutable view on a dictionary used internally by the +/// JavaScript target. No mutable API is exposed to the user. +/// +/// Transients are to be treated as having a linear (single-use, think rust) type. +/// A transient value becomes invalid as soon as it's passed to one of the functions. +type TransientDict(key, value) + +/// Convert a normal Dict to a transient dict. +/// A transient dict is a mutable copy of the original. +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../dict.mjs", "toTransient") +fn to_transient(dict: Dict(key, value)) -> TransientDict(key, value) + +/// Convert a transient dict back into a normal dict, freezing its contents. +/// Using the transient after this point is highly unsafe and leads to undefined behavior. +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../dict.mjs", "fromTransient") +fn from_transient(transient: TransientDict(key, value)) -> Dict(key, value) + +/// Determines the number of key-value pairs in the dict. +/// This function runs in constant time and does not need to iterate the dict. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> size == 0 +/// ``` +/// +/// ```gleam +/// assert new() |> insert("key", "value") |> size == 1 +/// ``` +/// +@external(erlang, "maps", "size") +@external(javascript, "../dict.mjs", "size") +pub fn size(dict: Dict(k, v)) -> Int + +/// Determines whether or not the dict is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> is_empty +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert("b", 1) |> is_empty } +/// ``` +/// +pub fn is_empty(dict: Dict(k, v)) -> Bool { + size(dict) == 0 +} + +/// Converts the dict to a list of 2-element tuples `#(key, value)`, one for +/// each key-value pair in the dict. +/// +/// The tuples in the list have no specific order. +/// +/// ## Examples +/// +/// Calling `to_list` on an empty `dict` returns an empty list. +/// +/// ```gleam +/// assert new() |> to_list == [] +/// ``` +/// +/// The ordering of elements in the resulting list is an implementation detail +/// that should not be relied upon. +/// +/// ```gleam +/// assert new() +/// |> insert("b", 1) +/// |> insert("a", 0) +/// |> insert("c", 2) +/// |> to_list +/// == [#("a", 0), #("b", 1), #("c", 2)] +/// ``` +/// +@external(erlang, "maps", "to_list") +pub fn to_list(dict: Dict(k, v)) -> List(#(k, v)) { + fold(dict, from: [], with: fn(acc, key, value) { [#(key, value), ..acc] }) +} + +/// Converts a list of 2-element tuples `#(key, value)` to a dict. +/// +/// If two tuples have the same key the last one in the list will be the one +/// that is present in the dict. +/// +@external(erlang, "maps", "from_list") +pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { + from_list_loop(to_transient(new()), list) +} + +fn from_list_loop( + transient: TransientDict(k, v), + list: List(#(k, v)), +) -> Dict(k, v) { + case list { + [] -> from_transient(transient) + [#(key, value), ..rest] -> + from_list_loop(transient_insert(key, value, transient), rest) + } +} + +/// Determines whether or not a value is present in the dict for a given key. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> has_key("a") +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert("a", 0) |> has_key("b") } +/// ``` +/// +@external(javascript, "../dict.mjs", "has") +pub fn has_key(dict: Dict(k, v), key: k) -> Bool { + do_has_key(key, dict) +} + +@external(erlang, "maps", "is_key") +fn do_has_key(key: k, dict: Dict(k, v)) -> Bool + +/// Creates a fresh dict that contains no values. +/// +@external(erlang, "maps", "new") +@external(javascript, "../dict.mjs", "make") +pub fn new() -> Dict(k, v) + +/// Fetches a value from a dict for a given key. +/// +/// The dict may not have a value for the key, so the value is wrapped in a +/// `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> get("a") == Ok(0) +/// ``` +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> get("b") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "map_get") +@external(javascript, "../dict.mjs", "get") +pub fn get(from: Dict(k, v), get: k) -> Result(v, Nil) + +/// Inserts a value into the dict with the given key. +/// +/// If the dict already has a value for the given key then the value is +/// replaced with the new value. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) == from_list([#("a", 0)]) +/// ``` +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> insert("a", 5) == from_list([#("a", 5)]) +/// ``` +/// +@external(javascript, "../dict.mjs", "insert") +pub fn insert(into dict: Dict(k, v), for key: k, insert value: v) -> Dict(k, v) { + do_insert(key, value, dict) +} + +@external(erlang, "maps", "put") +fn do_insert(key: k, value: v, dict: Dict(k, v)) -> Dict(k, v) + +@external(erlang, "maps", "put") +@external(javascript, "../dict.mjs", "destructiveTransientInsert") +fn transient_insert( + key: k, + value: v, + transient: TransientDict(k, v), +) -> TransientDict(k, v) + +/// Updates all values in a given dict by calling a given function on each key +/// and value. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#(3, 3), #(2, 4)]) +/// |> map_values(fn(key, value) { key * value }) +/// == from_list([#(3, 9), #(2, 8)]) +/// ``` +/// +@external(javascript, "../dict.mjs", "map") +pub fn map_values(in dict: Dict(k, v), with fun: fn(k, v) -> a) -> Dict(k, a) { + do_map_values(fun, dict) +} + +@external(erlang, "maps", "map") +fn do_map_values(f: fn(k, v) -> a, dict: Dict(k, v)) -> Dict(k, a) + +/// Gets a list of all keys in a given dict. +/// +/// Dicts are not ordered so the keys are not returned in any specific order. Do +/// not write code that relies on the order keys are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> keys == ["a", "b"] +/// ``` +/// +@external(erlang, "maps", "keys") +pub fn keys(dict: Dict(k, v)) -> List(k) { + fold(dict, [], fn(acc, key, _value) { [key, ..acc] }) +} + +/// Gets a list of all values in a given dict. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order values are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> values == [0, 1] +/// ``` +/// +@external(erlang, "maps", "values") +pub fn values(dict: Dict(k, v)) -> List(v) { + fold(dict, [], fn(acc, _key, value) { [value, ..acc] }) +} + +/// Creates a new dict from a given dict, minus any entries that a given function +/// returns `False` for. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> filter(fn(key, value) { value != 0 }) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> filter(fn(key, value) { True }) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn filter( + in dict: Dict(k, v), + keeping predicate: fn(k, v) -> Bool, +) -> Dict(k, v) { + do_filter(predicate, dict) +} + +@external(erlang, "maps", "filter") +fn do_filter(f: fn(k, v) -> Bool, dict: Dict(k, v)) -> Dict(k, v) { + to_transient(new()) + |> fold(over: dict, with: fn(transient, key, value) { + case f(key, value) { + True -> transient_insert(key, value, transient) + False -> transient + } + }) + |> from_transient +} + +/// Creates a new dict from a given dict, only including any entries for which the +/// keys are in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> take(["b"]) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> take(["a", "b", "c"]) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn take(from dict: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) { + do_take(desired_keys, dict) +} + +@external(erlang, "maps", "with") +fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + do_take_loop(dict, desired_keys, to_transient(new())) +} + +fn do_take_loop( + dict: Dict(k, v), + desired_keys: List(k), + acc: TransientDict(k, v), +) -> Dict(k, v) { + case desired_keys { + [] -> from_transient(acc) + [key, ..rest] -> + case get(dict, key) { + Ok(value) -> do_take_loop(dict, rest, transient_insert(key, value, acc)) + Error(_) -> do_take_loop(dict, rest, acc) + } + } +} + +/// Creates a new dict from a pair of given dicts by combining their entries. +/// +/// If there are entries with the same keys in both dicts the entry from the +/// second dict takes precedence. +/// +/// ## Examples +/// +/// ```gleam +/// let a = from_list([#("a", 0), #("b", 1)]) +/// let b = from_list([#("b", 2), #("c", 3)]) +/// assert merge(a, b) == from_list([#("a", 0), #("b", 2), #("c", 3)]) +/// ``` +/// +@external(erlang, "maps", "merge") +pub fn merge(into dict: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) { + combine(dict, new_entries, fn(_, new_entry) { new_entry }) +} + +/// Creates a new dict from a given dict with all the same entries except for the +/// one with a given key, if it exists. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> delete("a") +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> delete("c") +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn delete(from dict: Dict(k, v), delete key: k) -> Dict(k, v) { + to_transient(dict) |> transient_delete(key, _) |> from_transient +} + +@external(erlang, "maps", "remove") +@external(javascript, "../dict.mjs", "destructiveTransientDelete") +fn transient_delete(a: k, b: TransientDict(k, v)) -> TransientDict(k, v) + +/// Creates a new dict from a given dict with all the same entries except any with +/// keys found in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["a"]) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["c"]) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["a", "b", "c"]) +/// == from_list([]) +/// ``` +/// +pub fn drop(from dict: Dict(k, v), drop disallowed_keys: List(k)) -> Dict(k, v) { + do_drop(disallowed_keys, dict) +} + +@external(erlang, "maps", "without") +fn do_drop(disallowed_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + drop_loop(to_transient(dict), disallowed_keys) +} + +fn drop_loop( + transient: TransientDict(k, v), + disallowed_keys: List(k), +) -> Dict(k, v) { + case disallowed_keys { + [] -> from_transient(transient) + [key, ..rest] -> drop_loop(transient_delete(key, transient), rest) + } +} + +/// Creates a new dict with one entry inserted or updated using a given function. +/// +/// If there was not an entry in the dict for the given key then the function +/// gets `None` as its argument, otherwise it gets `Some(value)`. +/// +/// ## Examples +/// +/// ```gleam +/// let dict = from_list([#("a", 0)]) +/// let increment = fn(x) { +/// case x { +/// Some(i) -> i + 1 +/// None -> 0 +/// } +/// } +/// +/// assert upsert(dict, "a", increment) == from_list([#("a", 1)]) +/// ``` +/// +/// ```gleam +/// assert upsert(dict, "b", increment) == from_list([#("a", 0), #("b", 0)]) +/// ``` +/// +pub fn upsert( + in dict: Dict(k, v), + update key: k, + with fun: fn(Option(v)) -> v, +) -> Dict(k, v) { + case get(dict, key) { + Ok(value) -> insert(dict, key, fun(option.Some(value))) + Error(_) -> insert(dict, key, fun(option.None)) + } +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order entries are used by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// assert fold(dict, 0, fn(accumulator, key, value) { accumulator + value }) +/// == 13 +/// ``` +/// +/// ```gleam +/// import gleam/string +/// +/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// assert +/// fold(dict, "", fn(accumulator, key, value) { +/// string.append(accumulator, key) +/// }) +/// == "abc" +/// ``` +/// +@external(javascript, "../dict.mjs", "fold") +pub fn fold( + over dict: Dict(k, v), + from initial: acc, + with fun: fn(acc, k, v) -> acc, +) -> acc { + let fun = fn(key, value, acc) { fun(acc, key, value) } + do_fold(fun, initial, dict) +} + +@external(erlang, "maps", "fold") +fn do_fold(fun: fn(k, v, acc) -> acc, initial: acc, dict: Dict(k, v)) -> acc + +/// Calls a function for each key and value in a dict, discarding the return +/// value. +/// +/// Useful for producing a side effect for every item of a dict. +/// +/// ```gleam +/// import gleam/io +/// +/// let dict = from_list([#("a", "apple"), #("b", "banana"), #("c", "cherry")]) +/// +/// assert +/// each(dict, fn(k, v) { +/// io.println(k <> " => " <> v) +/// }) +/// == Nil +/// // a => apple +/// // b => banana +/// // c => cherry +/// ``` +/// +/// The order of elements in the iteration is an implementation detail that +/// should not be relied upon. +/// +pub fn each(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil { + fold(dict, Nil, fn(nil, k, v) { + fun(k, v) + nil + }) +} + +/// Creates a new dict from a pair of given dicts by combining their entries. +/// +/// If there are entries with the same keys in both dicts the given function is +/// used to determine the new value to use in the resulting dict. +/// +/// ## Examples +/// +/// ```gleam +/// let a = from_list([#("a", 0), #("b", 1)]) +/// let b = from_list([#("a", 2), #("c", 3)]) +/// assert combine(a, b, fn(one, other) { one + other }) +/// == from_list([#("a", 2), #("b", 1), #("c", 3)]) +/// ``` +/// +pub fn combine( + dict: Dict(k, v), + other: Dict(k, v), + with fun: fn(v, v) -> v, +) -> Dict(k, v) { + do_combine(fn(_, l, r) { fun(l, r) }, dict, other) +} + +@external(erlang, "maps", "merge_with") +fn do_combine( + combine: fn(k, v, v) -> v, + left: Dict(k, v), + right: Dict(k, v), +) -> Dict(k, v) { + let #(big, small, combine) = case size(left) >= size(right) { + True -> #(left, right, combine) + False -> #(right, left, fn(k, l, r) { combine(k, r, l) }) + } + + to_transient(big) + |> fold(over: small, with: fn(transient, key, value) { + let update = fn(existing) { combine(key, existing, value) } + transient_update_with(key, update, value, transient) + }) + |> from_transient +} + +@external(erlang, "maps", "update_with") +@external(javascript, "../dict.mjs", "destructiveTransientUpdateWith") +fn transient_update_with( + key: k, + fun: fn(v) -> v, + init: v, + transient: TransientDict(k, v), +) -> TransientDict(k, v) + +@internal +pub fn group(key: fn(v) -> k, list: List(v)) -> Dict(k, List(v)) { + group_loop(to_transient(new()), key, list) +} + +fn group_loop( + transient: TransientDict(k, List(v)), + to_key: fn(v) -> k, + list: List(v), +) -> Dict(k, List(v)) { + case list { + [] -> from_transient(transient) + [value, ..rest] -> { + let key = to_key(value) + let update = fn(existing) { [value, ..existing] } + + transient + |> transient_update_with(key, update, [value], _) + |> group_loop(to_key, rest) + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic.gleam new file mode 100644 index 0000000..b144da7 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic.gleam @@ -0,0 +1,99 @@ +import gleam/dict + +/// `Dynamic` data is data that we don't know the type of yet. +/// We likely get data like this from interop with Erlang, or from +/// IO with the outside world. +/// +/// This module contains code for forming dynamic data, and the +/// `gleam/dynamic/decode` module contains code for turning dynamic data back +/// into Gleam data with known types. You will likely mostly use the other +/// module in your projects. +/// +/// The exact runtime representation of dynamic values will depend on the +/// compilation target used. +/// +pub type Dynamic + +/// Return a string indicating the type of the dynamic value. +/// +/// This function may be useful for constructing error messages or logs. If you +/// want to turn dynamic data into well typed data then you want the +/// `gleam/dynamic/decode` module. +/// +/// ```gleam +/// assert classify(string("Hello")) == "String" +/// ``` +/// +@external(erlang, "gleam_stdlib", "classify_dynamic") +@external(javascript, "../gleam_stdlib.mjs", "classify_dynamic") +pub fn classify(data: Dynamic) -> String + +/// Create a dynamic value from a bool. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn bool(a: Bool) -> Dynamic + +/// Create a dynamic value from a string. +/// +/// On Erlang this will be a binary string rather than a character list. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn string(a: String) -> Dynamic + +/// Create a dynamic value from a float. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn float(a: Float) -> Dynamic + +/// Create a dynamic value from an int. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn int(a: Int) -> Dynamic + +/// Create a dynamic value from a bit array. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn bit_array(a: BitArray) -> Dynamic + +/// Create a dynamic value from a list. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn list(a: List(Dynamic)) -> Dynamic + +/// Create a dynamic value from a list, converting it to a sequential runtime +/// format rather than the regular list format. +/// +/// On Erlang this will be a tuple, on JavaScript this will be an array. +/// +@external(erlang, "erlang", "list_to_tuple") +@external(javascript, "../gleam_stdlib.mjs", "list_to_array") +pub fn array(a: List(Dynamic)) -> Dynamic + +/// Create a dynamic value made of an unordered series of keys and values, where +/// the keys are unique. +/// +/// On Erlang this will be a map, on JavaScript this will be a Gleam dict +/// object. +/// +pub fn properties(entries: List(#(Dynamic, Dynamic))) -> Dynamic { + cast(dict.from_list(entries)) +} + +/// A dynamic value representing nothing. +/// +/// On Erlang this will be the atom `nil`, on JavaScript this will be +/// `undefined`. +/// +pub fn nil() -> Dynamic { + cast(Nil) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn cast(a: anything) -> Dynamic diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam new file mode 100644 index 0000000..b95b332 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam @@ -0,0 +1,1115 @@ +//// The `Dynamic` type is used to represent dynamically typed data. That is, data +//// that we don't know the precise type of yet, so we need to introspect the data to +//// see if it is of the desired type before we can use it. Typically data like this +//// would come from user input or from untyped languages such as Erlang or JavaScript. +//// +//// This module provides the `Decoder` type and associated functions, which provides +//// a type-safe and composable way to convert dynamic data into some desired type, +//// or into errors if the data doesn't have the desired structure. +//// +//// The `Decoder` type is generic and has 1 type parameter, which is the type that +//// it attempts to decode. A `Decoder(String)` can be used to decode strings, and a +//// `Decoder(Option(Int))` can be used to decode `Option(Int)`s +//// +//// Decoders work using _runtime reflection_ and the data structures of the target +//// platform. Differences between Erlang and JavaScript data structures may impact +//// your decoders, so it is important to test your decoders on all supported +//// platforms. +//// +//// The decoding technique used by this module was inspired by Juraj Petráš' +//// [Toy](https://github.com/Hackder/toy), Go's `encoding/json`, and Elm's +//// `Json.Decode`. Thank you to them! +//// +//// # Generating decoders +//// +//// The language server has the "generate dynamic decoder" code action, which +//// will generate a decoder function when run on a custom type definition. +//// This generated decoder function can be a convenient shortcut when creating +//// your own decoders, and you can edit the generated function to suit your needs. +//// +//// # Examples +//// +//// Dynamic data may come from various sources and so many different syntaxes could +//// be used to describe or construct them. In these examples a pseudocode +//// syntax is used to describe the data. +//// +//// ## Simple types +//// +//// This module defines decoders for simple data types such as [`string`](#string), +//// [`int`](#int), [`float`](#float), [`bit_array`](#bit_array), and [`bool`](#bool). +//// +//// ```gleam +//// // Data: +//// // "Hello, Joe!" +//// +//// let result = decode.run(data, decode.string) +//// assert result == Ok("Hello, Joe!") +//// ``` +//// +//// ## Lists +//// +//// The [`list`](#list) decoder decodes `List`s. To use it you must construct it by +//// passing in another decoder into the `list` function, which is the decoder that +//// is to be used for the elements of the list, type checking both the list and its +//// elements. +//// +//// ```gleam +//// // Data: +//// // [1, 2, 3, 4] +//// +//// let result = decode.run(data, decode.list(decode.int)) +//// assert result == Ok([1, 2, 3, 4]) +//// ``` +//// +//// On Erlang this decoder can decode from lists, and on JavaScript it can +//// decode from lists as well as JavaScript arrays. +//// +//// ## Options +//// +//// The [`optional`](#optional) decoder is used to decode values that may or may not +//// be present. In other environments these might be called "nullable" values. +//// +//// Like the `list` decoder, the `optional` decoder takes another decoder, +//// which is used to decode the value if it is present. +//// +//// ```gleam +//// // Data: +//// // 12.45 +//// +//// let result = decode.run(data, decode.optional(decode.float)) +//// assert result == Ok(option.Some(12.45)) +//// ``` +//// ```gleam +//// // Data: +//// // null +//// +//// let result = decode.run(data, decode.optional(decode.int)) +//// assert result == Ok(option.None) +//// ``` +//// +//// This decoder knows how to handle multiple different runtime representations of +//// absent values, including `Nil`, `None`, `null`, and `undefined`. +//// +//// ## Dicts +//// +//// The [`dict`](#dict) decoder decodes `Dicts` and contains two other decoders, one +//// for the keys, one for the values. +//// +//// ```gleam +//// // Data: +//// // { "Lucy" -> 10, "Nubi" -> 20 } +//// +//// let result = decode.run(data, decode.dict(decode.string, decode.int)) +//// assert result == Ok(dict.from_list([ +//// #("Lucy", 10), +//// #("Nubi", 20), +//// ])) +//// ``` +//// +//// ## Indexing objects +//// +//// The [`at`](#at) decoder can be used to decode a value that is nested within +//// key-value containers such as Gleam dicts, Erlang maps, or JavaScript objects. +//// +//// ```gleam +//// // Data: +//// // { "one" -> { "two" -> 123 } } +//// +//// let result = decode.run(data, decode.at(["one", "two"], decode.int)) +//// assert result == Ok(123) +//// ``` +//// +//// ## Indexing arrays +//// +//// If you use ints as keys then the [`at`](#at) decoder can be used to index into +//// array-like containers such as Gleam or Erlang tuples, or JavaScript arrays. +//// +//// ```gleam +//// // Data: +//// // ["one", "two", "three"] +//// +//// let result = decode.run(data, decode.at([1], decode.string)) +//// assert result == Ok("two") +//// ``` +//// +//// ## Records +//// +//// Decoding records from dynamic data is more complex and requires combining a +//// decoder for each field and a special constructor that builds your records with +//// the decoded field values. +//// +//// ```gleam +//// // Data: +//// // { +//// // "score" -> 180, +//// // "name" -> "Mel Smith", +//// // "is-admin" -> false, +//// // "enrolled" -> true, +//// // "colour" -> "Red", +//// // } +//// +//// let decoder = { +//// use name <- decode.field("name", decode.string) +//// use score <- decode.field("score", decode.int) +//// use colour <- decode.field("colour", decode.string) +//// use enrolled <- decode.field("enrolled", decode.bool) +//// decode.success(Player(name:, score:, colour:, enrolled:)) +//// } +//// +//// let result = decode.run(data, decoder) +//// assert result == Ok(Player("Mel Smith", 180, "Red", True)) +//// ``` +//// +//// ## Enum variants +//// +//// Imagine you have a custom type where all the variants do not contain any values. +//// +//// ```gleam +//// pub type PocketMonsterType { +//// Fire +//// Water +//// Grass +//// Electric +//// } +//// ``` +//// +//// You might choose to encode these variants as strings, `"fire"` for `Fire`, +//// `"water"` for `Water`, and so on. To decode them you'll need to decode the dynamic +//// data as a string, but then you'll need to decode it further still as not all +//// strings are valid values for the enum. This can be done with the `then` +//// function, which enables running a second decoder after the first one +//// succeeds. +//// +//// ```gleam +//// let decoder = { +//// use decoded_string <- decode.then(decode.string) +//// case decoded_string { +//// // Return succeeding decoders for valid strings +//// "fire" -> decode.success(Fire) +//// "water" -> decode.success(Water) +//// "grass" -> decode.success(Grass) +//// "electric" -> decode.success(Electric) +//// // Return a failing decoder for any other strings +//// _ -> decode.failure(Fire, expected: "PocketMonsterType") +//// } +//// } +//// +//// let result = decode.run(dynamic.string("water"), decoder) +//// assert result == Ok(Water) +//// +//// let result = decode.run(dynamic.string("wobble"), decoder) +//// assert result == Error([DecodeError("PocketMonsterType", "String", [])]) +//// ``` +//// +//// ## Record variants +//// +//// Decoding type variants that contain other values is done by combining the +//// techniques from the "enum variants" and "records" examples. Imagine you have +//// this custom type that you want to decode: +//// +//// ```gleam +//// pub type PocketMonsterPerson { +//// Trainer(name: String, badge_count: Int) +//// GymLeader(name: String, speciality: PocketMonsterType) +//// } +//// ``` +//// And you would like to be able to decode these from dynamic data like this: +//// ```erlang +//// { +//// "type" -> "trainer", +//// "name" -> "Ash", +//// "badge-count" -> 1, +//// } +//// ``` +//// ```erlang +//// { +//// "type" -> "gym-leader", +//// "name" -> "Misty", +//// "speciality" -> "water", +//// } +//// ``` +//// +//// Notice how both documents have a `"type"` field, which is used to indicate which +//// variant the data is for. +//// +//// First, define decoders for each of the variants: +//// +//// ```gleam +//// let trainer_decoder = { +//// use name <- decode.field("name", decode.string) +//// use badge_count <- decode.field("badge-count", decode.int) +//// decode.success(Trainer(name, badge_count)) +//// } +//// +//// let gym_leader_decoder = { +//// use name <- decode.field("name", decode.string) +//// use speciality <- decode.field("speciality", pocket_monster_type_decoder) +//// decode.success(GymLeader(name, speciality)) +//// } +//// ``` +//// +//// A third decoder can be used to extract and decode the `"type"` field, and the +//// expression can evaluate to whichever decoder is suitable for the document. +//// +//// ```gleam +//// // Data: +//// // { +//// // "type" -> "gym-leader", +//// // "name" -> "Misty", +//// // "speciality" -> "water", +//// // } +//// +//// let decoder = { +//// use tag <- decode.field("type", decode.string) +//// case tag { +//// "gym-leader" -> gym_leader_decoder +//// _ -> trainer_decoder +//// } +//// } +//// +//// let result = decode.run(data, decoder) +//// assert result == Ok(GymLeader("Misty", Water)) +//// ``` + +import gleam/bit_array +import gleam/dict.{type Dict} +import gleam/dynamic +import gleam/float +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} + +/// `Dynamic` data is data that we don't know the type of yet, originating from +/// external untyped systems. +/// +/// You should never be converting your well typed data to dynamic data. +/// +pub type Dynamic = + dynamic.Dynamic + +/// Error returned when unexpected data is encountered +/// +pub type DecodeError { + DecodeError(expected: String, found: String, path: List(String)) +} + +/// A decoder is a value that can be used to turn dynamically typed `Dynamic` +/// data into typed data using the `run` function. +/// +/// Several smaller decoders can be combined to make larger decoders using +/// functions such as `list` and `field`. +/// +pub opaque type Decoder(t) { + Decoder(function: fn(Dynamic) -> #(t, List(DecodeError))) +} + +/// The same as [`field`](#field), except taking a path to the value rather +/// than a field name. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("data"), dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// ]) +/// +/// let decoder = { +/// use name <- decode.subfield(["data", "name"], decode.string) +/// use email <- decode.subfield(["data", "email"], decode.string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +pub fn subfield( + field_path: List(name), + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + Decoder(function: fn(data) { + let #(out, errors1) = + index(field_path, [], field_decoder.function, data, fn(data, position) { + let #(default, _) = field_decoder.function(data) + #(default, [DecodeError("Field", "Nothing", [])]) + |> push_path(list.reverse(position)) + }) + let #(out, errors2) = next(out).function(data) + #(out, list.append(errors1, errors2)) + }) +} + +/// Run a decoder on a `Dynamic` value, decoding the value if it is of the +/// desired type, or returning errors. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = { +/// use name <- decode.field("name", decode.string) +/// use email <- decode.field("email", decode.string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// decode.run(data, decoder) +/// ``` +/// +pub fn run(data: Dynamic, decoder: Decoder(t)) -> Result(t, List(DecodeError)) { + let #(maybe_invalid_data, errors) = decoder.function(data) + case errors { + [] -> Ok(maybe_invalid_data) + [_, ..] -> Error(errors) + } +} + +/// A decoder that decodes a value that is nested within other values. For +/// example, decoding a value that is within some deeply nested JSON objects. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.at(["one", "two"], decode.int) +/// +/// let data = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.properties([ +/// #(dynamic.string("two"), dynamic.int(1000)), +/// ]), +/// ]) +/// +/// assert decode.run(data, decoder) == Ok(1000) +/// ``` +/// +/// ```gleam +/// assert dynamic.nil() +/// |> decode.run(decode.optional(decode.int)) +/// == Ok(option.None) +/// ``` +/// +pub fn at(path: List(segment), inner: Decoder(a)) -> Decoder(a) { + Decoder(function: fn(data) { + index(path, [], inner.function, data, fn(data, position) { + let #(default, _) = inner.function(data) + #(default, [DecodeError("Field", "Nothing", [])]) + |> push_path(list.reverse(position)) + }) + }) +} + +fn index( + path: List(a), + position: List(a), + inner: fn(Dynamic) -> #(b, List(DecodeError)), + data: Dynamic, + handle_miss: fn(Dynamic, List(a)) -> #(b, List(DecodeError)), +) -> #(b, List(DecodeError)) { + case path { + [] -> { + data + |> inner + |> push_path(list.reverse(position)) + } + + [key, ..path] -> { + case bare_index(data, key) { + Ok(Some(data)) -> { + index(path, [key, ..position], inner, data, handle_miss) + } + Ok(None) -> { + handle_miss(data, [key, ..position]) + } + Error(kind) -> { + let #(default, _) = inner(data) + #(default, [DecodeError(kind, dynamic.classify(data), [])]) + |> push_path(list.reverse(position)) + } + } + } + } +} + +@external(erlang, "gleam_stdlib", "index") +@external(javascript, "../../gleam_stdlib.mjs", "index") +fn bare_index(data: Dynamic, key: anything) -> Result(Option(Dynamic), String) + +fn push_path( + layer: #(t, List(DecodeError)), + path: List(key), +) -> #(t, List(DecodeError)) { + let path = list.map(path, fn(key) { key |> cast |> path_segment_to_string }) + let errors = + list.map(layer.1, fn(error) { + DecodeError(..error, path: list.append(path, error.path)) + }) + #(layer.0, errors) +} + +fn path_segment_to_string(key: Dynamic) -> String { + let decoder = + one_of(string, [ + int |> map(int.to_string), + float |> map(float.to_string), + ]) + case run(key, decoder) { + Ok(key) -> key + Error(_) -> "<" <> dynamic.classify(key) <> ">" + } +} + +/// Finalise a decoder having successfully extracted a value. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.field("email", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +pub fn success(data: t) -> Decoder(t) { + Decoder(function: fn(_) { #(data, []) }) +} + +/// Construct a decode error for some unexpected dynamic data. +/// +pub fn decode_error( + expected expected: String, + found found: Dynamic, +) -> List(DecodeError) { + [DecodeError(expected: expected, found: dynamic.classify(found), path: [])] +} + +/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is +/// of the desired type, or returning errors. An error is returned if there is +/// no field for the specified key. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.field("email", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +/// If you wish to decode a value that is more deeply nested within the dynamic +/// data, see [`subfield`](#subfield) and [`at`](#at). +/// +/// If you wish to return a default in the event that a field is not present, +/// see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at). +/// +pub fn field( + field_name: name, + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + subfield([field_name], field_decoder, next) +} + +/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is +/// of the desired type, or returning errors. The given default value is +/// returned if there is no field for the specified key. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.optional_field("email", "n/a", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "n/a")) +/// ``` +/// +pub fn optional_field( + key: name, + default: t, + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + Decoder(function: fn(data) { + let #(out, errors1) = + case bare_index(data, key) { + Ok(Some(data)) -> field_decoder.function(data) + Ok(None) -> #(default, []) + Error(kind) -> #(default, [ + DecodeError(kind, dynamic.classify(data), []), + ]) + } + |> push_path([key]) + let #(out, errors2) = next(out).function(data) + #(out, list.append(errors1, errors2)) + }) +} + +/// A decoder that decodes a value that is nested within other values. For +/// example, decoding a value that is within some deeply nested JSON objects. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.optionally_at(["one", "two"], 100, decode.int) +/// +/// let data = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.properties([])), +/// ]) +/// +/// assert decode.run(data, decoder) == Ok(100) +/// ``` +/// +pub fn optionally_at( + path: List(segment), + default: a, + inner: Decoder(a), +) -> Decoder(a) { + Decoder(function: fn(data) { + index(path, [], inner.function, data, fn(_, _) { #(default, []) }) + }) +} + +fn run_dynamic_function( + data: Dynamic, + name: String, + f: fn(Dynamic) -> Result(t, t), +) -> #(t, List(DecodeError)) { + case f(data) { + Ok(data) -> #(data, []) + Error(placeholder) -> #(placeholder, [ + DecodeError(name, dynamic.classify(data), []), + ]) + } +} + +/// A decoder that decodes `String` values. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.string("Hello!"), decode.string) +/// assert result == Ok("Hello!") +/// ``` +/// +pub const string: Decoder(String) = Decoder(decode_string) + +fn decode_string(data: Dynamic) -> #(String, List(DecodeError)) { + run_dynamic_function(data, "String", dynamic_string) +} + +@external(javascript, "../../gleam_stdlib.mjs", "string") +fn dynamic_string(from data: Dynamic) -> Result(String, String) { + case dynamic_bit_array(data) { + Ok(data) -> + case bit_array.to_string(data) { + Ok(string) -> Ok(string) + Error(_) -> Error("") + } + Error(_) -> Error("") + } +} + +/// A decoder that decodes `Bool` values. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.bool(True), decode.bool) +/// assert result == Ok(True) +/// ``` +/// +pub const bool: Decoder(Bool) = Decoder(decode_bool) + +fn decode_bool(data: Dynamic) -> #(Bool, List(DecodeError)) { + case cast(True) == data { + True -> #(True, []) + False -> + case cast(False) == data { + True -> #(False, []) + False -> #(False, decode_error("Bool", data)) + } + } +} + +/// A decoder that decodes `Int` values. +/// +/// This will not coerse float values into int values, so on platforms with +/// distinct runtime int and float types (Erlang, not JavaScript) it will fail, +/// even if the float is a whole number (e.g. 1.0). +/// +/// If you want to decode both ints and floats you may want to use the `one_of` +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.int(147), decode.int) +/// assert result == Ok(147) +/// ``` +/// +pub const int: Decoder(Int) = Decoder(decode_int) + +fn decode_int(data: Dynamic) -> #(Int, List(DecodeError)) { + run_dynamic_function(data, "Int", dynamic_int) +} + +@external(erlang, "gleam_stdlib", "int") +@external(javascript, "../../gleam_stdlib.mjs", "int") +fn dynamic_int(data: Dynamic) -> Result(Int, Int) + +/// A decoder that decodes `Float` values. +/// +/// This will not coerse int values into float values, so on platforms with +/// distinct runtime int and float types (Erlang, not JavaScript) it will fail +/// for ints. One time this may happen is when decoding JSON data. +/// +/// If you want to decode both ints and floats you may want to use the `one_of` +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.float(3.14), decode.float) +/// assert result == Ok(3.14) +/// ``` +/// +pub const float: Decoder(Float) = Decoder(decode_float) + +fn decode_float(data: Dynamic) -> #(Float, List(DecodeError)) { + run_dynamic_function(data, "Float", dynamic_float) +} + +@external(erlang, "gleam_stdlib", "float") +@external(javascript, "../../gleam_stdlib.mjs", "float") +fn dynamic_float(data: Dynamic) -> Result(Float, Float) + +/// A decoder that decodes `Dynamic` values. This decoder never returns an error. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.float(3.14), decode.dynamic) +/// assert result == Ok(dynamic.float(3.14)) +/// ``` +/// +pub const dynamic: Decoder(Dynamic) = Decoder(decode_dynamic) + +fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(DecodeError)) { + #(data, []) +} + +/// A decoder that decodes `BitArray` values. This decoder never returns an error. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.bit_array(<<5, 7>>), decode.bit_array) +/// assert result == Ok(<<5, 7>>) +/// ``` +/// +pub const bit_array: Decoder(BitArray) = Decoder(decode_bit_array) + +fn decode_bit_array(data: Dynamic) -> #(BitArray, List(DecodeError)) { + run_dynamic_function(data, "BitArray", dynamic_bit_array) +} + +@external(erlang, "gleam_stdlib", "bit_array") +@external(javascript, "../../gleam_stdlib.mjs", "bit_array") +fn dynamic_bit_array(data: Dynamic) -> Result(BitArray, BitArray) + +/// A decoder that decodes lists where all elements are decoded with a given +/// decoder. +/// +/// ## Examples +/// +/// ```gleam +/// let result = +/// [1, 2, 3] +/// |> list.map(dynamic.int) +/// |> dynamic.list +/// |> decode.run(decode.list(of: decode.int)) +/// assert result == Ok([1, 2, 3]) +/// ``` +/// +pub fn list(of inner: Decoder(a)) -> Decoder(List(a)) { + Decoder(fn(data) { + decode_list(data, inner.function, fn(p, k) { push_path(p, [k]) }, 0, []) + }) +} + +@external(erlang, "gleam_stdlib", "list") +@external(javascript, "../../gleam_stdlib.mjs", "list") +fn decode_list( + data: Dynamic, + item: fn(Dynamic) -> #(t, List(DecodeError)), + push_path: fn(#(t, List(DecodeError)), key) -> #(t, List(DecodeError)), + index: Int, + acc: List(t), +) -> #(List(t), List(DecodeError)) + +/// A decoder that decodes dicts where all keys and values are decoded with +/// given decoders. +/// +/// ## Examples +/// +/// ```gleam +/// let values = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.int(1)), +/// #(dynamic.string("two"), dynamic.int(2)), +/// ]) +/// +/// let result = +/// decode.run(values, decode.dict(decode.string, decode.int)) +/// assert result == Ok(values) +/// ``` +/// +pub fn dict( + key: Decoder(key), + value: Decoder(value), +) -> Decoder(Dict(key, value)) { + Decoder(fn(data) { + case decode_dict(data) { + Error(_) -> #(dict.new(), decode_error("Dict", data)) + Ok(dict) -> + dict.fold(dict, #(dict.new(), []), fn(a, k, v) { + // If there are any errors from previous key-value pairs then we + // don't need to run the decoders, instead return the existing acc. + case a.1 { + [] -> fold_dict(a, k, v, key.function, value.function) + [_, ..] -> a + } + }) + } + }) +} + +fn fold_dict( + acc: #(Dict(k, v), List(DecodeError)), + key: Dynamic, + value: Dynamic, + key_decoder: fn(Dynamic) -> #(k, List(DecodeError)), + value_decoder: fn(Dynamic) -> #(v, List(DecodeError)), +) -> #(Dict(k, v), List(DecodeError)) { + // First we decode the key. + case key_decoder(key) { + #(key_decoded, []) -> + // Then we decode the value. + case value_decoder(value) { + #(value, []) -> { + // It worked! Insert the new key-value pair so we can move onto the next. + let dict = dict.insert(acc.0, key_decoded, value) + #(dict, acc.1) + } + #(_, errors) -> { + let key_identifier = path_segment_to_string(key) + push_path(#(dict.new(), errors), [key_identifier]) + } + } + #(_, errors) -> push_path(#(dict.new(), errors), ["keys"]) + } +} + +@external(erlang, "gleam_stdlib", "dict") +@external(javascript, "../../gleam_stdlib.mjs", "dict") +fn decode_dict(data: Dynamic) -> Result(Dict(Dynamic, Dynamic), Nil) + +/// A decoder that decodes nullable values of a type decoded by with a given +/// decoder. +/// +/// This function can handle common representations of null on all runtimes, such as +/// `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on +/// JavaScript. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.int(100), decode.optional(decode.int)) +/// assert result == Ok(option.Some(100)) +/// ``` +/// +/// ```gleam +/// let result = decode.run(dynamic.nil(), decode.optional(decode.int)) +/// assert result == Ok(option.None) +/// ``` +/// +pub fn optional(inner: Decoder(a)) -> Decoder(Option(a)) { + Decoder(function: fn(data) { + case is_null(data) { + True -> #(option.None, []) + False -> { + let #(data, errors) = inner.function(data) + #(option.Some(data), errors) + } + } + }) +} + +/// Apply a transformation function to any value decoded by the decoder. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.int |> decode.map(int.to_string) +/// let result = decode.run(dynamic.int(1000), decoder) +/// assert result == Ok("1000") +/// ``` +/// +pub fn map(decoder: Decoder(a), transformer: fn(a) -> b) -> Decoder(b) { + Decoder(function: fn(d) { + let #(data, errors) = decoder.function(d) + #(transformer(data), errors) + }) +} + +/// Apply a transformation function to any errors returned by the decoder. +/// +pub fn map_errors( + decoder: Decoder(a), + transformer: fn(List(DecodeError)) -> List(DecodeError), +) -> Decoder(a) { + Decoder(function: fn(d) { + let #(data, errors) = decoder.function(d) + #(data, transformer(errors)) + }) +} + +/// Replace all errors produced by a decoder with one single error for a named +/// expected type. +/// +/// This function may be useful if you wish to simplify errors before +/// presenting them to a user, particularly when using the `one_of` function. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.string |> decode.collapse_errors("MyThing") +/// let result = decode.run(dynamic.int(1000), decoder) +/// assert result == Error([DecodeError("MyThing", "Int", [])]) +/// ``` +/// +pub fn collapse_errors(decoder: Decoder(a), name: String) -> Decoder(a) { + Decoder(function: fn(dynamic_data) { + let #(data, errors) as layer = decoder.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> #(data, decode_error(name, dynamic_data)) + } + }) +} + +/// Create a new decoder based upon the value of a previous decoder. +/// +/// This may be useful to run one previous decoder to use in further decoding. +/// +pub fn then(decoder: Decoder(a), next: fn(a) -> Decoder(b)) -> Decoder(b) { + Decoder(function: fn(dynamic_data) { + let #(data, errors) = decoder.function(dynamic_data) + let decoder = next(data) + let #(data, _) as layer = decoder.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> #(data, errors) + } + }) +} + +/// Create a new decoder from several other decoders. Each of the inner +/// decoders is run in turn, and the value from the first to succeed is used. +/// +/// If no decoder succeeds then the errors from the first decoder are used. +/// If you wish for different errors then you may wish to use the +/// `collapse_errors` or `map_errors` functions. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.one_of(decode.string, or: [ +/// decode.int |> decode.map(int.to_string), +/// decode.float |> decode.map(float.to_string), +/// ]) +/// assert decode.run(dynamic.int(1000), decoder) == Ok("1000") +/// ``` +/// +pub fn one_of( + first: Decoder(a), + or alternatives: List(Decoder(a)), +) -> Decoder(a) { + Decoder(function: fn(dynamic_data) { + let #(_, errors) as layer = first.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> run_decoders(dynamic_data, layer, alternatives) + } + }) +} + +fn run_decoders( + data: Dynamic, + failure: #(a, List(DecodeError)), + decoders: List(Decoder(a)), +) -> #(a, List(DecodeError)) { + case decoders { + [] -> failure + + [decoder, ..decoders] -> { + let #(_, errors) as layer = decoder.function(data) + case errors { + [] -> layer + [_, ..] -> run_decoders(data, failure, decoders) + } + } + } +} + +/// Define a decoder that always fails. +/// +/// The first parameter is a "placeholder" value, which is some default value that the +/// decoder uses internally in place of the value that would have been produced +/// if the decoder was successful. It doesn't matter what this value is, it is +/// never returned by the decoder or shown to the user, so pick some arbitrary +/// value. If it is an int you might pick `0`, if it is a list you might pick +/// `[]`. +/// +/// The second parameter is the name of the type that has failed to decode. +/// +/// ```gleam +/// decode.failure(User(name: "", score: 0, tags: []), expected: "User") +/// ``` +/// +pub fn failure(placeholder: a, expected name: String) -> Decoder(a) { + Decoder(function: fn(d) { #(placeholder, decode_error(name, d)) }) +} + +/// Create a decoder for a new data type from a decoding function. +/// +/// This function is used for new primitive types. For example, you might +/// define a decoder for Erlang's pid type. +/// +/// A default "placeholder" value is also required to make a decoder. When this +/// decoder is used as part of a larger decoder this placeholder value is used +/// so that the rest of the decoder can continue to run and +/// collect all decoding errors. It doesn't matter what this value is, it is +/// never returned by the decoder or shown to the user, so pick some arbitrary +/// value. If it is an int you might pick `0`, if it is a list you might pick +/// `[]`. +/// +/// If you were to make a decoder for the `Int` type (rather than using the +/// build-in `Int` decoder) you would define it like so: +/// +/// ```gleam +/// pub fn int_decoder() -> decode.Decoder(Int) { +/// let default = "" +/// decode.new_primitive_decoder("Int", int_from_dynamic) +/// } +/// +/// @external(erlang, "my_module", "int_from_dynamic") +/// fn int_from_dynamic(data: Int) -> Result(Int, Int) +/// ``` +/// +/// ```erlang +/// -module(my_module). +/// -export([int_from_dynamic/1]). +/// +/// int_from_dynamic(Data) -> +/// case is_integer(Data) of +/// true -> {ok, Data}; +/// false -> {error, 0} +/// end. +/// ``` +/// +pub fn new_primitive_decoder( + name: String, + decoding_function: fn(Dynamic) -> Result(t, t), +) -> Decoder(t) { + Decoder(function: fn(d) { + case decoding_function(d) { + Ok(t) -> #(t, []) + Error(placeholder) -> #(placeholder, [ + DecodeError(name, dynamic.classify(d), []), + ]) + } + }) +} + +/// Create a decoder that can refer to itself, useful for decoding deeply +/// nested data. +/// +/// Attempting to create a recursive decoder without this function could result +/// in an infinite loop. If you are using `field` or other `use`able functions +/// then you may not need to use this function. +/// +/// ## Examples +/// +/// ```gleam +/// type Nested { +/// Nested(List(Nested)) +/// Value(String) +/// } +/// +/// fn nested_decoder() -> decode.Decoder(Nested) { +/// use <- decode.recursive +/// decode.one_of(decode.string |> decode.map(Value), [ +/// decode.list(nested_decoder()) |> decode.map(Nested), +/// ]) +/// } +/// ``` +/// +pub fn recursive(inner: fn() -> Decoder(a)) -> Decoder(a) { + Decoder(function: fn(data) { + let decoder = inner() + decoder.function(data) + }) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../../gleam_stdlib.mjs", "identity") +fn cast(a: anything) -> Dynamic + +@external(erlang, "gleam_stdlib", "is_null") +@external(javascript, "../../gleam_stdlib.mjs", "is_null") +fn is_null(a: Dynamic) -> Bool diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/float.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/float.gleam new file mode 100644 index 0000000..aa9bd7f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/float.gleam @@ -0,0 +1,621 @@ +//// Functions for working with floats. +//// +//// ## Float representation +//// +//// Floats are represented as 64 bit floating point numbers on both the Erlang +//// and JavaScript runtimes. The floating point behaviour is native to their +//// respective runtimes, so their exact behaviour will be slightly different on +//// the two runtimes. +//// +//// ### Infinity and NaN +//// +//// Under the JavaScript runtime, exceeding the maximum (or minimum) +//// representable value for a floating point value will result in Infinity (or +//// -Infinity). Should you try to divide two infinities you will get NaN as a +//// result. +//// +//// When running on BEAM, exceeding the maximum (or minimum) representable +//// value for a floating point value will raise an error. +//// +//// ## Division by zero +//// +//// Gleam runs on the Erlang virtual machine, which does not follow the IEEE +//// 754 standard for floating point arithmetic and does not have an `Infinity` +//// value. In Erlang division by zero results in a crash, however Gleam does +//// not have partial functions and operators in core so instead division by zero +//// returns zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/order.{type Order} + +/// Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was +/// not possible. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("2.3") == Ok(2.3) +/// ``` +/// +/// ```gleam +/// assert parse("ABC") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_float") +@external(javascript, "../gleam_stdlib.mjs", "parse_float") +pub fn parse(string: String) -> Result(Float, Nil) + +/// Returns the string representation of the provided `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(2.3) == "2.3" +/// ``` +/// +@external(erlang, "gleam_stdlib", "float_to_string") +@external(javascript, "../gleam_stdlib.mjs", "float_to_string") +pub fn to_string(x: Float) -> String + +/// Restricts a float between two bounds. +/// +/// Note: If the `min` argument is larger than the `max` argument then they +/// will be swapped, so the minimum bound is always lower than the maximum +/// bound. +/// +/// +/// ## Examples +/// +/// ```gleam +/// assert clamp(1.2, min: 1.4, max: 1.6) == 1.4 +/// ``` +/// +/// ```gleam +/// assert clamp(1.2, min: 1.4, max: 0.6) == 1.2 +/// ``` +/// +pub fn clamp(x: Float, min min_bound: Float, max max_bound: Float) -> Float { + case min_bound >=. max_bound { + True -> x |> min(min_bound) |> max(max_bound) + False -> x |> min(max_bound) |> max(min_bound) + } +} + +/// Compares two `Float`s, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(2.0, 2.3) == Lt +/// ``` +/// +/// To handle +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) +/// you may use [`loosely_compare`](#loosely_compare) instead. +/// +pub fn compare(a: Float, with b: Float) -> Order { + case a == b { + True -> order.Eq + False -> + case a <. b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two `Float`s within a tolerance, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// assert loosely_compare(5.0, with: 5.3, tolerating: 0.5) == Eq +/// ``` +/// +/// If you want to check only for equality you may use +/// [`loosely_equals`](#loosely_equals) instead. +/// +pub fn loosely_compare( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Order { + let difference = absolute_value(a -. b) + case difference <=. tolerance { + True -> order.Eq + False -> compare(a, b) + } +} + +/// Checks for equality of two `Float`s within a tolerance, +/// returning a `Bool`. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// assert loosely_equals(5.0, with: 5.3, tolerating: 0.5) +/// ``` +/// +/// ```gleam +/// assert !loosely_equals(5.0, with: 5.1, tolerating: 0.1) +/// ``` +/// +pub fn loosely_equals( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Bool { + let difference = absolute_value(a -. b) + difference <=. tolerance +} + +/// Compares two `Float`s, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert min(2.0, 2.3) == 2.0 +/// ``` +/// +pub fn min(a: Float, b: Float) -> Float { + case a <. b { + True -> a + False -> b + } +} + +/// Compares two `Float`s, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert max(2.0, 2.3) == 2.3 +/// ``` +/// +pub fn max(a: Float, b: Float) -> Float { + case a >. b { + True -> a + False -> b + } +} + +/// Rounds the value to the next highest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert ceiling(2.3) == 3.0 +/// ``` +/// +@external(erlang, "math", "ceil") +@external(javascript, "../gleam_stdlib.mjs", "ceiling") +pub fn ceiling(x: Float) -> Float + +/// Rounds the value to the next lowest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert floor(2.3) == 2.0 +/// ``` +/// +@external(erlang, "math", "floor") +@external(javascript, "../gleam_stdlib.mjs", "floor") +pub fn floor(x: Float) -> Float + +/// Rounds the value to the nearest whole number as an `Int`. +/// +/// ## Examples +/// +/// ```gleam +/// assert round(2.3) == 2 +/// ``` +/// +/// ```gleam +/// assert round(2.5) == 3 +/// ``` +/// +@external(erlang, "erlang", "round") +pub fn round(x: Float) -> Int { + case x >=. 0.0 { + True -> js_round(x) + False -> 0 - js_round(negate(x)) + } +} + +@external(javascript, "../gleam_stdlib.mjs", "round") +fn js_round(a: Float) -> Int + +/// Returns the value as an `Int`, truncating all decimal digits. +/// +/// ## Examples +/// +/// ```gleam +/// assert truncate(2.4343434847383438) == 2 +/// ``` +/// +@external(erlang, "erlang", "trunc") +@external(javascript, "../gleam_stdlib.mjs", "truncate") +pub fn truncate(x: Float) -> Int + +/// Converts the value to a given precision as a `Float`. +/// The precision is the number of allowed decimal places. +/// Negative precisions are allowed and force rounding +/// to the nearest tenth, hundredth, thousandth etc. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_precision(2.43434348473, 2) == 2.43 +/// ``` +/// +/// ```gleam +/// assert to_precision(547890.453444, -3) == 548000.0 +/// ``` +/// +pub fn to_precision(x: Float, precision: Int) -> Float { + case precision <= 0 { + True -> { + let factor = do_power(10.0, do_to_float(-precision)) + do_to_float(round(x /. factor)) *. factor + } + False -> { + let factor = do_power(10.0, do_to_float(precision)) + do_to_float(round(x *. factor)) /. factor + } + } +} + +@external(erlang, "erlang", "float") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_to_float(a: Int) -> Float + +/// Returns the absolute value of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert absolute_value(-12.5) == 12.5 +/// ``` +/// +/// ```gleam +/// assert absolute_value(10.2) == 10.2 +/// ``` +/// +pub fn absolute_value(x: Float) -> Float { + case x >=. 0.0 { + True -> x + False -> 0.0 -. x + } +} + +/// Returns the result of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert power(2.0, -1.0) == Ok(0.5) +/// ``` +/// +/// ```gleam +/// assert power(2.0, 2.0) == Ok(4.0) +/// ``` +/// +/// ```gleam +/// assert power(8.0, 1.5) == Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// assert 4.0 |> power(of: 2.0) == Ok(16.0) +/// ``` +/// +/// ```gleam +/// assert power(-1.0, 0.5) == Error(Nil) +/// ``` +/// +pub fn power(base: Float, of exponent: Float) -> Result(Float, Nil) { + let fractional: Bool = ceiling(exponent) -. exponent >. 0.0 + // In the following check: + // 1. If the base is negative and the exponent is fractional then + // return an error as it will otherwise be an imaginary number + // 2. If the base is 0 and the exponent is negative then the expression + // is equivalent to the exponent divided by 0 and an error should be + // returned + case base <. 0.0 && fractional || base == 0.0 && exponent <. 0.0 { + True -> Error(Nil) + False -> Ok(do_power(base, exponent)) + } +} + +@external(erlang, "math", "pow") +@external(javascript, "../gleam_stdlib.mjs", "power") +fn do_power(a: Float, b: Float) -> Float + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert square_root(4.0) == Ok(2.0) +/// ``` +/// +/// ```gleam +/// assert square_root(-16.0) == Error(Nil) +/// ``` +/// +pub fn square_root(x: Float) -> Result(Float, Nil) { + power(x, 0.5) +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(1.0) == -1.0 +/// ``` +/// +pub fn negate(x: Float) -> Float { + -1.0 *. x +} + +/// Sums a list of `Float`s. +/// +/// ## Example +/// +/// ```gleam +/// assert sum([1.0, 2.2, 3.3]) == 6.5 +/// ``` +/// +pub fn sum(numbers: List(Float)) -> Float { + sum_loop(numbers, 0.0) +} + +fn sum_loop(numbers: List(Float), initial: Float) -> Float { + case numbers { + [first, ..rest] -> sum_loop(rest, first +. initial) + [] -> initial + } +} + +/// Multiplies a list of `Float`s and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// assert product([2.5, 3.2, 4.2]) == 33.6 +/// ``` +/// +pub fn product(numbers: List(Float)) -> Float { + product_loop(numbers, 1.0) +} + +fn product_loop(numbers: List(Float), initial: Float) -> Float { + case numbers { + [first, ..rest] -> product_loop(rest, first *. initial) + [] -> initial + } +} + +/// Generates a random float between the given zero (inclusive) and one +/// (exclusive). +/// +/// On Erlang this updates the random state in the process dictionary. +/// See: +/// +/// ## Examples +/// +/// ```gleam +/// random() +/// // -> 0.646355926896028 +/// ``` +/// +@external(erlang, "rand", "uniform") +@external(javascript, "../gleam_stdlib.mjs", "random_uniform") +pub fn random() -> Float + +/// Computes the modulo of a float division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// The computed value will always have the same sign as the `divisor`. +/// +/// ## Examples +/// +/// ```gleam +/// assert modulo(13.3, by: 3.3) == Ok(0.1) +/// ``` +/// +/// ```gleam +/// assert modulo(-13.3, by: 3.3) == Ok(3.2) +/// ``` +/// +/// ```gleam +/// assert modulo(13.3, by: -3.3) == Ok(-3.2) +/// ``` +/// +/// ```gleam +/// assert modulo(-13.3, by: -3.3) == Ok(-0.1) +/// ``` +/// +pub fn modulo(dividend: Float, by divisor: Float) -> Result(Float, Nil) { + case divisor { + 0.0 -> Error(Nil) + _ -> Ok(dividend -. floor(dividend /. divisor) *. divisor) + } +} + +/// Returns division of the inputs as a `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// assert divide(0.0, 1.0) == Ok(0.0) +/// ``` +/// +/// ```gleam +/// assert divide(1.0, 0.0) == Error(Nil) +/// ``` +/// +pub fn divide(a: Float, by b: Float) -> Result(Float, Nil) { + case b { + 0.0 -> Error(Nil) + b -> Ok(a /. b) + } +} + +/// Adds two floats together. +/// +/// It's the function equivalent of the `+.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert add(1.0, 2.0) == 3.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1.0, 2.0, 3.0], 0.0, add) == 6.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> add(2.0) == 5.0 +/// ``` +/// +pub fn add(a: Float, b: Float) -> Float { + a +. b +} + +/// Multiplies two floats together. +/// +/// It's the function equivalent of the `*.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert multiply(2.0, 4.0) == 8.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([2.0, 3.0, 4.0], 1.0, multiply) == 24.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> multiply(2.0) == 6.0 +/// ``` +/// +pub fn multiply(a: Float, b: Float) -> Float { + a *. b +} + +/// Subtracts one float from another. +/// +/// It's the function equivalent of the `-.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert subtract(3.0, 1.0) == 2.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1.0, 2.0, 3.0], 10.0, subtract) == 4.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> subtract(_, 2.0) == 1.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> subtract(2.0, _) == -1.0 +/// ``` +/// +pub fn subtract(a: Float, b: Float) -> Float { + a -. b +} + +/// Returns the natural logarithm (base e) of the given `Float` as a `Result`. If the +/// input is less than or equal to 0, returns `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert logarithm(1.0) == Ok(0.0) +/// ``` +/// +/// ```gleam +/// assert logarithm(2.718281828459045) == Ok(1.0) +/// ``` +/// +/// ```gleam +/// assert logarithm(0.0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert logarithm(-1.0) == Error(Nil) +/// ``` +/// +pub fn logarithm(x: Float) -> Result(Float, Nil) { + // In the following check: + // 1. If x is negative then return an error as the natural logarithm + // of a negative number is undefined (would be a complex number) + // 2. If x is 0 then return an error as the natural logarithm of 0 + // approaches negative infinity + case x <=. 0.0 { + True -> Error(Nil) + False -> Ok(do_log(x)) + } +} + +@external(erlang, "math", "log") +@external(javascript, "../gleam_stdlib.mjs", "log") +fn do_log(x: Float) -> Float + +/// Returns e (Euler's number) raised to the power of the given exponent, as +/// a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert exponential(0.0) == Ok(1.0) +/// ``` +/// +/// ```gleam +/// assert exponential(1.0) == Ok(2.718281828459045) +/// ``` +/// +/// ```gleam +/// assert exponential(-1.0) == Ok(0.36787944117144233) +/// ``` +/// +@external(erlang, "math", "exp") +@external(javascript, "../gleam_stdlib.mjs", "exp") +pub fn exponential(x: Float) -> Float diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/function.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/function.gleam new file mode 100644 index 0000000..3537579 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/function.gleam @@ -0,0 +1,5 @@ +/// Takes a single argument and always returns its input value. +/// +pub fn identity(x: a) -> a { + x +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/int.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/int.gleam new file mode 100644 index 0000000..0cf2742 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/int.gleam @@ -0,0 +1,828 @@ +//// Functions for working with integers. +//// +//// ## Division by zero +//// +//// In Erlang division by zero results in a crash, however Gleam does not have +//// partial functions and operators in core so instead division by zero returns +//// zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/float +import gleam/order.{type Order} + +/// Returns the absolute value of the input. +/// +/// ## Examples +/// +/// ```gleam +/// assert absolute_value(-12) == 12 +/// ``` +/// +/// ```gleam +/// assert absolute_value(10) == 10 +/// ``` +/// +pub fn absolute_value(x: Int) -> Int { + case x >= 0 { + True -> x + False -> x * -1 + } +} + +/// Returns the result of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert power(2, -1.0) == Ok(0.5) +/// ``` +/// +/// ```gleam +/// assert power(2, 2.0) == Ok(4.0) +/// ``` +/// +/// ```gleam +/// assert power(8, 1.5) == Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// assert 4 |> power(of: 2.0) == Ok(16.0) +/// ``` +/// +/// ```gleam +/// assert power(-1, 0.5) == Error(Nil) +/// ``` +/// +pub fn power(base: Int, of exponent: Float) -> Result(Float, Nil) { + base + |> to_float + |> float.power(exponent) +} + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert square_root(4) == Ok(2.0) +/// ``` +/// +/// ```gleam +/// assert square_root(-16) == Error(Nil) +/// ``` +/// +pub fn square_root(x: Int) -> Result(Float, Nil) { + x + |> to_float + |> float.square_root() +} + +/// Parses a given string as an int if possible. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("2") == Ok(2) +/// ``` +/// +/// ```gleam +/// assert parse("ABC") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_int") +@external(javascript, "../gleam_stdlib.mjs", "parse_int") +pub fn parse(string: String) -> Result(Int, Nil) + +/// Parses a given string as an int in a given base if possible. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert base_parse("10", 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert base_parse("30", 16) == Ok(48) +/// ``` +/// +/// ```gleam +/// assert base_parse("1C", 36) == Ok(48) +/// ``` +/// +/// ```gleam +/// assert base_parse("48", 1) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert base_parse("48", 37) == Error(Nil) +/// ``` +/// +pub fn base_parse(string: String, base: Int) -> Result(Int, Nil) { + case base >= 2 && base <= 36 { + True -> do_base_parse(string, base) + False -> Error(Nil) + } +} + +@external(erlang, "gleam_stdlib", "int_from_base_string") +@external(javascript, "../gleam_stdlib.mjs", "int_from_base_string") +fn do_base_parse(a: String, b: Int) -> Result(Int, Nil) + +/// Prints a given int to a string. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(2) == "2" +/// ``` +/// +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "to_string") +pub fn to_string(x: Int) -> String + +/// Prints a given int to a string using the base number provided. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`. +/// For common bases (2, 8, 16, 36), use the `to_baseN` functions. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base_string(2, 2) == Ok("10") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 16) == Ok("30") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 36) == Ok("1C") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 1) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 37) == Error(Nil) +/// ``` +/// +pub fn to_base_string(x: Int, base: Int) -> Result(String, Nil) { + case base >= 2 && base <= 36 { + True -> Ok(do_to_base_string(x, base)) + False -> Error(Nil) + } +} + +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "int_to_base_string") +fn do_to_base_string(a: Int, b: Int) -> String + +/// Prints a given int to a string using base-2. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base2(2) == "10" +/// ``` +/// +pub fn to_base2(x: Int) -> String { + do_to_base_string(x, 2) +} + +/// Prints a given int to a string using base-8. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base8(15) == "17" +/// ``` +/// +pub fn to_base8(x: Int) -> String { + do_to_base_string(x, 8) +} + +/// Prints a given int to a string using base-16. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base16(48) == "30" +/// ``` +/// +pub fn to_base16(x: Int) -> String { + do_to_base_string(x, 16) +} + +/// Prints a given int to a string using base-36. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base36(48) == "1C" +/// ``` +/// +pub fn to_base36(x: Int) -> String { + do_to_base_string(x, 36) +} + +/// Takes an int and returns its value as a float. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_float(5) == 5.0 +/// ``` +/// +/// ```gleam +/// assert to_float(0) == 0.0 +/// ``` +/// +/// ```gleam +/// assert to_float(-3) == -3.0 +/// ``` +/// +@external(erlang, "erlang", "float") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn to_float(x: Int) -> Float + +/// Restricts an int between two bounds. +/// +/// Note: If the `min` argument is larger than the `max` argument then they +/// will be swapped, so the minimum bound is always lower than the maximum +/// bound. +/// +/// ## Examples +/// +/// ```gleam +/// assert clamp(40, min: 50, max: 60) == 50 +/// ``` +/// +/// ```gleam +/// assert clamp(40, min: 50, max: 30) == 40 +/// ``` +/// +pub fn clamp(x: Int, min min_bound: Int, max max_bound: Int) -> Int { + case min_bound >= max_bound { + True -> x |> min(min_bound) |> max(max_bound) + False -> x |> min(max_bound) |> max(min_bound) + } +} + +/// Compares two ints, returning an order. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(2, 3) == Lt +/// ``` +/// +/// ```gleam +/// assert compare(4, 3) == Gt +/// ``` +/// +/// ```gleam +/// assert compare(3, 3) == Eq +/// ``` +/// +pub fn compare(a: Int, with b: Int) -> Order { + case a == b { + True -> order.Eq + False -> + case a < b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two ints, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert min(2, 3) == 2 +/// ``` +/// +pub fn min(a: Int, b: Int) -> Int { + case a < b { + True -> a + False -> b + } +} + +/// Compares two ints, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert max(2, 3) == 3 +/// ``` +/// +pub fn max(a: Int, b: Int) -> Int { + case a > b { + True -> a + False -> b + } +} + +/// Returns whether the value provided is even. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_even(2) +/// ``` +/// +/// ```gleam +/// assert !is_even(3) +/// ``` +/// +pub fn is_even(x: Int) -> Bool { + x % 2 == 0 +} + +/// Returns whether the value provided is odd. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_odd(3) +/// ``` +/// +/// ```gleam +/// assert !is_odd(2) +/// ``` +/// +pub fn is_odd(x: Int) -> Bool { + x % 2 != 0 +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(1) == -1 +/// ``` +/// +pub fn negate(x: Int) -> Int { + -1 * x +} + +/// Sums a list of ints. +/// +/// ## Example +/// +/// ```gleam +/// assert sum([1, 2, 3]) == 6 +/// ``` +/// +pub fn sum(numbers: List(Int)) -> Int { + sum_loop(numbers, 0) +} + +fn sum_loop(numbers: List(Int), initial: Int) -> Int { + case numbers { + [first, ..rest] -> sum_loop(rest, first + initial) + [] -> initial + } +} + +/// Multiplies a list of ints and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// assert product([2, 3, 4]) == 24 +/// ``` +/// +pub fn product(numbers: List(Int)) -> Int { + product_loop(numbers, 1) +} + +fn product_loop(numbers: List(Int), initial: Int) -> Int { + case numbers { + [first, ..rest] -> product_loop(rest, first * initial) + [] -> initial + } +} + +/// Generates a random int between zero and the given maximum. +/// +/// The lower number is inclusive, the upper number is exclusive. +/// +/// ## Examples +/// +/// ```gleam +/// random(10) +/// // -> 4 +/// ``` +/// +/// ```gleam +/// random(1) +/// // -> 0 +/// ``` +/// +/// ```gleam +/// random(-1) +/// // -> -1 +/// ``` +/// +pub fn random(max: Int) -> Int { + { float.random() *. to_float(max) } + |> float.floor + |> float.round +} + +/// Performs a truncated integer division. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert divide(0, 1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert divide(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert divide(5, 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert divide(-99, 2) == Ok(-49) +/// ``` +/// +pub fn divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend / divisor) + } +} + +/// Computes the remainder of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Most of the time you will want to use the `%` operator instead of this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// assert remainder(3, 2) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert remainder(10, -1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert remainder(13, by: 3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(-13, by: 3) == Ok(-1) +/// ``` +/// +/// ```gleam +/// assert remainder(13, by: -3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(-13, by: -3) == Ok(-1) +/// ``` +/// +pub fn remainder(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend % divisor) + } +} + +/// Computes the modulo of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Note that this is different from `int.remainder` and `%` in that the +/// computed value will always have the same sign as the `divisor`. +/// +/// ## Examples +/// +/// ```gleam +/// assert modulo(3, 2) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert modulo(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert modulo(10, -1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert modulo(13, by: 3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert modulo(-13, by: 3) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert modulo(13, by: -3) == Ok(-2) +/// ``` +/// +pub fn modulo(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + _ -> { + let remainder = dividend % divisor + case remainder * divisor < 0 { + True -> Ok(remainder + divisor) + False -> Ok(remainder) + } + } + } +} + +/// Performs a *floored* integer division, which means that the result will +/// always be rounded towards negative infinity. +/// +/// If you want to perform truncated integer division (rounding towards zero), +/// use `int.divide()` or the `/` operator instead. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert floor_divide(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert floor_divide(5, 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert floor_divide(6, -4) == Ok(-2) +/// ``` +/// +/// ```gleam +/// assert floor_divide(-99, 2) == Ok(-50) +/// ``` +/// +pub fn floor_divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> + case dividend * divisor < 0 && dividend % divisor != 0 { + True -> Ok(dividend / divisor - 1) + False -> Ok(dividend / divisor) + } + } +} + +/// Adds two integers together. +/// +/// It's the function equivalent of the `+` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert add(1, 2) == 3 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// assert list.fold([1, 2, 3], 0, add) == 6 +/// ``` +/// +/// ```gleam +/// assert 3 |> add(2) == 5 +/// ``` +/// +pub fn add(a: Int, b: Int) -> Int { + a + b +} + +/// Multiplies two integers together. +/// +/// It's the function equivalent of the `*` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert multiply(2, 4) == 8 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([2, 3, 4], 1, multiply) == 24 +/// ``` +/// +/// ```gleam +/// assert 3 |> multiply(2) == 6 +/// ``` +/// +pub fn multiply(a: Int, b: Int) -> Int { + a * b +} + +/// Subtracts one int from another. +/// +/// It's the function equivalent of the `-` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert subtract(3, 1) == 2 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1, 2, 3], 10, subtract) == 4 +/// ``` +/// +/// ```gleam +/// assert 3 |> subtract(2) == 1 +/// ``` +/// +/// ```gleam +/// assert 3 |> subtract(2, _) == -1 +/// ``` +/// +pub fn subtract(a: Int, b: Int) -> Int { + a - b +} + +/// Calculates the bitwise AND of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "band") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_and") +pub fn bitwise_and(x: Int, y: Int) -> Int + +/// Calculates the bitwise NOT of its argument. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bnot") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_not") +pub fn bitwise_not(x: Int) -> Int + +/// Calculates the bitwise OR of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_or") +pub fn bitwise_or(x: Int, y: Int) -> Int + +/// Calculates the bitwise XOR of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bxor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_exclusive_or") +pub fn bitwise_exclusive_or(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic left bitshift. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bsl") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_left") +pub fn bitwise_shift_left(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic right bitshift. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bsr") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_right") +pub fn bitwise_shift_right(x: Int, y: Int) -> Int + +/// Run a function for each int between ints `from` and `to`. +/// +/// `from` is inclusive, and `to` is exclusive. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// range(from: 0, to: 3, with: "", run: fn(acc, i) { +/// acc <> to_string(i) +/// }) +/// == "012" +/// ``` +/// +/// ```gleam +/// assert range(from: 1, to: -2, with: [], run: list.prepend) == [-1, 0, 1] +/// ``` +/// +pub fn range( + from start: Int, + to stop: Int, + with acc: acc, + run reducer: fn(acc, Int) -> acc, +) -> acc { + let increment = case start < stop { + True -> 1 + False -> -1 + } + range_loop(start, stop, increment, acc, reducer) +} + +fn range_loop( + current: Int, + stop: Int, + increment: Int, + acc: acc, + reducer: fn(acc, Int) -> acc, +) -> acc { + case current == stop { + True -> acc + False -> { + let acc = reducer(acc, current) + let current = current + increment + range_loop(current, stop, increment, acc, reducer) + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/io.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/io.gleam new file mode 100644 index 0000000..0d51c9f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/io.gleam @@ -0,0 +1,55 @@ +/// Writes a string to standard output (stdout). +/// +/// If you want your output to be printed on its own line see `println`. +/// +/// ## Example +/// +/// ```gleam +/// assert io.print("Hi mum") == Nil +/// // Hi mum +/// ``` +/// +@external(erlang, "gleam_stdlib", "print") +@external(javascript, "../gleam_stdlib.mjs", "print") +pub fn print(string: String) -> Nil + +/// Writes a string to standard error (stderr). +/// +/// If you want your output to be printed on its own line see `println_error`. +/// +/// ## Example +/// +/// ```gleam +/// assert io.print_error("Hi pop") == Nil +/// // Hi pop +/// ``` +/// +@external(erlang, "gleam_stdlib", "print_error") +@external(javascript, "../gleam_stdlib.mjs", "print_error") +pub fn print_error(string: String) -> Nil + +/// Writes a string to standard output (stdout), appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// assert io.println("Hi mum") == Nil +/// // Hi mum +/// ``` +/// +@external(erlang, "gleam_stdlib", "println") +@external(javascript, "../gleam_stdlib.mjs", "console_log") +pub fn println(string: String) -> Nil + +/// Writes a string to standard error (stderr), appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// assert io.println_error("Hi pop") == Nil +/// // Hi pop +/// ``` +/// +@external(erlang, "gleam_stdlib", "println_error") +@external(javascript, "../gleam_stdlib.mjs", "console_error") +pub fn println_error(string: String) -> Nil diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/list.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/list.gleam new file mode 100644 index 0000000..3b6fa2a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/list.gleam @@ -0,0 +1,2279 @@ +//// Lists are an ordered sequence of elements and are one of the most common +//// data types in Gleam. +//// +//// New elements can be added and removed from the front of a list in +//// constant time, while adding and removing from the end requires traversing +//// and copying the whole list, so keep this in mind when designing your +//// programs. +//// +//// There is a dedicated syntax for prefixing to a list: +//// +//// ```gleam +//// let new_list = [1, 2, ..existing_list] +//// ``` +//// +//// And a matching syntax for getting the first elements of a list: +//// +//// ```gleam +//// case list { +//// [first_element, ..rest] -> first_element +//// _ -> "this pattern matches when the list is empty" +//// } +//// ``` +//// + +import gleam/dict.{type Dict} +import gleam/float +import gleam/int +import gleam/order.{type Order} + +/// Counts the number of elements in a given list. +/// +/// This function has to traverse the list to determine the number of elements, +/// so it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// assert length([]) == 0 +/// ``` +/// +/// ```gleam +/// assert length([1]) == 1 +/// ``` +/// +/// ```gleam +/// assert length([1, 2]) == 2 +/// ``` +/// +@external(erlang, "erlang", "length") +pub fn length(of list: List(a)) -> Int { + length_loop(list, 0) +} + +fn length_loop(list: List(a), count: Int) -> Int { + case list { + [_, ..list] -> length_loop(list, count + 1) + [] -> count + } +} + +/// Counts the number of elements in a given list satisfying a given predicate. +/// +/// This function has to traverse the list to determine the number of elements, +/// so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert count([], fn(a) { a > 0 }) == 0 +/// ``` +/// +/// ```gleam +/// assert count([1], fn(a) { a > 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert count([1, 2, 3], int.is_odd) == 2 +/// ``` +/// +pub fn count(list: List(a), where predicate: fn(a) -> Bool) -> Int { + count_loop(list, predicate, 0) +} + +fn count_loop(list: List(a), predicate: fn(a) -> Bool, acc: Int) -> Int { + case list { + [] -> acc + [first, ..rest] -> + case predicate(first) { + True -> count_loop(rest, predicate, acc + 1) + False -> count_loop(rest, predicate, acc) + } + } +} + +/// Creates a new list from a given list containing the same elements but in the +/// opposite order. +/// +/// This function has to traverse the list to create the new reversed list, so +/// it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// assert reverse([]) == [] +/// ``` +/// +/// ```gleam +/// assert reverse([1]) == [1] +/// ``` +/// +/// ```gleam +/// assert reverse([1, 2]) == [2, 1] +/// ``` +/// +@external(erlang, "lists", "reverse") +pub fn reverse(list: List(a)) -> List(a) { + reverse_and_prepend(list, []) +} + +/// Reverses a list and prepends it to another list. +/// This function runs in linear time, proportional to the length of the list +/// to prepend. +/// +@external(erlang, "lists", "reverse") +fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { + case prefix { + [] -> suffix + [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) + } +} + +/// Determines whether or not the list is empty. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_empty([]) +/// ``` +/// +/// ```gleam +/// assert !is_empty([1]) +/// ``` +/// +/// ```gleam +/// assert !is_empty([1, 1]) +/// ``` +/// +pub fn is_empty(list: List(a)) -> Bool { + list == [] +} + +/// Determines whether or not a given element exists within a given list. +/// +/// This function traverses the list to find the element, so it runs in linear +/// time. +/// +/// ## Examples +/// +/// ```gleam +/// assert !contains([], any: 0) +/// ``` +/// +/// ```gleam +/// assert [0] |> contains(any: 0) +/// ``` +/// +/// ```gleam +/// assert !contains([1], any: 0) +/// ``` +/// +/// ```gleam +/// assert !contains([1, 1], any: 0) +/// ``` +/// +/// ```gleam +/// assert [1, 0] |> contains(any: 0) +/// ``` +/// +pub fn contains(list: List(a), any elem: a) -> Bool { + case list { + [] -> False + [first, ..] if first == elem -> True + [_, ..rest] -> contains(rest, elem) + } +} + +/// Gets the first element from the start of the list, if there is one. +/// +/// ## Examples +/// +/// ```gleam +/// assert first([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert first([0]) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert first([1, 2]) == Ok(1) +/// ``` +/// +pub fn first(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..] -> Ok(first) + } +} + +/// Returns the list minus the first element. If the list is empty, `Error(Nil)` is +/// returned. +/// +/// This function runs in constant time and does not make a copy of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert rest([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert rest([0]) == Ok([]) +/// ``` +/// +/// ```gleam +/// assert rest([1, 2]) == Ok([2]) +/// ``` +/// +pub fn rest(list: List(a)) -> Result(List(a), Nil) { + case list { + [] -> Error(Nil) + [_, ..rest] -> Ok(rest) + } +} + +/// Groups the elements from the given list by the given key function. +/// +/// Does not preserve the initial value order. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/dict +/// +/// assert +/// [Ok(3), Error("Wrong"), Ok(200), Ok(73)] +/// |> group(by: fn(i) { +/// case i { +/// Ok(_) -> "Successful" +/// Error(_) -> "Failed" +/// } +/// }) +/// |> dict.to_list +/// == [ +/// #("Failed", [Error("Wrong")]), +/// #("Successful", [Ok(73), Ok(200), Ok(3)]) +/// ] +/// ``` +/// +/// ```gleam +/// import gleam/dict +/// +/// assert group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 }) +/// |> dict.to_list +/// == [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])] +/// ``` +/// +pub fn group(list: List(v), by key: fn(v) -> k) -> Dict(k, List(v)) { + dict.group(key, list) +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert filter([2, 4, 6, 1], fn(x) { x > 2 }) == [4, 6] +/// ``` +/// +/// ```gleam +/// assert filter([2, 4, 6, 1], fn(x) { x > 6 }) == [] +/// ``` +/// +pub fn filter(list: List(a), keeping predicate: fn(a) -> Bool) -> List(a) { + filter_loop(list, predicate, []) +} + +fn filter_loop(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let new_acc = case fun(first) { + True -> [first, ..acc] + False -> acc + } + filter_loop(rest, fun, new_acc) + } + } +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `Ok(_)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert filter_map([2, 4, 6, 1], Error) == [] +/// ``` +/// +/// ```gleam +/// assert filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) == [3, 5, 7, 2] +/// ``` +/// +pub fn filter_map(list: List(a), with fun: fn(a) -> Result(b, e)) -> List(b) { + filter_map_loop(list, fun, []) +} + +fn filter_map_loop( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let new_acc = case fun(first) { + Ok(first) -> [first, ..acc] + Error(_) -> acc + } + filter_map_loop(rest, fun, new_acc) + } + } +} + +/// Returns a new list containing the results of applying the supplied function to each element. +/// +/// ## Examples +/// +/// ```gleam +/// assert map([2, 4, 6], fn(x) { x * 2 }) == [4, 8, 12] +/// ``` +/// +pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { + map_loop(list, fun, []) +} + +fn map_loop(list: List(a), fun: fn(a) -> b, acc: List(b)) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> map_loop(rest, fun, [fun(first), ..acc]) + } +} + +/// Combines two lists into a single list using the given function. +/// +/// If a list is longer than the other, the extra elements are dropped. +/// +/// ## Examples +/// +/// ```gleam +/// assert map2([1, 2, 3], [4, 5, 6], fn(x, y) { x + y }) == [5, 7, 9] +/// ``` +/// +/// ```gleam +/// assert map2([1, 2], ["a", "b", "c"], fn(i, x) { #(i, x) }) +/// == [#(1, "a"), #(2, "b")] +/// ``` +/// +pub fn map2(list1: List(a), list2: List(b), with fun: fn(a, b) -> c) -> List(c) { + map2_loop(list1, list2, fun, []) +} + +fn map2_loop( + list1: List(a), + list2: List(b), + fun: fn(a, b) -> c, + acc: List(c), +) -> List(c) { + case list1, list2 { + [], _ | _, [] -> reverse(acc) + [a, ..as_], [b, ..bs] -> map2_loop(as_, bs, fun, [fun(a, b), ..acc]) + } +} + +/// Similar to `map` but also lets you pass around an accumulated value. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// map_fold( +/// over: [1, 2, 3], +/// from: 100, +/// with: fn(memo, i) { #(memo + i, i * 2) } +/// ) +/// == #(106, [2, 4, 6]) +/// ``` +/// +pub fn map_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> #(acc, b), +) -> #(acc, List(b)) { + map_fold_loop(list, fun, initial, []) +} + +fn map_fold_loop( + list: List(a), + fun: fn(acc, a) -> #(acc, b), + acc: acc, + list_acc: List(b), +) -> #(acc, List(b)) { + case list { + [] -> #(acc, reverse(list_acc)) + [first, ..rest] -> { + let #(acc, first) = fun(acc, first) + map_fold_loop(rest, fun, acc, [first, ..list_acc]) + } + } +} + +/// Similar to `map`, but the supplied function will also be passed the index +/// of the element being mapped as an additional argument. +/// +/// The index starts at 0, so the first element is 0, the second is 1, and so +/// on. +/// +/// ## Examples +/// +/// ```gleam +/// assert index_map(["a", "b"], fn(x, i) { #(i, x) }) == [#(0, "a"), #(1, "b")] +/// ``` +/// +pub fn index_map(list: List(a), with fun: fn(a, Int) -> b) -> List(b) { + index_map_loop(list, fun, 0, []) +} + +fn index_map_loop( + list: List(a), + fun: fn(a, Int) -> b, + index: Int, + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let acc = [fun(first, index), ..acc] + index_map_loop(rest, fun, index + 1, acc) + } + } +} + +/// Takes a function that returns a `Result` and applies it to each element in a +/// given list in turn. +/// +/// If the function returns `Ok(new_value)` for all elements in the list then a +/// list of the new values is returned. +/// +/// If the function returns `Error(reason)` for any of the elements then it is +/// returned immediately. None of the elements in the list are processed after +/// one returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert try_map([1, 2, 3], fn(x) { Ok(x + 2) }) == Ok([3, 4, 5]) +/// ``` +/// +/// ```gleam +/// assert try_map([1, 2, 3], fn(_) { Error(0) }) == Error(0) +/// ``` +/// +/// ```gleam +/// assert try_map([[1], [2, 3]], first) == Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// assert try_map([[1], [], [2]], first) == Error(Nil) +/// ``` +/// +pub fn try_map( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(List(b), e) { + try_map_loop(list, fun, []) +} + +fn try_map_loop( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> Result(List(b), e) { + case list { + [] -> Ok(reverse(acc)) + [first, ..rest] -> + case fun(first) { + Ok(first) -> try_map_loop(rest, fun, [first, ..acc]) + Error(error) -> Error(error) + } + } +} + +/// Returns a list that is the given list with up to the given number of +/// elements removed from the front of the list. +/// +/// If the list has less than the number of elements an empty list is +/// returned. +/// +/// This function runs in linear time but does not copy the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop([1, 2, 3, 4], 2) == [3, 4] +/// ``` +/// +/// ```gleam +/// assert drop([1, 2, 3, 4], 9) == [] +/// ``` +/// +pub fn drop(from list: List(a), up_to n: Int) -> List(a) { + case n <= 0 { + True -> list + False -> + case list { + [] -> [] + [_, ..rest] -> drop(rest, n - 1) + } + } +} + +/// Returns a list containing the first given number of elements from the given +/// list. +/// +/// If the list has less than the number of elements then the full list is +/// returned. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert take([1, 2, 3, 4], 2) == [1, 2] +/// ``` +/// +/// ```gleam +/// assert take([1, 2, 3, 4], 9) == [1, 2, 3, 4] +/// ``` +/// +pub fn take(from list: List(a), up_to n: Int) -> List(a) { + take_loop(list, n, []) +} + +fn take_loop(list: List(a), n: Int, acc: List(a)) -> List(a) { + case n <= 0 { + True -> reverse(acc) + False -> + case list { + [] -> reverse(acc) + [first, ..rest] -> take_loop(rest, n - 1, [first, ..acc]) + } + } +} + +/// Returns a new empty list. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() == [] +/// ``` +/// +pub fn new() -> List(a) { + [] +} + +/// Returns the given item wrapped in a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert wrap(1) == [1] +/// ``` +/// +/// ```gleam +/// assert wrap(["a", "b", "c"]) == [["a", "b", "c"]] +/// ``` +/// +/// ```gleam +/// assert wrap([[]]) == [[[]]] +/// ``` +/// +/// +pub fn wrap(item: a) -> List(a) { + [item] +} + +/// Joins one list onto the end of another. +/// +/// This function runs in linear time, and it traverses and copies the first +/// list. +/// +/// ## Examples +/// +/// ```gleam +/// assert append([1, 2], [3]) == [1, 2, 3] +/// ``` +/// +@external(erlang, "lists", "append") +pub fn append(first: List(a), second: List(a)) -> List(a) { + append_loop(reverse(first), second) +} + +fn append_loop(first: List(a), second: List(a)) -> List(a) { + case first { + [] -> second + [first, ..rest] -> append_loop(rest, [first, ..second]) + } +} + +/// Prefixes an item to a list. This can also be done using the dedicated +/// syntax instead. +/// +/// ```gleam +/// let existing_list = [2, 3, 4] +/// assert [1, ..existing_list] == [1, 2, 3, 4] +/// ``` +/// +/// ```gleam +/// let existing_list = [2, 3, 4] +/// assert prepend(to: existing_list, this: 1) == [1, 2, 3, 4] +/// ``` +/// +pub fn prepend(to list: List(a), this item: a) -> List(a) { + [item, ..list] +} + +/// Joins a list of lists into a single list. +/// +/// This function traverses all elements twice on the JavaScript target. +/// This function traverses all elements once on the Erlang target. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten([[1], [2, 3], []]) == [1, 2, 3] +/// ``` +/// +@external(erlang, "lists", "append") +pub fn flatten(lists: List(List(a))) -> List(a) { + flatten_loop(lists, []) +} + +fn flatten_loop(lists: List(List(a)), acc: List(a)) -> List(a) { + case lists { + [] -> reverse(acc) + [list, ..further_lists] -> + flatten_loop(further_lists, reverse_and_prepend(list, to: acc)) + } +} + +/// Maps the list with the given function into a list of lists, and then flattens it. +/// +/// ## Examples +/// +/// ```gleam +/// assert flat_map([2, 4, 6], fn(x) { [x, x + 1] }) == [2, 3, 4, 5, 6, 7] +/// ``` +/// +pub fn flat_map(over list: List(a), with fun: fn(a) -> List(b)) -> List(b) { + flatten(map(list, fun)) +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from left to right. +/// +/// `fold([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 1), 2), 3)`. +/// +/// This function runs in linear time. +/// +pub fn fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [first, ..rest] -> fold(rest, fun(initial, first), fun) + } +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from right to left. +/// +/// `fold_right([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 3), 2), 1)`. +/// +/// This function runs in linear time. +/// +/// Unlike `fold` this function is not tail recursive. Where possible use +/// `fold` instead as it will use less memory. +/// +pub fn fold_right( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [first, ..rest] -> fun(fold_right(rest, initial, fun), first) + } +} + +/// Like `fold` but the folding function also receives the index of the current element. +/// +/// ## Examples +/// +/// ```gleam +/// assert ["a", "b", "c"] +/// |> index_fold("", fn(acc, item, index) { +/// acc <> int.to_string(index) <> ":" <> item <> " " +/// }) +/// == "0:a 1:b 2:c" +/// ``` +/// +/// ```gleam +/// assert [10, 20, 30] +/// |> index_fold(0, fn(acc, item, index) { acc + item * index }) +/// == 80 +/// ``` +/// +pub fn index_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a, Int) -> acc, +) -> acc { + index_fold_loop(list, initial, fun, 0) +} + +fn index_fold_loop( + over: List(a), + acc: acc, + with: fn(acc, a, Int) -> acc, + index: Int, +) -> acc { + case over { + [] -> acc + [first, ..rest] -> + index_fold_loop(rest, with(acc, first, index), with, index + 1) + } +} + +/// A variant of fold that might fail. +/// +/// The folding function should return `Result(accumulator, error)`. +/// If the returned value is `Ok(accumulator)` try_fold will try the next value in the list. +/// If the returned value is `Error(error)` try_fold will stop and return that error. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4] +/// |> try_fold(0, fn(acc, i) { +/// case i < 3 { +/// True -> Ok(acc + i) +/// False -> Error(Nil) +/// } +/// }) +/// == Error(Nil) +/// ``` +/// +pub fn try_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> Result(acc, e), +) -> Result(acc, e) { + case list { + [] -> Ok(initial) + [first, ..rest] -> + case fun(initial, first) { + Ok(result) -> try_fold(rest, result, fun) + Error(_) as error -> error + } + } +} + +pub type ContinueOrStop(a) { + Continue(a) + Stop(a) +} + +/// A variant of fold that allows to stop folding earlier. +/// +/// The folding function should return `ContinueOrStop(accumulator)`. +/// If the returned value is `Continue(accumulator)` fold_until will try the next value in the list. +/// If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4] +/// |> fold_until(0, fn(acc, i) { +/// case i < 3 { +/// True -> Continue(acc + i) +/// False -> Stop(acc) +/// } +/// }) +/// == 3 +/// ``` +/// +pub fn fold_until( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> ContinueOrStop(acc), +) -> acc { + case list { + [] -> initial + [first, ..rest] -> + case fun(initial, first) { + Continue(next_accumulator) -> fold_until(rest, next_accumulator, fun) + Stop(b) -> b + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `True`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// assert find([1, 2, 3], fn(x) { x > 2 }) == Ok(3) +/// ``` +/// +/// ```gleam +/// assert find([1, 2, 3], fn(x) { x > 4 }) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert find([], fn(_) { True }) == Error(Nil) +/// ``` +/// +pub fn find( + in list: List(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> + case is_desired(first) { + True -> Ok(first) + False -> find(in: rest, one_that: is_desired) + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `Ok(new_value)`, then returns the wrapped `new_value`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// assert find_map([[], [2], [3]], first) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert find_map([[], []], first) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert find_map([], first) == Error(Nil) +/// ``` +/// +pub fn find_map( + in list: List(a), + with fun: fn(a) -> Result(b, c), +) -> Result(b, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> + case fun(first) { + Ok(first) -> Ok(first) + Error(_) -> find_map(in: rest, with: fun) + } + } +} + +/// Returns `True` if the given function returns `True` for all the elements in +/// the given list. If the function returns `False` for any of the elements it +/// immediately returns `False` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert all([4, 5], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert !all([4, 3], fn(x) { x > 3 }) +/// ``` +/// +pub fn all(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> True + [first, ..rest] -> + case predicate(first) { + True -> all(rest, predicate) + False -> False + } + } +} + +/// Returns `True` if the given function returns `True` for any the elements in +/// the given list. If the function returns `True` for any of the elements it +/// immediately returns `True` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert !any([], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert any([4, 5], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert any([4, 3], fn(x) { x > 4 }) +/// ``` +/// +/// ```gleam +/// assert any([3, 4], fn(x) { x > 3 }) +/// ``` +/// +pub fn any(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> False + [first, ..rest] -> + case predicate(first) { + True -> True + False -> any(rest, predicate) + } + } +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, the remaining elements from +/// the longer list are not used. +/// +/// ## Examples +/// +/// ```gleam +/// assert zip([], []) == [] +/// ``` +/// +/// ```gleam +/// assert zip([1, 2], [3]) == [#(1, 3)] +/// ``` +/// +/// ```gleam +/// assert zip([1], [3, 4]) == [#(1, 3)] +/// ``` +/// +/// ```gleam +/// assert zip([1, 2], [3, 4]) == [#(1, 3), #(2, 4)] +/// ``` +/// +pub fn zip(list: List(a), with other: List(b)) -> List(#(a, b)) { + zip_loop(list, other, []) +} + +fn zip_loop(one: List(a), other: List(b), acc: List(#(a, b))) -> List(#(a, b)) { + case one, other { + [first_one, ..rest_one], [first_other, ..rest_other] -> + zip_loop(rest_one, rest_other, [#(first_one, first_other), ..acc]) + _, _ -> reverse(acc) + } +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, an `Error` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// assert strict_zip([], []) == Ok([]) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1, 2], [3]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1], [3, 4]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1, 2], [3, 4]) == Ok([#(1, 3), #(2, 4)]) +/// ``` +/// +pub fn strict_zip( + list: List(a), + with other: List(b), +) -> Result(List(#(a, b)), Nil) { + strict_zip_loop(list, other, []) +} + +fn strict_zip_loop( + one: List(a), + other: List(b), + acc: List(#(a, b)), +) -> Result(List(#(a, b)), Nil) { + case one, other { + [], [] -> Ok(reverse(acc)) + [], _ | _, [] -> Error(Nil) + [first_one, ..rest_one], [first_other, ..rest_other] -> + strict_zip_loop(rest_one, rest_other, [#(first_one, first_other), ..acc]) + } +} + +/// Takes a single list of 2-element tuples and returns two lists. +/// +/// ## Examples +/// +/// ```gleam +/// assert unzip([#(1, 2), #(3, 4)]) == #([1, 3], [2, 4]) +/// ``` +/// +/// ```gleam +/// assert unzip([]) == #([], []) +/// ``` +/// +pub fn unzip(input: List(#(a, b))) -> #(List(a), List(b)) { + unzip_loop(input, [], []) +} + +fn unzip_loop( + input: List(#(a, b)), + one: List(a), + other: List(b), +) -> #(List(a), List(b)) { + case input { + [] -> #(reverse(one), reverse(other)) + [#(first_one, first_other), ..rest] -> + unzip_loop(rest, [first_one, ..one], [first_other, ..other]) + } +} + +/// Inserts a given value between each existing element in a given list. +/// +/// This function runs in linear time and copies the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert intersperse([1, 1, 1], 2) == [1, 2, 1, 2, 1] +/// ``` +/// +/// ```gleam +/// assert intersperse([], 2) == [] +/// ``` +/// +pub fn intersperse(list: List(a), with elem: a) -> List(a) { + case list { + [] | [_] -> list + [first, ..rest] -> intersperse_loop(rest, elem, [first]) + } +} + +fn intersperse_loop(list: List(a), separator: a, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + intersperse_loop(rest, separator, [first, separator, ..acc]) + } +} + +/// Removes any duplicate elements from a given list. +/// +/// This function returns in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert unique([1, 1, 1, 4, 7, 3, 3, 4]) == [1, 4, 7, 3] +/// ``` +/// +pub fn unique(list: List(a)) -> List(a) { + unique_loop(list, dict.new(), []) +} + +fn unique_loop(list: List(a), seen: Dict(a, Nil), acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + case dict.has_key(seen, first) { + True -> unique_loop(rest, seen, acc) + False -> + unique_loop(rest, dict.insert(seen, first, Nil), [first, ..acc]) + } + } +} + +/// Sorts from smallest to largest based upon the ordering specified by a given +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) == [1, 2, 3, 4, 4, 5, 6] +/// ``` +/// +pub fn sort(list: List(a), by compare: fn(a, a) -> Order) -> List(a) { + // This is a natural, tail recursive, stable merge sort: + // - natural: it is very efficient if you call it on a list that is already + // (pre)sorted because it works on slices of the original list. + // - tail recursive: the stack won't grow linearly with the size of the list. + // - stable: if two items are considered to be equal then their original + // relative order is preserved. + case list { + // If the list has zero/one item then it's already sorted. + [] -> [] + [x] -> [x] + + // Otherwise the algorithm works as follow: we split the list in sequences + // of already sorted values as they appear in the list and then we merge + // those together two by two using `merge_all`. + [x, y, ..rest] -> { + // We need to compare the first two items to properly call `sequences` + // with the correct initial values. If the second item is <= than the + // first, then we know we'll start by growing a descending sequence + // (and an ascending one in the opposite case). + let direction = case compare(x, y) { + order.Lt | order.Eq -> Ascending + order.Gt -> Descending + } + + // `sequences` produces sequences in ascending order so we call the + // `merge_all` function saying it to expect all sequences to be sorted + // that way. + let sequences = sequences(rest, compare, [x], direction, y, []) + merge_all(sequences, Ascending, compare) + } + } +} + +type Sorting { + Ascending + Descending +} + +/// Given a list it returns slices of it that are locally sorted in ascending +/// order. +/// +/// Imagine you have this list: +/// +/// ``` +/// [1, 2, 3, 2, 1, 0] +/// ^^^^^^^ ^^^^^^^ This is a slice in descending order +/// | +/// | This is a slice that is sorted in ascending order +/// ``` +/// +/// So the produced result will contain these two slices, each one sorted in +/// ascending order: `[[1, 2, 3], [0, 1, 2]]`. +/// +/// - `growing` is an accumulator with the current slice being grown +/// - `direction` is the growing direction of the slice being grown, it could +/// either be ascending or strictly descending +/// - `prev` is the previous element that needs to be added to the growing slice +/// it is carried around to check whether we have to keep growing the current +/// slice or not +/// - `acc` is the accumulator containing the slices sorted in ascending order +/// +fn sequences( + list: List(a), + compare: fn(a, a) -> Order, + growing: List(a), + direction: Sorting, + prev: a, + acc: List(List(a)), +) -> List(List(a)) { + // First of all we must not forget to add the previous element to the + // currently growing slice. + let growing = [prev, ..growing] + + case list { + [] -> + case direction { + // Notice how we have to reverse the accumulator we're growing: since + // we always add items to the head, `growing` is built in the opposite + // sorting order of what it actually is in the original list. + Ascending -> [reverse(growing), ..acc] + Descending -> [growing, ..acc] + } + + [new, ..rest] -> + case compare(prev, new), direction { + // In case the new element respects the ordering of the growing + // sequence, then we just keep growing it. + // Notice how a growing sequence is weakly growing (that is it can have + // consecutive equal items) while a decreasing sequence is strictly + // decreasing (no consecutive equal items), this is needed to make the + // algorithm stable! + order.Gt, Descending | order.Lt, Ascending | order.Eq, Ascending -> + sequences(rest, compare, growing, direction, new, acc) + + // We were growing an ascending (descending) sequence and the new item + // is smaller (bigger) than the previous one, this means we have to stop + // growing this sequence and start with a new one whose first item will + // be the one we just found. + order.Gt, Ascending | order.Lt, Descending | order.Eq, Descending -> { + let acc = case direction { + Ascending -> [reverse(growing), ..acc] + Descending -> [growing, ..acc] + } + case rest { + // The list is over so we just create a sequence containing the last + // item we saw and add it to the accumulator before returning it. + [] -> [[new], ..acc] + + // If the list is not over we have a peek at the next item to decide + // in which direction is growing the new sequence and make the + // recursive call with the appropriate arguments. + [next, ..rest] -> { + let direction = case compare(new, next) { + order.Lt | order.Eq -> Ascending + order.Gt -> Descending + } + sequences(rest, compare, [new], direction, next, acc) + } + } + } + } + } +} + +/// Given some some sorted sequences (assumed to be sorted in `direction`) it +/// merges them all together until we're left with just a list sorted in +/// ascending order. +/// +fn merge_all( + sequences: List(List(a)), + direction: Sorting, + compare: fn(a, a) -> Order, +) -> List(a) { + case sequences, direction { + [], _ -> [] + + // If we have a single list in ascending order then we're done. + [sequence], Ascending -> sequence + + // If we have a single list in descending order, we reverse it to make sure + // it's in ascending order and we're done. + [sequence], Descending -> reverse(sequence) + + // Merging together sequences that are in ascending (descending) order + // reverses their order, so the recursive call will assume to be merging + // lists sorted in the opposite order! + _, Ascending -> { + let sequences = merge_ascending_pairs(sequences, compare, []) + merge_all(sequences, Descending, compare) + } + + _, Descending -> { + let sequences = merge_descending_pairs(sequences, compare, []) + merge_all(sequences, Ascending, compare) + } + } +} + +/// Given a list of ascending lists, it merges adjacent pairs into a single +/// descending list, halving their number. +/// It returns a list of the remaining descending lists. +/// +fn merge_ascending_pairs( + sequences: List(List(a)), + compare: fn(a, a) -> Order, + acc: List(List(a)), +) { + case sequences { + [] -> reverse(acc) + + // Beware, if we have just one item left we must reverse it: we take + // ascending lists as input and have to return descending ones. + // If we returned it like it is it would be sorted in ascending order. + [sequence] -> reverse([reverse(sequence), ..acc]) + + [ascending1, ascending2, ..rest] -> { + let descending = merge_ascendings(ascending1, ascending2, compare, []) + merge_ascending_pairs(rest, compare, [descending, ..acc]) + } + } +} + +/// This is the same as merge_ascending_pairs but flipped for descending lists. +/// +fn merge_descending_pairs( + sequences: List(List(a)), + compare: fn(a, a) -> Order, + acc: List(List(a)), +) { + case sequences { + [] -> reverse(acc) + + [sequence] -> reverse([reverse(sequence), ..acc]) + + [descending1, descending2, ..rest] -> { + let ascending = merge_descendings(descending1, descending2, compare, []) + merge_descending_pairs(rest, compare, [ascending, ..acc]) + } + } +} + +/// Merges two lists sorted in ascending order into a single list sorted in +/// descending order according to the given comparator function. +/// +/// This reversing of the sort order is not avoidable if we want to implement +/// merge as a tail recursive function. We could reverse the accumulator before +/// returning it but that would end up being less efficient; so the merging +/// algorithm has to play around this. +/// +fn merge_ascendings( + list1: List(a), + list2: List(a), + compare: fn(a, a) -> Order, + acc: List(a), +) -> List(a) { + case list1, list2 { + [], list | list, [] -> reverse_and_prepend(list, acc) + + [first1, ..rest1], [first2, ..rest2] -> + case compare(first1, first2) { + order.Lt -> merge_ascendings(rest1, list2, compare, [first1, ..acc]) + order.Gt | order.Eq -> + merge_ascendings(list1, rest2, compare, [first2, ..acc]) + } + } +} + +/// This is exactly the same as merge_ascendings but mirrored: it merges two +/// lists sorted in descending order into a single list sorted in ascending +/// order according to the given comparator function. +/// +/// This reversing of the sort order is not avoidable if we want to implement +/// merge as a tail recursive function. We could reverse the accumulator before +/// returning it but that would end up being less efficient; so the merging +/// algorithm has to play around this. +/// +fn merge_descendings( + list1: List(a), + list2: List(a), + compare: fn(a, a) -> Order, + acc: List(a), +) -> List(a) { + case list1, list2 { + [], list | list, [] -> reverse_and_prepend(list, acc) + [first1, ..rest1], [first2, ..rest2] -> + case compare(first1, first2) { + order.Lt -> merge_descendings(list1, rest2, compare, [first2, ..acc]) + order.Gt | order.Eq -> + merge_descendings(rest1, list2, compare, [first1, ..acc]) + } + } +} + +/// Builds a list of a given value a given number of times. +/// +/// ## Examples +/// +/// ```gleam +/// assert repeat("a", times: 0) == [] +/// ``` +/// +/// ```gleam +/// assert repeat("a", times: 5) == ["a", "a", "a", "a", "a"] +/// ``` +/// +pub fn repeat(item a: a, times times: Int) -> List(a) { + repeat_loop(a, times, []) +} + +fn repeat_loop(item: a, times: Int, acc: List(a)) -> List(a) { + case times <= 0 { + True -> acc + False -> repeat_loop(item, times - 1, [item, ..acc]) + } +} + +/// Splits a list in two before the given index. +/// +/// If the list is not long enough to have the given index the before list will +/// be the input list, and the after list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 0) == #([], [6, 7, 8, 9]) +/// ``` +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 2) == #([6, 7], [8, 9]) +/// ``` +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 4) == #([6, 7, 8, 9], []) +/// ``` +/// +pub fn split(list list: List(a), at index: Int) -> #(List(a), List(a)) { + split_loop(list, index, []) +} + +fn split_loop(list: List(a), n: Int, taken: List(a)) -> #(List(a), List(a)) { + case n <= 0 { + True -> #(reverse(taken), list) + False -> + case list { + [] -> #(reverse(taken), []) + [first, ..rest] -> split_loop(rest, n - 1, [first, ..taken]) + } + } +} + +/// Splits a list in two before the first element that a given function returns +/// `False` for. +/// +/// If the function returns `True` for all elements the first list will be the +/// input list, and the second list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 }) +/// == #([1, 2, 3], [4, 5]) +/// ``` +/// +/// ```gleam +/// assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 }) +/// == #([1, 2, 3, 4, 5], []) +/// ``` +/// +pub fn split_while( + list list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> #(List(a), List(a)) { + split_while_loop(list, predicate, []) +} + +fn split_while_loop( + list: List(a), + f: fn(a) -> Bool, + acc: List(a), +) -> #(List(a), List(a)) { + case list { + [] -> #(reverse(acc), []) + [first, ..rest] -> + case f(first) { + True -> split_while_loop(rest, f, [first, ..acc]) + False -> #(reverse(acc), list) + } + } +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element and returns the second element. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "a") == Ok(0) +/// ``` +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "b") == Ok(1) +/// ``` +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "c") == Error(Nil) +/// ``` +/// +pub fn key_find( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> Result(v, Nil) { + find_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, finds all tuples that have a given +/// key as the first element and returns the second element. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_filter([#("a", 0), #("b", 1), #("a", 2)], "a") == [0, 2] +/// ``` +/// +/// ```gleam +/// assert key_filter([#("a", 0), #("b", 1)], "c") == [] +/// ``` +/// +pub fn key_filter( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> List(v) { + filter_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element. This function will return the second element +/// of the found tuple and list with tuple removed. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "a") == Ok(#(0, [#("b", 1)])) +/// ``` +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "b") == Ok(#(1, [#("a", 0)])) +/// ``` +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "c") == Error(Nil) +/// ``` +/// +pub fn key_pop(list: List(#(k, v)), key: k) -> Result(#(v, List(#(k, v))), Nil) { + key_pop_loop(list, key, []) +} + +fn key_pop_loop( + list: List(#(k, v)), + key: k, + checked: List(#(k, v)), +) -> Result(#(v, List(#(k, v))), Nil) { + case list { + [] -> Error(Nil) + [#(k, v), ..rest] if k == key -> + Ok(#(v, reverse_and_prepend(checked, rest))) + [first, ..rest] -> key_pop_loop(rest, key, [first, ..checked]) + } +} + +/// Given a list of 2-element tuples, inserts a key and value into the list. +/// +/// If there was already a tuple with the key then it is replaced, otherwise it +/// is added to the end of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_set([#(5, 0), #(4, 1)], 4, 100) == [#(5, 0), #(4, 100)] +/// ``` +/// +/// ```gleam +/// assert key_set([#(5, 0), #(4, 1)], 1, 100) == [#(5, 0), #(4, 1), #(1, 100)] +/// ``` +/// +pub fn key_set(list: List(#(k, v)), key: k, value: v) -> List(#(k, v)) { + key_set_loop(list, key, value, []) +} + +fn key_set_loop( + list: List(#(k, v)), + key: k, + value: v, + inspected: List(#(k, v)), +) -> List(#(k, v)) { + case list { + [#(k, _), ..rest] if k == key -> + reverse_and_prepend(inspected, [#(k, value), ..rest]) + [first, ..rest] -> key_set_loop(rest, key, value, [first, ..inspected]) + [] -> reverse([#(key, value), ..inspected]) + } +} + +/// Calls a function for each element in a list, discarding the return value. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ```gleam +/// import gleam/io +/// +/// assert each(["1", "2", "3"], io.println) == Nil +/// // 1 +/// // 2 +/// // 3 +/// ``` +/// +pub fn each(list: List(a), f: fn(a) -> b) -> Nil { + case list { + [] -> Nil + [first, ..rest] -> { + f(first) + each(rest, f) + } + } +} + +/// Calls a `Result` returning function for each element in a list, discarding +/// the return value. If the function returns `Error` then the iteration is +/// stopped and the error is returned. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// try_each( +/// over: [1, 2, 3], +/// with: function_that_might_fail, +/// ) +/// == Ok(Nil) +/// ``` +/// +pub fn try_each( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(Nil, e) { + case list { + [] -> Ok(Nil) + [first, ..rest] -> + case fun(first) { + Ok(_) -> try_each(over: rest, with: fun) + Error(e) -> Error(e) + } + } +} + +/// Partitions a list into a tuple/pair of lists +/// by a given categorisation function. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert [1, 2, 3, 4, 5] |> partition(int.is_odd) == #([1, 3, 5], [2, 4]) +/// ``` +/// +pub fn partition( + list: List(a), + with categorise: fn(a) -> Bool, +) -> #(List(a), List(a)) { + partition_loop(list, categorise, [], []) +} + +fn partition_loop(list, categorise, trues, falses) { + case list { + [] -> #(reverse(trues), reverse(falses)) + [first, ..rest] -> + case categorise(first) { + True -> partition_loop(rest, categorise, [first, ..trues], falses) + False -> partition_loop(rest, categorise, trues, [first, ..falses]) + } + } +} + +/// Returns all the permutations of a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert permutations([1, 2]) == [[1, 2], [2, 1]] +/// ``` +/// +pub fn permutations(list: List(a)) -> List(List(a)) { + case list { + [] -> [[]] + l -> permutation_zip(l, [], []) + } +} + +fn permutation_zip( + list: List(a), + rest: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [] -> reverse(acc) + [head, ..tail] -> + permutation_prepend( + head, + permutations(reverse_and_prepend(rest, tail)), + tail, + [head, ..rest], + acc, + ) + } +} + +fn permutation_prepend( + el: a, + permutations: List(List(a)), + list_1: List(a), + list_2: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case permutations { + [] -> permutation_zip(list_1, list_2, acc) + [head, ..tail] -> + permutation_prepend(el, tail, list_1, list_2, [[el, ..head], ..acc]) + } +} + +/// Returns a list of sliding windows. +/// +/// ## Examples +/// +/// ```gleam +/// assert window([1,2,3,4,5], 3) == [[1, 2, 3], [2, 3, 4], [3, 4, 5]] +/// ``` +/// +/// ```gleam +/// assert window([1, 2], 4) == [] +/// ``` +/// +pub fn window(list: List(a), by n: Int) -> List(List(a)) { + case n <= 0 { + True -> [] + False -> window_loop([], list, n) + } +} + +fn window_loop(acc: List(List(a)), list: List(a), n: Int) -> List(List(a)) { + let window = take(list, n) + + case length(window) == n { + True -> window_loop([window, ..acc], drop(list, 1), n) + False -> reverse(acc) + } +} + +/// Returns a list of tuples containing two contiguous elements. +/// +/// ## Examples +/// +/// ```gleam +/// assert window_by_2([1,2,3,4]) == [#(1, 2), #(2, 3), #(3, 4)] +/// ``` +/// +/// ```gleam +/// assert window_by_2([1]) == [] +/// ``` +/// +pub fn window_by_2(list: List(a)) -> List(#(a, a)) { + zip(list, drop(list, 1)) +} + +/// Drops the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_while([1, 2, 3, 4], fn (x) { x < 3 }) == [3, 4] +/// ``` +/// +pub fn drop_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + case list { + [] -> [] + [first, ..rest] -> + case predicate(first) { + True -> drop_while(rest, predicate) + False -> [first, ..rest] + } + } +} + +/// Takes the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert take_while([1, 2, 3, 2, 4], fn (x) { x < 3 }) == [1, 2] +/// ``` +/// +pub fn take_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + take_while_loop(list, predicate, []) +} + +fn take_while_loop( + list: List(a), + predicate: fn(a) -> Bool, + acc: List(a), +) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + case predicate(first) { + True -> take_while_loop(rest, predicate, [first, ..acc]) + False -> reverse(acc) + } + } +} + +/// Returns a list of chunks in which +/// the return value of calling `f` on each element is the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 }) +/// == [[1], [2, 2], [3], [4, 4, 6], [7, 7]] +/// ``` +/// +pub fn chunk(in list: List(a), by f: fn(a) -> k) -> List(List(a)) { + case list { + [] -> [] + [first, ..rest] -> chunk_loop(rest, f, f(first), [first], []) + } +} + +fn chunk_loop( + list: List(a), + f: fn(a) -> k, + previous_key: k, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [first, ..rest] -> { + let key = f(first) + case key == previous_key { + True -> chunk_loop(rest, f, key, [first, ..current_chunk], acc) + False -> { + let new_acc = [reverse(current_chunk), ..acc] + chunk_loop(rest, f, key, [first], new_acc) + } + } + } + [] -> reverse([reverse(current_chunk), ..acc]) + } +} + +/// Returns a list of chunks containing `count` elements each. +/// +/// If the last chunk does not have `count` elements, it is instead +/// a partial chunk, with less than `count` elements. +/// +/// For any `count` less than 1 this function behaves as if it was set to 1. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5, 6] |> sized_chunk(into: 2) +/// == [[1, 2], [3, 4], [5, 6]] +/// ``` +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5, 6, 7, 8] |> sized_chunk(into: 3) +/// == [[1, 2, 3], [4, 5, 6], [7, 8]] +/// ``` +/// +pub fn sized_chunk(in list: List(a), into count: Int) -> List(List(a)) { + sized_chunk_loop(list, count, count, [], []) +} + +fn sized_chunk_loop( + list: List(a), + count: Int, + left: Int, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [] -> + case current_chunk { + [] -> reverse(acc) + remaining -> reverse([reverse(remaining), ..acc]) + } + [first, ..rest] -> { + let chunk = [first, ..current_chunk] + case left > 1 { + True -> sized_chunk_loop(rest, count, left - 1, chunk, acc) + False -> + sized_chunk_loop(rest, count, count, [], [reverse(chunk), ..acc]) + } + } + } +} + +/// This function acts similar to fold, but does not take an initial state. +/// Instead, it starts from the first element in the list +/// and combines it with each subsequent element in turn using the given +/// function. The function is called as `fun(accumulator, current_element)`. +/// +/// Returns `Ok` to indicate a successful run, and `Error` if called on an +/// empty list. +/// +/// ## Examples +/// +/// ```gleam +/// assert [] |> reduce(fn(acc, x) { acc + x }) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5] |> reduce(fn(acc, x) { acc + x }) == Ok(15) +/// ``` +/// +pub fn reduce(over list: List(a), with fun: fn(a, a) -> a) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> Ok(fold(rest, first, fun)) + } +} + +/// Similar to `fold`, but yields the state of the accumulator at each stage. +/// +/// ## Examples +/// +/// ```gleam +/// assert scan(over: [1, 2, 3], from: 100, with: fn(acc, i) { acc + i }) +/// == [101, 103, 106] +/// ``` +/// +pub fn scan( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> List(acc) { + scan_loop(list, initial, [], fun) +} + +fn scan_loop( + list: List(a), + accumulator: acc, + accumulated: List(acc), + fun: fn(acc, a) -> acc, +) -> List(acc) { + case list { + [] -> reverse(accumulated) + [first, ..rest] -> { + let next = fun(accumulator, first) + scan_loop(rest, next, [next, ..accumulated], fun) + } + } +} + +/// Returns the last element in the given list. +/// +/// Returns `Error(Nil)` if the list is empty. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert last([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert last([1, 2, 3, 4, 5]) == Ok(5) +/// ``` +/// +pub fn last(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [last] -> Ok(last) + [_, ..rest] -> last(rest) + } +} + +/// Return unique combinations of elements in the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert combinations([1, 2, 3], 2) == [[1, 2], [1, 3], [2, 3]] +/// ``` +/// +/// ```gleam +/// assert combinations([1, 2, 3, 4], 3) +/// == [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] +/// ``` +/// +pub fn combinations(items: List(a), by n: Int) -> List(List(a)) { + case n, items { + 0, _ -> [[]] + _, [] -> [] + _, [first, ..rest] -> + rest + |> combinations(n - 1) + |> map(fn(combination) { [first, ..combination] }) + |> reverse + |> fold(combinations(rest, n), fn(acc, c) { [c, ..acc] }) + } +} + +/// Return unique pair combinations of elements in the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert combination_pairs([1, 2, 3]) == [#(1, 2), #(1, 3), #(2, 3)] +/// ``` +/// +pub fn combination_pairs(items: List(a)) -> List(#(a, a)) { + combination_pairs_loop(items, []) +} + +fn combination_pairs_loop(items: List(a), acc: List(#(a, a))) -> List(#(a, a)) { + case items { + [] -> reverse(acc) + [first, ..rest] -> { + let first_combinations = map(rest, with: fn(other) { #(first, other) }) + let acc = reverse_and_prepend(first_combinations, acc) + combination_pairs_loop(rest, acc) + } + } +} + +/// Make a list alternating the elements from the given lists +/// +/// ## Examples +/// +/// ```gleam +/// assert interleave([[1, 2], [101, 102], [201, 202]]) +/// == [1, 101, 201, 2, 102, 202] +/// ``` +/// +pub fn interleave(list: List(List(a))) -> List(a) { + list + |> transpose + |> flatten +} + +/// Transpose rows and columns of the list of lists. +/// +/// Notice: This function is not tail recursive, +/// and thus may exceed stack size if called, +/// with large lists (on the JavaScript target). +/// +/// ## Examples +/// +/// ```gleam +/// assert transpose([[1, 2, 3], [101, 102, 103]]) +/// == [[1, 101], [2, 102], [3, 103]] +/// ``` +/// +pub fn transpose(list_of_lists: List(List(a))) -> List(List(a)) { + transpose_loop(list_of_lists, []) +} + +fn transpose_loop(rows: List(List(a)), columns: List(List(a))) -> List(List(a)) { + case rows { + [] -> reverse(columns) + _ -> { + let #(column, rest) = take_firsts(rows, [], []) + case column { + [_, ..] -> transpose_loop(rest, [column, ..columns]) + [] -> transpose_loop(rest, columns) + } + } + } +} + +fn take_firsts( + rows: List(List(a)), + column: List(a), + remaining_rows: List(List(a)), +) -> #(List(a), List(List(a))) { + case rows { + [] -> #(reverse(column), reverse(remaining_rows)) + [[], ..rest] -> take_firsts(rest, column, remaining_rows) + [[first, ..remaining_row], ..rest_rows] -> { + let remaining_rows = [remaining_row, ..remaining_rows] + take_firsts(rest_rows, [first, ..column], remaining_rows) + } + } +} + +/// Takes a list, randomly sorts all items and returns the shuffled list. +/// +/// This function uses `float.random` to decide the order of the elements. +/// +/// ## Example +/// +/// ```gleam +/// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> shuffle +/// // -> [1, 6, 9, 10, 3, 8, 4, 2, 7, 5] +/// ``` +/// +pub fn shuffle(list: List(a)) -> List(a) { + list + |> fold(from: [], with: fn(acc, a) { [#(float.random(), a), ..acc] }) + |> do_shuffle_by_pair_indexes() + |> shuffle_pair_unwrap_loop([]) +} + +fn shuffle_pair_unwrap_loop(list: List(#(Float, a)), acc: List(a)) -> List(a) { + case list { + [] -> acc + [elem_pair, ..enumerable] -> + shuffle_pair_unwrap_loop(enumerable, [elem_pair.1, ..acc]) + } +} + +fn do_shuffle_by_pair_indexes( + list_of_pairs: List(#(Float, a)), +) -> List(#(Float, a)) { + sort(list_of_pairs, fn(a_pair: #(Float, a), b_pair: #(Float, a)) -> Order { + float.compare(a_pair.0, b_pair.0) + }) +} + +/// Takes a list and a comparator, and returns the maximum element in the list +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5] |> list.max(int.compare) == Ok(5) +/// ``` +/// +/// ```gleam +/// assert ["a", "c", "b"] |> list.max(string.compare) == Ok("c") +/// ``` +/// +pub fn max( + over list: List(a), + with compare: fn(a, a) -> Order, +) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> Ok(max_loop(rest, compare, first)) + } +} + +fn max_loop(list, compare, max) { + case list { + [] -> max + [first, ..rest] -> + case compare(first, max) { + order.Gt -> max_loop(rest, compare, first) + order.Lt | order.Eq -> max_loop(rest, compare, max) + } + } +} + +/// Returns a random sample of up to n elements from a list using reservoir +/// sampling via [Algorithm L](https://en.wikipedia.org/wiki/Reservoir_sampling#Optimal:_Algorithm_L). +/// Returns an empty list if the sample size is less than or equal to 0. +/// +/// Order is not random, only selection is. +/// +/// ## Examples +/// +/// ```gleam +/// sample([1, 2, 3, 4, 5], 3) +/// // -> [2, 4, 5] // A random sample of 3 items +/// ``` +/// +pub fn sample(from list: List(a), up_to n: Int) -> List(a) { + let #(reservoir, rest) = build_reservoir(from: list, sized: n) + + case dict.is_empty(reservoir) { + // If the reservoire is empty that means we were asking to sample 0 or + // less items. That doesn't make much sense, so we just return an empty + // list. + True -> [] + + // Otherwise we keep looping over the remaining part of the list replacing + // random elements in the reservoir. + False -> { + let w = float.exponential(log_random() /. int.to_float(n)) + dict.values(sample_loop(rest, reservoir, n, w)) + } + } +} + +fn sample_loop( + list: List(a), + reservoir: Dict(Int, a), + n: Int, + w: Float, +) -> Dict(Int, a) { + let skip = { + let assert Ok(log) = float.logarithm(1.0 -. w) + float.round(float.floor(log_random() /. log)) + } + + case drop(list, skip) { + [] -> reservoir + [first, ..rest] -> { + let reservoir = dict.insert(reservoir, int.random(n), first) + let w = w *. float.exponential(log_random() /. int.to_float(n)) + sample_loop(rest, reservoir, n, w) + } + } +} + +const min_positive = 2.2250738585072014e-308 + +fn log_random() -> Float { + let assert Ok(random) = float.logarithm(float.random() +. min_positive) + random +} + +/// Builds the initial reservoir used by Algorithm L. +/// This is a dictionary with keys ranging from `0` up to `n - 1` where each +/// value is the corresponding element at that position in `list`. +/// +/// This also returns the remaining elements of `list` that didn't end up in +/// the reservoir. +/// +fn build_reservoir(from list: List(a), sized n: Int) -> #(Dict(Int, a), List(a)) { + build_reservoir_loop(list, n, dict.new()) +} + +fn build_reservoir_loop( + list: List(a), + size: Int, + reservoir: Dict(Int, a), +) -> #(Dict(Int, a), List(a)) { + let reservoir_size = dict.size(reservoir) + case reservoir_size >= size { + // The reservoir already has the size we wanted. + True -> #(reservoir, list) + + // Otherwise we add another element from the list to the reservoir + False -> + case list { + [] -> #(reservoir, []) + [first, ..rest] -> { + let reservoir = dict.insert(reservoir, reservoir_size, first) + build_reservoir_loop(rest, size, reservoir) + } + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/option.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/option.gleam new file mode 100644 index 0000000..7d3dfc7 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/option.gleam @@ -0,0 +1,326 @@ +/// `Option` represents a value that may be present or not. `Some` means the value is +/// present, `None` means the value is not. +/// +/// This is Gleam's alternative to having a value that could be Null, as is +/// possible in some other languages. +/// +/// ## `Option` and `Result` +/// +/// In other languages fallible functions may return either `Result` or +/// `Option` depending on whether there is more information to be given about the +/// failure. In Gleam all fallible functions return `Result`, and `Nil` is used +/// as the error if there is no extra detail to give. This consistency removes +/// the boilerplate that would otherwise be needed to convert between `Option` +/// and `Result` types, and makes APIs more predictable. +/// +/// The `Option` type should only be used for taking optional values as +/// function arguments, or for storing them in other data structures. +/// +pub type Option(a) { + Some(a) + None +} + +/// Combines a list of `Option`s into a single `Option`. +/// If all elements in the list are `Some` then returns a `Some` holding the list of values. +/// If any element is `None` then returns `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([Some(1), Some(2)]) == Some([1, 2]) +/// ``` +/// +/// ```gleam +/// assert all([Some(1), None]) == None +/// ``` +/// +pub fn all(list: List(Option(a))) -> Option(List(a)) { + all_loop(list, []) +} + +fn all_loop(list: List(Option(a)), acc: List(a)) -> Option(List(a)) { + case list { + [] -> Some(reverse(acc)) + [None, ..] -> None + [Some(first), ..rest] -> all_loop(rest, [first, ..acc]) + } +} + +// This is copied from the list module and not imported as importing it would +// result in a circular dependency! +@external(erlang, "lists", "reverse") +fn reverse(list: List(a)) -> List(a) { + reverse_and_prepend(list, []) +} + +fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { + case prefix { + [] -> suffix + [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) + } +} + +/// Checks whether the `Option` is a `Some` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_some(Some(1)) +/// ``` +/// +/// ```gleam +/// assert !is_some(None) +/// ``` +/// +pub fn is_some(option: Option(a)) -> Bool { + option != None +} + +/// Checks whether the `Option` is a `None` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert !is_none(Some(1)) +/// ``` +/// +/// ```gleam +/// assert is_none(None) +/// ``` +/// +pub fn is_none(option: Option(a)) -> Bool { + option == None +} + +/// Converts an `Option` type to a `Result` type. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_result(Some(1), "some_error") == Ok(1) +/// ``` +/// +/// ```gleam +/// assert to_result(None, "some_error") == Error("some_error") +/// ``` +/// +pub fn to_result(option: Option(a), e) -> Result(a, e) { + case option { + Some(a) -> Ok(a) + None -> Error(e) + } +} + +/// Converts a `Result` type to an `Option` type. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_result(Ok(1)) == Some(1) +/// ``` +/// +/// ```gleam +/// assert from_result(Error("some_error")) == None +/// ``` +/// +pub fn from_result(result: Result(a, e)) -> Option(a) { + case result { + Ok(a) -> Some(a) + Error(_) -> None + } +} + +/// Extracts the value from an `Option`, returning a default value if there is none. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap(Some(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap(None, 0) == 0 +/// ``` +/// +pub fn unwrap(option: Option(a), or default: a) -> a { + case option { + Some(x) -> x + None -> default + } +} + +/// Extracts the value from an `Option`, evaluating the default function if the option is `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_unwrap(Some(1), fn() { 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert lazy_unwrap(None, fn() { 0 }) == 0 +/// ``` +/// +pub fn lazy_unwrap(option: Option(a), or default: fn() -> a) -> a { + case option { + Some(x) -> x + None -> default() + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it. +/// +/// If the `Option` is a `None` rather than `Some`, the function is not called and the +/// `Option` stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map(over: Some(1), with: fn(x) { x + 1 }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert map(over: None, with: fn(x) { x + 1 }) == None +/// ``` +/// +pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { + case option { + Some(x) -> Some(fun(x)) + None -> None + } +} + +/// Merges a nested `Option` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten(Some(Some(1))) == Some(1) +/// ``` +/// +/// ```gleam +/// assert flatten(Some(None)) == None +/// ``` +/// +/// ```gleam +/// assert flatten(None) == None +/// ``` +/// +pub fn flatten(option: Option(Option(a))) -> Option(a) { + case option { + Some(x) -> x + None -> None + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it, where the given function also returns an `Option`. The two options are +/// then merged together into one `Option`. +/// +/// If the `Option` is a `None` rather than `Some` the function is not called and the +/// option stays the same. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that return `Option`. +/// +/// ## Examples +/// +/// ```gleam +/// assert then(Some(1), fn(x) { Some(x + 1) }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert then(Some(1), fn(x) { Some(#("a", x)) }) == Some(#("a", 1)) +/// ``` +/// +/// ```gleam +/// assert then(Some(1), fn(_) { None }) == None +/// ``` +/// +/// ```gleam +/// assert then(None, fn(x) { Some(x + 1) }) == None +/// ``` +/// +pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { + case option { + Some(x) -> fun(x) + None -> None + } +} + +/// Returns the first value if it is `Some`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(Some(1), Some(2)) == Some(1) +/// ``` +/// +/// ```gleam +/// assert or(Some(1), None) == Some(1) +/// ``` +/// +/// ```gleam +/// assert or(None, Some(2)) == Some(2) +/// ``` +/// +/// ```gleam +/// assert or(None, None) == None +/// ``` +/// +pub fn or(first: Option(a), second: Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second + } +} + +/// Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_or(Some(1), fn() { Some(2) }) == Some(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Some(1), fn() { None }) == Some(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(None, fn() { Some(2) }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert lazy_or(None, fn() { None }) == None +/// ``` +/// +pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second() + } +} + +/// Given a list of `Option`s, +/// returns only the values inside `Some`. +/// +/// ## Examples +/// +/// ```gleam +/// assert values([Some(1), None, Some(3)]) == [1, 3] +/// ``` +/// +pub fn values(options: List(Option(a))) -> List(a) { + values_loop(options, []) +} + +fn values_loop(list: List(Option(a)), acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [None, ..rest] -> values_loop(rest, acc) + [Some(first), ..rest] -> values_loop(rest, [first, ..acc]) + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/order.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/order.gleam new file mode 100644 index 0000000..49f2d93 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/order.gleam @@ -0,0 +1,144 @@ +/// Represents the result of a single comparison to determine the precise +/// ordering of two values. +/// +pub type Order { + /// Less-than + Lt + + /// Equal + Eq + + /// Greater than + Gt +} + +/// Inverts an order, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(Lt) == Gt +/// ``` +/// +/// ```gleam +/// assert negate(Eq) == Eq +/// ``` +/// +/// ```gleam +/// assert negate(Gt) == Lt +/// ``` +/// +pub fn negate(order: Order) -> Order { + case order { + Lt -> Gt + Eq -> Eq + Gt -> Lt + } +} + +/// Produces a numeric representation of the order. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_int(Lt) == -1 +/// ``` +/// +/// ```gleam +/// assert to_int(Eq) == 0 +/// ``` +/// +/// ```gleam +/// assert to_int(Gt) == 1 +/// ``` +/// +pub fn to_int(order: Order) -> Int { + case order { + Lt -> -1 + Eq -> 0 + Gt -> 1 + } +} + +/// Compares two `Order` values to one another, producing a new `Order`. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(Eq, with: Lt) == Gt +/// ``` +/// +pub fn compare(a: Order, with b: Order) -> Order { + case a, b { + x, y if x == y -> Eq + Lt, _ | Eq, Gt -> Lt + _, _ -> Gt + } +} + +/// Inverts an ordering function, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// import gleam/list +/// +/// assert list.sort([1, 5, 4], by: reverse(int.compare)) == [5, 4, 1] +/// ``` +/// +pub fn reverse(orderer: fn(a, a) -> Order) -> fn(a, a) -> Order { + fn(a, b) { orderer(b, a) } +} + +/// Return a fallback `Order` in case the first argument is `Eq`. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert break_tie(in: int.compare(1, 1), with: Lt) == Lt +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// assert break_tie(in: int.compare(1, 0), with: Eq) == Gt +/// ``` +/// +pub fn break_tie(in order: Order, with other: Order) -> Order { + case order { + Lt | Gt -> order + Eq -> other + } +} + +/// Invokes a fallback function returning an `Order` in case the first argument +/// is `Eq`. +/// +/// This can be useful when the fallback comparison might be expensive and it +/// needs to be delayed until strictly necessary. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt }) == Lt +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// assert lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq }) == Gt +/// ``` +/// +pub fn lazy_break_tie(in order: Order, with comparison: fn() -> Order) -> Order { + case order { + Lt | Gt -> order + Eq -> comparison() + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/pair.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/pair.gleam new file mode 100644 index 0000000..8d818b5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/pair.gleam @@ -0,0 +1,79 @@ +/// Returns the first element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// assert first(#(1, 2)) == 1 +/// ``` +/// +pub fn first(pair: #(a, b)) -> a { + let #(a, _) = pair + a +} + +/// Returns the second element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// assert second(#(1, 2)) == 2 +/// ``` +/// +pub fn second(pair: #(a, b)) -> b { + let #(_, a) = pair + a +} + +/// Returns a new pair with the elements swapped. +/// +/// ## Examples +/// +/// ```gleam +/// assert swap(#(1, 2)) == #(2, 1) +/// ``` +/// +pub fn swap(pair: #(a, b)) -> #(b, a) { + let #(a, b) = pair + #(b, a) +} + +/// Returns a new pair with the first element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// assert #(1, 2) |> map_first(fn(n) { n * 2 }) == #(2, 2) +/// ``` +/// +pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { + let #(a, b) = pair + #(fun(a), b) +} + +/// Returns a new pair with the second element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// assert #(1, 2) |> map_second(fn(n) { n * 2 }) == #(1, 4) +/// ``` +/// +pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { + let #(a, b) = pair + #(a, fun(b)) +} + +/// Returns a new pair with the given elements. This can also be done using the dedicated +/// syntax instead: `new(1, 2) == #(1, 2)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert new(1, 2) == #(1, 2) +/// ``` +/// +pub fn new(first: a, second: b) -> #(a, b) { + #(first, second) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/result.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/result.gleam new file mode 100644 index 0000000..11a298f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/result.gleam @@ -0,0 +1,405 @@ +//// Result represents the result of something that may succeed or not. +//// `Ok` means it was successful, `Error` means it was not successful. + +import gleam/list + +/// Checks whether the result is an `Ok` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_ok(Ok(1)) +/// ``` +/// +/// ```gleam +/// assert !is_ok(Error(Nil)) +/// ``` +/// +pub fn is_ok(result: Result(a, e)) -> Bool { + case result { + Error(_) -> False + Ok(_) -> True + } +} + +/// Checks whether the result is an `Error` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert !is_error(Ok(1)) +/// ``` +/// +/// ```gleam +/// assert is_error(Error(Nil)) +/// ``` +/// +pub fn is_error(result: Result(a, e)) -> Bool { + case result { + Ok(_) -> False + Error(_) -> True + } +} + +/// Updates a value held within the `Ok` of a result by calling a given function +/// on it. +/// +/// If the result is an `Error` rather than `Ok` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map(over: Ok(1), with: fn(x) { x + 1 }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert map(over: Error(1), with: fn(x) { x + 1 }) == Error(1) +/// ``` +/// +pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { + case result { + Ok(x) -> Ok(fun(x)) + Error(e) -> Error(e) + } +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it. +/// +/// If the result is `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map_error(over: Error(1), with: fn(x) { x + 1 }) == Error(2) +/// ``` +/// +/// ```gleam +/// assert map_error(over: Ok(1), with: fn(x) { x + 1 }) == Ok(1) +/// ``` +/// +pub fn map_error( + over result: Result(a, e), + with fun: fn(e) -> f, +) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(error) -> Error(fun(error)) + } +} + +/// Merges a nested `Result` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten(Ok(Ok(1))) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert flatten(Ok(Error(""))) == Error("") +/// ``` +/// +/// ```gleam +/// assert flatten(Error(Nil)) == Error(Nil) +/// ``` +/// +pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { + case result { + Ok(x) -> x + Error(error) -> Error(error) + } +} + +/// "Updates" an `Ok` result by passing its value to a function that yields a result, +/// and returning the yielded result. (This may "replace" the `Ok` with an `Error`.) +/// +/// If the input is an `Error` rather than an `Ok`, the function is not called and +/// the original `Error` is returned. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that may fail. +/// +/// ## Examples +/// +/// ```gleam +/// assert try(Ok(1), fn(x) { Ok(x + 1) }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert try(Ok(1), fn(x) { Ok(#("a", x)) }) == Ok(#("a", 1)) +/// ``` +/// +/// ```gleam +/// assert try(Ok(1), fn(_) { Error("Oh no") }) == Error("Oh no") +/// ``` +/// +/// ```gleam +/// assert try(Error(Nil), fn(x) { Ok(x + 1) }) == Error(Nil) +/// ``` +/// +pub fn try( + result: Result(a, e), + apply fun: fn(a) -> Result(b, e), +) -> Result(b, e) { + case result { + Ok(x) -> fun(x) + Error(e) -> Error(e) + } +} + +/// Extracts the `Ok` value from a result, returning a default value if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap(Ok(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap(Error(""), 0) == 0 +/// ``` +/// +pub fn unwrap(result: Result(a, e), or default: a) -> a { + case result { + Ok(v) -> v + Error(_) -> default + } +} + +/// Extracts the `Ok` value from a result, evaluating the default function if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_unwrap(Ok(1), fn() { 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert lazy_unwrap(Error(""), fn() { 0 }) == 0 +/// ``` +/// +pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { + case result { + Ok(v) -> v + Error(_) -> default() + } +} + +/// Extracts the `Error` value from a result, returning a default value if the result +/// is an `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap_error(Error(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap_error(Ok(""), 0) == 0 +/// ``` +/// +pub fn unwrap_error(result: Result(a, e), or default: e) -> e { + case result { + Ok(_) -> default + Error(e) -> e + } +} + +/// Returns the first value if it is `Ok`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(Ok(1), Ok(2)) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert or(Ok(1), Error("Error 2")) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert or(Error("Error 1"), Ok(2)) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert or(Error("Error 1"), Error("Error 2")) == Error("Error 2") +/// ``` +/// +pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second + } +} + +/// Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value. +/// +/// If you need access to the initial error value, use `result.try_recover`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_or(Ok(1), fn() { Ok(2) }) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Ok(1), fn() { Error("Error 2") }) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Error("Error 1"), fn() { Ok(2) }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Error("Error 1"), fn() { Error("Error 2") }) +/// == Error("Error 2") +/// ``` +/// +pub fn lazy_or( + first: Result(a, e), + second: fn() -> Result(a, e), +) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second() + } +} + +/// Combines a list of results into a single result. +/// If all elements in the list are `Ok` then returns an `Ok` holding the list of values. +/// If any element is `Error` then returns the first error. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([Ok(1), Ok(2)]) == Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// assert all([Ok(1), Error("e")]) == Error("e") +/// ``` +/// +pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { + list.try_map(results, fn(result) { result }) +} + +/// Given a list of results, returns a pair where the first element is a list +/// of all the values inside `Ok` and the second element is a list with all the +/// values inside `Error`. The values in both lists appear in reverse order with +/// respect to their position in the original list of results. +/// +/// ## Examples +/// +/// ```gleam +/// assert partition([Ok(1), Error("a"), Error("b"), Ok(2)]) +/// == #([2, 1], ["b", "a"]) +/// ``` +/// +pub fn partition(results: List(Result(a, e))) -> #(List(a), List(e)) { + partition_loop(results, [], []) +} + +fn partition_loop(results: List(Result(a, e)), oks: List(a), errors: List(e)) { + case results { + [] -> #(oks, errors) + [Ok(a), ..rest] -> partition_loop(rest, [a, ..oks], errors) + [Error(e), ..rest] -> partition_loop(rest, oks, [e, ..errors]) + } +} + +/// Replace the value within a result +/// +/// ## Examples +/// +/// ```gleam +/// assert replace(Ok(1), Nil) == Ok(Nil) +/// ``` +/// +/// ```gleam +/// assert replace(Error(1), Nil) == Error(1) +/// ``` +/// +pub fn replace(result: Result(a, e), value: b) -> Result(b, e) { + case result { + Ok(_) -> Ok(value) + Error(error) -> Error(error) + } +} + +/// Replace the error within a result +/// +/// ## Examples +/// +/// ```gleam +/// assert replace_error(Error(1), Nil) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert replace_error(Ok(1), Nil) == Ok(1) +/// ``` +/// +pub fn replace_error(result: Result(a, e), error: f) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(_) -> Error(error) + } +} + +/// Given a list of results, returns only the values inside `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// assert values([Ok(1), Error("a"), Ok(3)]) == [1, 3] +/// ``` +/// +pub fn values(results: List(Result(a, e))) -> List(a) { + list.filter_map(results, fn(result) { result }) +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it, where the given function also returns a result. The two results are +/// then merged together into one result. +/// +/// If the result is an `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// This function is useful for chaining together computations that may fail +/// and trying to recover from possible errors. +/// +/// If you do not need access to the initial error value, use `result.lazy_or`. +/// +/// ## Examples +/// +/// ```gleam +/// assert Ok(1) +/// |> try_recover(with: fn(_) { Error("failed to recover") }) +/// == Ok(1) +/// ``` +/// +/// ```gleam +/// assert Error(1) +/// |> try_recover(with: fn(error) { Ok(error + 1) }) +/// == Ok(2) +/// ``` +/// +/// ```gleam +/// assert Error(1) +/// |> try_recover(with: fn(error) { Error("failed to recover") }) +/// == Error("failed to recover") +/// ``` +/// +pub fn try_recover( + result: Result(a, e), + with fun: fn(e) -> Result(a, f), +) -> Result(a, f) { + case result { + Ok(value) -> Ok(value) + Error(error) -> fun(error) + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/set.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/set.gleam new file mode 100644 index 0000000..c553933 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/set.gleam @@ -0,0 +1,410 @@ +import gleam/dict.{type Dict} +import gleam/list +import gleam/result + +// A list is used as the dict value as an empty list has the smallest +// representation in Erlang's binary format +@target(erlang) +type Token = + List(Nil) + +@target(erlang) +const token = [] + +@target(javascript) +type Token = + Nil + +@target(javascript) +const token = Nil + +/// A set is a collection of unique members of the same type. +/// +/// It is implemented using the `gleam/dict` module, so inserts and lookups have +/// logarithmic time complexity. +/// +pub opaque type Set(member) { + Set(dict: Dict(member, Token)) +} + +/// Creates a new empty set. +/// +pub fn new() -> Set(member) { + Set(dict.new()) +} + +/// Gets the number of members in a set. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(1) +/// |> insert(2) +/// |> size +/// == 2 +/// ``` +/// +pub fn size(set: Set(member)) -> Int { + dict.size(set.dict) +} + +/// Determines whether or not the set is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> is_empty +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert(1) |> is_empty } +/// ``` +/// +pub fn is_empty(set: Set(member)) -> Bool { + set == new() +} + +/// Inserts a member into the set. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(1) +/// |> insert(2) +/// |> size +/// == 2 +/// ``` +/// +pub fn insert(into set: Set(member), this member: member) -> Set(member) { + Set(dict: dict.insert(set.dict, member, token)) +} + +/// Checks whether a set contains a given member. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(2) +/// |> contains(2) +/// ``` +/// +/// ```gleam +/// assert !{ +/// new() +/// |> insert(2) +/// |> contains(1) +/// } +/// ``` +/// +pub fn contains(in set: Set(member), this member: member) -> Bool { + set.dict + |> dict.get(member) + |> result.is_ok +} + +/// Removes a member from a set. If the set does not contain the member then +/// the set is returned unchanged. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert !{ +/// new() +/// |> insert(2) +/// |> delete(2) +/// |> contains(2) +/// } +/// ``` +/// +pub fn delete(from set: Set(member), this member: member) -> Set(member) { + Set(dict: dict.delete(set.dict, member)) +} + +/// Converts the set into a list of the contained members. +/// +/// The list has no specific ordering, any unintentional ordering may change in +/// future versions of Gleam or Erlang. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert(2) |> to_list == [2] +/// ``` +/// +pub fn to_list(set: Set(member)) -> List(member) { + dict.keys(set.dict) +} + +/// Creates a new set of the members in a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// import gleam/list +/// +/// assert [1, 1, 2, 4, 3, 2] +/// |> from_list +/// |> to_list +/// |> list.sort(by: int.compare) +/// == [1, 2, 3, 4] +/// ``` +/// +pub fn from_list(members: List(member)) -> Set(member) { + let dict = + list.fold(over: members, from: dict.new(), with: fn(m, k) { + dict.insert(m, k, token) + }) + Set(dict) +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Sets are not ordered so the values are not returned in any specific order. +/// Do not write code that relies on the order entries are used by this +/// function as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 3, 9]) +/// |> fold(0, fn(accumulator, member) { accumulator + member }) +/// == 13 +/// ``` +/// +pub fn fold( + over set: Set(member), + from initial: acc, + with reducer: fn(acc, member) -> acc, +) -> acc { + dict.fold(over: set.dict, from: initial, with: fn(a, k, _) { reducer(a, k) }) +} + +/// Creates a new set from an existing set, minus any members that a given +/// function returns `False` for. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert from_list([1, 4, 6, 3, 675, 44, 67]) +/// |> filter(keeping: int.is_even) +/// |> to_list +/// == [4, 6, 44] +/// ``` +/// +pub fn filter( + in set: Set(member), + keeping predicate: fn(member) -> Bool, +) -> Set(member) { + Set(dict.filter(in: set.dict, keeping: fn(m, _) { predicate(m) })) +} + +/// Creates a new set from a given set with the result of applying the given +/// function to each member. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3, 4]) +/// |> map(with: fn(x) { x * 2 }) +/// |> to_list +/// == [2, 4, 6, 8] +/// ``` +/// +pub fn map(set: Set(member), with fun: fn(member) -> mapped) -> Set(mapped) { + fold(over: set, from: new(), with: fn(acc, member) { + insert(acc, fun(member)) + }) +} + +/// Creates a new set from a given set with all the same entries except any +/// entry found on the given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3, 4]) +/// |> drop([1, 3]) +/// |> to_list +/// == [2, 4] +/// ``` +/// +pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) { + list.fold(over: disallowed, from: set, with: delete) +} + +/// Creates a new set from a given set, only including any members which are in +/// a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3]) +/// |> take([1, 3, 5]) +/// |> to_list +/// == [1, 3] +/// ``` +/// +pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { + Set(dict.take(from: set.dict, keeping: desired)) +} + +/// Creates a new set that contains all members of both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert union(from_list([1, 2]), from_list([2, 3])) |> to_list +/// == [1, 2, 3] +/// ``` +/// +pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { + let #(larger, smaller) = order(first, second) + fold(over: smaller, from: larger, with: insert) +} + +fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { + case dict.size(first.dict) > dict.size(second.dict) { + True -> #(first, second) + False -> #(second, first) + } +} + +/// Creates a new set that contains members that are present in both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert intersection(from_list([1, 2]), from_list([2, 3])) |> to_list +/// == [2] +/// ``` +/// +pub fn intersection( + of first: Set(member), + and second: Set(member), +) -> Set(member) { + let #(larger, smaller) = order(first, second) + take(from: larger, keeping: to_list(smaller)) +} + +/// Creates a new set that contains members that are present in the first set +/// but not the second. +/// +/// ## Examples +/// +/// ```gleam +/// assert difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list +/// == [1] +/// ``` +/// +pub fn difference( + from first: Set(member), + minus second: Set(member), +) -> Set(member) { + drop(from: first, drop: to_list(second)) +} + +/// Determines if a set is fully contained by another. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_subset(from_list([1]), from_list([1, 2])) +/// ``` +/// +/// ```gleam +/// assert !is_subset(from_list([1, 2, 3]), from_list([3, 4, 5])) +/// ``` +/// +pub fn is_subset(first: Set(member), of second: Set(member)) -> Bool { + intersection(of: first, and: second) == first +} + +/// Determines if two sets contain no common members +/// +/// ## Examples +/// +/// ```gleam +/// assert is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6])) +/// ``` +/// +/// ```gleam +/// assert !is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5])) +/// ``` +/// +pub fn is_disjoint(first: Set(member), from second: Set(member)) -> Bool { + intersection(of: first, and: second) == new() +} + +/// Creates a new set that contains members that are present in either set, but +/// not both. +/// +/// ## Examples +/// +/// ```gleam +/// assert symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) +/// |> to_list +/// == [1, 2, 4] +/// ``` +/// +pub fn symmetric_difference( + of first: Set(member), + and second: Set(member), +) -> Set(member) { + difference( + from: union(of: first, and: second), + minus: intersection(of: first, and: second), + ) +} + +/// Calls a function for each member in a set, discarding the return +/// value. +/// +/// Useful for producing a side effect for every item of a set. +/// +/// The order of elements in the iteration is an implementation detail that +/// should not be relied upon. +/// +/// ## Examples +/// +/// ```gleam +/// let set = from_list(["apple", "banana", "cherry"]) +/// +/// assert each(set, io.println) == Nil +/// // apple +/// // banana +/// // cherry +/// ``` +/// +pub fn each(set: Set(member), fun: fn(member) -> a) -> Nil { + fold(set, Nil, fn(nil, member) { + fun(member) + nil + }) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string.gleam new file mode 100644 index 0000000..e543bb6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string.gleam @@ -0,0 +1,890 @@ +//// Strings in Gleam are UTF-8 binaries. They can be written in your code as +//// text surrounded by `"double quotes"`. + +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/order +import gleam/string_tree.{type StringTree} + +/// Determines if a `String` is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_empty("") +/// ``` +/// +/// ```gleam +/// assert !is_empty("the world") +/// ``` +/// +pub fn is_empty(str: String) -> Bool { + str == "" +} + +/// Gets the number of grapheme clusters in a given `String`. +/// +/// This function has to iterate across the whole string to count the number of +/// graphemes, so it runs in linear time. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert length("Gleam") == 5 +/// ``` +/// +/// ```gleam +/// assert length("ß↑e̊") == 3 +/// ``` +/// +/// ```gleam +/// assert length("") == 0 +/// ``` +/// +@external(erlang, "string", "length") +@external(javascript, "../gleam_stdlib.mjs", "string_length") +pub fn length(string: String) -> Int + +/// Reverses a `String`. +/// +/// This function has to iterate across the whole `String` so it runs in linear +/// time. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert reverse("stressed") == "desserts" +/// ``` +/// +pub fn reverse(string: String) -> String { + string + |> string_tree.from_string + |> string_tree.reverse + |> string_tree.to_string +} + +/// Creates a new `String` by replacing all occurrences of a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// assert replace("www.example.com", each: ".", with: "-") == "www-example-com" +/// ``` +/// +/// ```gleam +/// assert replace("a,b,c,d,e", each: ",", with: "/") == "a/b/c/d/e" +/// ``` +/// +pub fn replace( + in string: String, + each pattern: String, + with substitute: String, +) -> String { + string + |> string_tree.from_string + |> string_tree.replace(each: pattern, with: substitute) + |> string_tree.to_string +} + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// lowercase. +/// +/// Useful for case-insensitive comparisons. +/// +/// ## Examples +/// +/// ```gleam +/// assert lowercase("X-FILES") == "x-files" +/// ``` +/// +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +pub fn lowercase(string: String) -> String + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// uppercase. +/// +/// Useful for case-insensitive comparisons and VIRTUAL YELLING. +/// +/// ## Examples +/// +/// ```gleam +/// assert uppercase("skinner") == "SKINNER" +/// ``` +/// +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +pub fn uppercase(string: String) -> String + +/// Compares two `String`s to see which is "larger" by comparing their graphemes. +/// +/// This does not compare the size or length of the given `String`s. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/order +/// +/// assert compare("Anthony", "Anthony") == order.Eq +/// ``` +/// +/// ```gleam +/// import gleam/order +/// +/// assert compare("A", "B") == order.Lt +/// ``` +/// +pub fn compare(a: String, b: String) -> order.Order { + case a == b { + True -> order.Eq + _ -> + case less_than(a, b) { + True -> order.Lt + False -> order.Gt + } + } +} + +@external(erlang, "gleam_stdlib", "less_than") +@external(javascript, "../gleam_stdlib.mjs", "less_than") +fn less_than(a: String, b: String) -> Bool + +/// Takes a substring given a start grapheme index and a length. Negative indexes +/// are taken starting from the *end* of the string. +/// +/// This function runs in linear time with the size of the index and the +/// length. Negative indexes are linear with the size of the input string in +/// addition to the other costs. +/// +/// ## Examples +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 1, length: 2) == "le" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 1, length: 10) == "leam" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 10, length: 3) == "" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: -2, length: 2) == "am" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: -12, length: 2) == "" +/// ``` +/// +pub fn slice(from string: String, at_index idx: Int, length len: Int) -> String { + case len <= 0 { + True -> "" + False -> + case idx < 0 { + True -> { + let translated_idx = length(string) + idx + case translated_idx < 0 { + True -> "" + False -> grapheme_slice(string, translated_idx, len) + } + } + False -> grapheme_slice(string, idx, len) + } + } +} + +@external(erlang, "gleam_stdlib", "slice") +@external(javascript, "../gleam_stdlib.mjs", "string_grapheme_slice") +fn grapheme_slice(string: String, index: Int, length: Int) -> String + +@external(erlang, "binary", "part") +@external(javascript, "../gleam_stdlib.mjs", "string_byte_slice") +fn unsafe_byte_slice(string: String, index: Int, length: Int) -> String + +/// Drops contents of the first `String` that occur before the second `String`. +/// If the `from` string does not contain the `before` string, `from` is +/// returned unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert crop(from: "The Lone Gunmen", before: "Lone") == "Lone Gunmen" +/// ``` +/// +@external(erlang, "gleam_stdlib", "crop_string") +@external(javascript, "../gleam_stdlib.mjs", "crop_string") +pub fn crop(from string: String, before substring: String) -> String + +/// Drops *n* graphemes from the start of a `String`. +/// +/// This function runs in linear time with the number of graphemes to drop. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_start(from: "The Lone Gunmen", up_to: 2) == "e Lone Gunmen" +/// ``` +/// +pub fn drop_start(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes <= 0 { + True -> string + False -> { + let prefix = grapheme_slice(string, 0, num_graphemes) + let prefix_size = byte_size(prefix) + unsafe_byte_slice(string, prefix_size, byte_size(string) - prefix_size) + } + } +} + +/// Drops *n* graphemes from the end of a `String`. +/// +/// This function traverses the full string, so it runs in linear time with the +/// size of the string. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_end(from: "Cigarette Smoking Man", up_to: 2) +/// == "Cigarette Smoking M" +/// ``` +/// +pub fn drop_end(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes <= 0 { + True -> string + False -> slice(string, 0, length(string) - num_graphemes) + } +} + +/// Checks if the first `String` contains the second. +/// +/// ## Examples +/// +/// ```gleam +/// assert contains(does: "theory", contain: "ory") +/// ``` +/// +/// ```gleam +/// assert contains(does: "theory", contain: "the") +/// ``` +/// +/// ```gleam +/// assert !contains(does: "theory", contain: "THE") +/// ``` +/// +@external(erlang, "gleam_stdlib", "contains_string") +@external(javascript, "../gleam_stdlib.mjs", "contains_string") +pub fn contains(does haystack: String, contain needle: String) -> Bool + +/// Checks whether the first `String` starts with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert !starts_with("theory", "ory") +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_starts_with") +@external(javascript, "../gleam_stdlib.mjs", "starts_with") +pub fn starts_with(string: String, prefix: String) -> Bool + +/// Checks whether the first `String` ends with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert ends_with("theory", "ory") +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_ends_with") +@external(javascript, "../gleam_stdlib.mjs", "ends_with") +pub fn ends_with(string: String, suffix: String) -> Bool + +/// Creates a list of `String`s by splitting a given string on a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// assert split("home/gleam/desktop/", on: "/") +/// == ["home", "gleam", "desktop", ""] +/// ``` +/// +pub fn split(x: String, on substring: String) -> List(String) { + case substring { + "" -> to_graphemes(x) + _ -> + x + |> string_tree.from_string + |> string_tree.split(on: substring) + |> list.map(with: string_tree.to_string) + } +} + +/// Splits a `String` a single time on the given substring. +/// +/// Returns an `Error` if substring not present. +/// +/// ## Examples +/// +/// ```gleam +/// assert split_once("home/gleam/desktop/", on: "/") +/// == Ok(#("home", "gleam/desktop/")) +/// ``` +/// +/// ```gleam +/// assert split_once("home/gleam/desktop/", on: "?") == Error(Nil) +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "split_once") +pub fn split_once( + string: String, + on substring: String, +) -> Result(#(String, String), Nil) { + case erl_split(string, substring) { + [first, rest] -> Ok(#(first, rest)) + _ -> Error(Nil) + } +} + +@external(erlang, "string", "split") +fn erl_split(a: String, b: String) -> List(String) + +/// Creates a new `String` by joining two `String`s together. +/// +/// This function typically copies both `String`s and runs in linear time, but +/// the exact behaviour will depend on how the runtime you are using optimises +/// your code. Benchmark and profile your code if you need to understand its +/// performance better. +/// +/// If you are joining together large string and want to avoid copying any data +/// you may want to investigate using the [`string_tree`](../gleam/string_tree.html) +/// module. +/// +/// ## Examples +/// +/// ```gleam +/// assert append(to: "butter", suffix: "fly") == "butterfly" +/// ``` +/// +pub fn append(to first: String, suffix second: String) -> String { + first <> second +} + +/// Creates a new `String` by joining many `String`s together. +/// +/// This function copies all the `String`s and runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert concat(["never", "the", "less"]) == "nevertheless" +/// ``` +/// +@external(erlang, "erlang", "list_to_binary") +pub fn concat(strings: List(String)) -> String { + concat_loop(strings, "") +} + +fn concat_loop(strings: List(String), accumulator: String) -> String { + case strings { + [string, ..strings] -> concat_loop(strings, accumulator <> string) + [] -> accumulator + } +} + +/// Creates a new `String` by repeating a `String` a given number of times. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert repeat("ha", times: 3) == "hahaha" +/// ``` +/// +pub fn repeat(string: String, times times: Int) -> String { + case times <= 0 { + True -> "" + False -> repeat_loop(times, string, "") + } +} + +fn repeat_loop(times: Int, doubling_acc: String, acc: String) -> String { + let acc = case times % 2 { + 0 -> acc + _ -> acc <> doubling_acc + } + let times = times / 2 + case times <= 0 { + True -> acc + False -> repeat_loop(times, doubling_acc <> doubling_acc, acc) + } +} + +/// Joins many `String`s together with a given separator. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert join(["home","evan","Desktop"], with: "/") == "home/evan/Desktop" +/// ``` +/// +pub fn join(strings: List(String), with separator: String) -> String { + case strings { + [] -> "" + [first, ..rest] -> join_loop(rest, separator, first) + } +} + +fn join_loop( + strings: List(String), + separator: String, + accumulator: String, +) -> String { + case strings { + [] -> accumulator + [string, ..strings] -> + join_loop(strings, separator, accumulator <> separator <> string) + } +} + +/// Pads the start of a `String` until it has a given length. +/// +/// ## Examples +/// +/// ```gleam +/// assert pad_start("121", to: 5, with: ".") == "..121" +/// ``` +/// +/// ```gleam +/// assert pad_start("121", to: 3, with: ".") == "121" +/// ``` +/// +/// ```gleam +/// assert pad_start("121", to: 2, with: ".") == "121" +/// ``` +/// +pub fn pad_start( + string: String, + to desired_length: Int, + with pad_string: String, +) -> String { + let current_length = length(string) + let to_pad_length = desired_length - current_length + + case to_pad_length <= 0 { + True -> string + False -> padding(to_pad_length, pad_string) <> string + } +} + +/// Pads the end of a `String` until it has a given length. +/// +/// ## Examples +/// +/// ```gleam +/// assert pad_end("123", to: 5, with: ".") == "123.." +/// ``` +/// +/// ```gleam +/// assert pad_end("123", to: 3, with: ".") == "123" +/// ``` +/// +/// ```gleam +/// assert pad_end("123", to: 2, with: ".") == "123" +/// ``` +/// +pub fn pad_end( + string: String, + to desired_length: Int, + with pad_string: String, +) -> String { + let current_length = length(string) + let to_pad_length = desired_length - current_length + + case to_pad_length <= 0 { + True -> string + False -> string <> padding(to_pad_length, pad_string) + } +} + +fn padding(size: Int, pad_string: String) -> String { + let pad_string_length = length(pad_string) + let num_pads = size / pad_string_length + let extra = size % pad_string_length + + repeat(pad_string, num_pads) <> slice(pad_string, 0, extra) +} + +/// Removes whitespace on both sides of a `String`. +/// +/// Whitespace in this function is the set of nonbreakable whitespace +/// codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1]. +/// +/// [1]: https://unicode.org/reports/tr31/ +/// +/// ## Examples +/// +/// ```gleam +/// assert trim(" hats \n") == "hats" +/// ``` +/// +pub fn trim(string: String) -> String { + string |> trim_start |> trim_end +} + +@external(erlang, "string", "trim") +fn erl_trim(a: String, b: Direction) -> String + +type Direction { + Leading + Trailing +} + +/// Removes whitespace at the start of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// assert trim_start(" hats \n") == "hats \n" +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "trim_start") +pub fn trim_start(string: String) -> String { + erl_trim(string, Leading) +} + +/// Removes whitespace at the end of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// assert trim_end(" hats \n") == " hats" +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "trim_end") +pub fn trim_end(string: String) -> String { + erl_trim(string, Trailing) +} + +/// Splits a non-empty `String` into its first element (head) and rest (tail). +/// This lets you pattern match on `String`s exactly as you would with lists. +/// +/// ## Performance +/// +/// There is a notable overhead to using this function, so you may not want to +/// use it in a tight loop. If you wish to efficiently parse a string you may +/// want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter). +/// +/// ## Examples +/// +/// ```gleam +/// assert pop_grapheme("gleam") == Ok(#("g", "leam")) +/// ``` +/// +/// ```gleam +/// assert pop_grapheme("") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_pop_grapheme") +@external(javascript, "../gleam_stdlib.mjs", "pop_grapheme") +pub fn pop_grapheme(string: String) -> Result(#(String, String), Nil) + +/// Converts a `String` to a list of +/// [graphemes](https://en.wikipedia.org/wiki/Grapheme). +/// +/// ```gleam +/// assert to_graphemes("abc") == ["a", "b", "c"] +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +pub fn to_graphemes(string: String) -> List(String) { + string + |> to_graphemes_loop([]) + |> list.reverse +} + +fn to_graphemes_loop(string: String, acc: List(String)) -> List(String) { + case pop_grapheme(string) { + Ok(#(grapheme, rest)) -> to_graphemes_loop(rest, [grapheme, ..acc]) + Error(_) -> acc + } +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "codepoint") +fn unsafe_int_to_utf_codepoint(a: Int) -> UtfCodepoint + +/// Converts a `String` to a `List` of `UtfCodepoint`. +/// +/// See and +/// for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// assert "a" |> to_utf_codepoints == [UtfCodepoint(97)] +/// ``` +/// +/// ```gleam +/// // Semantically the same as: +/// // ["🏳", "️", "‍", "🌈"] or: +/// // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow] +/// assert "🏳️‍🌈" |> to_utf_codepoints +/// == [ +/// UtfCodepoint(127987), +/// UtfCodepoint(65039), +/// UtfCodepoint(8205), +/// UtfCodepoint(127752), +/// ] +/// ``` +/// +pub fn to_utf_codepoints(string: String) -> List(UtfCodepoint) { + do_to_utf_codepoints(string) +} + +@target(erlang) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + to_utf_codepoints_loop(<>, []) +} + +@target(erlang) +fn to_utf_codepoints_loop( + bit_array: BitArray, + acc: List(UtfCodepoint), +) -> List(UtfCodepoint) { + case bit_array { + <> -> + to_utf_codepoints_loop(rest, [first, ..acc]) + _ -> list.reverse(acc) + } +} + +@target(javascript) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + string + |> string_to_codepoint_integer_list + |> list.map(unsafe_int_to_utf_codepoint) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "string_to_codepoint_integer_list") +fn string_to_codepoint_integer_list(string: String) -> List(Int) + +/// Converts a `List` of `UtfCodepoint`s to a `String`. +/// +/// See and +/// for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(a) = utf_codepoint(97) +/// let assert Ok(b) = utf_codepoint(98) +/// let assert Ok(c) = utf_codepoint(99) +/// assert from_utf_codepoints([a, b, c]) == "abc" +/// ``` +/// +@external(erlang, "gleam_stdlib", "utf_codepoint_list_to_string") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_list_to_string") +pub fn from_utf_codepoints(utf_codepoints: List(UtfCodepoint)) -> String + +/// Converts an integer to a `UtfCodepoint`. +/// +/// Returns an `Error` if the integer does not represent a valid UTF codepoint. +/// +pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { + case value { + i if i > 1_114_111 -> Error(Nil) + i if i >= 55_296 && i <= 57_343 -> Error(Nil) + i if i < 0 -> Error(Nil) + i -> Ok(unsafe_int_to_utf_codepoint(i)) + } +} + +/// Converts a `UtfCodepoint` to its ordinal code point value. +/// +/// ## Examples +/// +/// ```gleam +/// let assert [utf_codepoint, ..] = to_utf_codepoints("💜") +/// assert utf_codepoint_to_int(utf_codepoint) == 128156 +/// ``` +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_to_int") +pub fn utf_codepoint_to_int(cp: UtfCodepoint) -> Int + +/// Converts a `String` into `Option(String)` where an empty `String` becomes +/// `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_option("") == None +/// ``` +/// +/// ```gleam +/// assert to_option("hats") == Some("hats") +/// ``` +/// +pub fn to_option(string: String) -> Option(String) { + case string { + "" -> None + _ -> Some(string) + } +} + +/// Returns the first grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert first("") == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert first("icecream") == Ok("i") +/// ``` +/// +pub fn first(string: String) -> Result(String, Nil) { + case pop_grapheme(string) { + Ok(#(first, _)) -> Ok(first) + Error(e) -> Error(e) + } +} + +/// Returns the last grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// This function traverses the full string, so it runs in linear time with the +/// length of the string. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert last("") == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert last("icecream") == Ok("m") +/// ``` +/// +pub fn last(string: String) -> Result(String, Nil) { + case pop_grapheme(string) { + Ok(#(first, "")) -> Ok(first) + Ok(#(_, rest)) -> Ok(slice(rest, -1, 1)) + Error(e) -> Error(e) + } +} + +/// Creates a new `String` with the first grapheme in the input `String` +/// converted to uppercase and the remaining graphemes to lowercase. +/// +/// ## Examples +/// +/// ```gleam +/// assert capitalise("mamouna") == "Mamouna" +/// ``` +/// +pub fn capitalise(string: String) -> String { + case pop_grapheme(string) { + Ok(#(first, rest)) -> append(to: uppercase(first), suffix: lowercase(rest)) + Error(_) -> "" + } +} + +/// Returns a `String` representation of a term in Gleam syntax. +/// +/// This may be occasionally useful for quick-and-dirty printing of values in +/// scripts. For error reporting and other uses prefer constructing strings by +/// pattern matching on the values. +/// +/// ## Limitations +/// +/// The output format of this function is not stable and could change at any +/// time. The output is not suitable for parsing. +/// +/// This function works using runtime reflection, so the output may not be +/// perfectly accurate for data structures where the runtime structure doesn't +/// hold enough information to determine the original syntax. For example, +/// tuples with an Erlang atom in the first position will be mistaken for Gleam +/// records. +/// +/// ## Security and safety +/// +/// There is no limit to how large the strings that this function can produce. +/// Be careful not to call this function with large data structures or you +/// could use very large amounts of memory, potentially causing runtime +/// problems. +/// +pub fn inspect(term: anything) -> String { + term + |> do_inspect + |> string_tree.to_string +} + +@external(erlang, "gleam_stdlib", "inspect") +@external(javascript, "../gleam_stdlib.mjs", "inspect") +fn do_inspect(term: anything) -> StringTree + +/// Returns the number of bytes in a `String`. +/// +/// This function runs in constant time on Erlang and in linear time on +/// JavaScript. +/// +/// ## Examples +/// +/// ```gleam +/// assert byte_size("🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻") == 58 +/// ``` +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "byte_size") +pub fn byte_size(string: String) -> Int + +/// Removes the given prefix from the start of a `String`, if present. +/// +/// If the `String` does not start with the given prefix the string is returned +/// unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert remove_prefix("@lpil", "@") == "lpil" +/// ``` +/// +/// ```gleam +/// assert remove_prefix("hello!", "@") == "hello!" +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_remove_prefix") +@external(javascript, "../gleam_stdlib.mjs", "string_remove_prefix") +pub fn remove_prefix(from string: String, matching prefix: String) -> String + +/// Removes the given suffix from the end of a `String`, if present. +/// +/// If the `String` does not end with the given suffix the string is returned +/// unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert remove_suffix("Hello!", "!") == "Hello" +/// ``` +/// +/// ```gleam +/// assert remove_suffix("Hello!?", "!") == "Hello!?" +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_remove_suffix") +@external(javascript, "../gleam_stdlib.mjs", "string_remove_suffix") +pub fn remove_suffix(from string: String, matching suffix: String) -> String diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string_tree.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string_tree.gleam new file mode 100644 index 0000000..98eba25 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/string_tree.gleam @@ -0,0 +1,203 @@ +import gleam/list + +/// `StringTree` is a type used for efficiently building text content to be +/// written to a file or a socket. Internally it is represented as a tree so to +/// append or prepend to a string tree is a constant time operation that +/// allocates a new node in the tree without copying any of the content. When +/// writing to an output stream the tree is traversed and the content is sent +/// directly rather than copying it into a single buffer beforehand. +/// +/// On Erlang this type is compatible with Erlang's iodata. On JavaScript this +/// type is compatible with normal strings. +/// +/// The BEAM virtual machine has an optimisation for appending strings, where it +/// will mutate the string buffer when safe to do so, so if you are looking to +/// build a string through appending many small strings then you may get better +/// performance by not using a string tree. Always benchmark your performance +/// sensitive code. +/// +pub type StringTree + +/// Create an empty `StringTree`. Useful as the start of a pipe chaining many +/// trees together. +/// +pub fn new() -> StringTree { + from_strings([]) +} + +/// Prepends a `String` onto the start of some `StringTree`. +/// +/// Runs in constant time. +/// +pub fn prepend(to tree: StringTree, prefix prefix: String) -> StringTree { + append_tree(from_string(prefix), tree) +} + +/// Appends a `String` onto the end of some `StringTree`. +/// +/// Runs in constant time. +/// +pub fn append(to tree: StringTree, suffix second: String) -> StringTree { + append_tree(tree, from_string(second)) +} + +/// Prepends some `StringTree` onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_tree( + to tree: StringTree, + prefix prefix: StringTree, +) -> StringTree { + append_tree(prefix, tree) +} + +/// Appends some `StringTree` onto the end of another. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "iodata_append") +@external(javascript, "../gleam_stdlib.mjs", "add") +pub fn append_tree(to tree: StringTree, suffix suffix: StringTree) -> StringTree + +/// Converts a list of strings into a `StringTree`. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +pub fn from_strings(strings: List(String)) -> StringTree + +/// Joins a list of trees into a single tree. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +pub fn concat(trees: List(StringTree)) -> StringTree + +/// Converts a string into a `StringTree`. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn from_string(string: String) -> StringTree + +/// Turns a `StringTree` into a `String`. +/// +/// This function is implemented natively by the virtual machine and is highly +/// optimised. +/// +@external(erlang, "unicode", "characters_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn to_string(tree: StringTree) -> String + +/// Returns the size of the `StringTree` in bytes. +/// +@external(erlang, "erlang", "iolist_size") +@external(javascript, "../gleam_stdlib.mjs", "length") +pub fn byte_size(tree: StringTree) -> Int + +/// Joins the given trees into a new tree separated with the given string. +/// +pub fn join(trees: List(StringTree), with sep: String) -> StringTree { + trees + |> list.intersperse(from_string(sep)) + |> concat +} + +/// Converts a `StringTree` to a new one where the contents have been +/// lowercased. +/// +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +pub fn lowercase(tree: StringTree) -> StringTree + +/// Converts a `StringTree` to a new one where the contents have been +/// uppercased. +/// +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +pub fn uppercase(tree: StringTree) -> StringTree + +/// Converts a `StringTree` to a new one with the contents reversed. +/// +@external(erlang, "string", "reverse") +pub fn reverse(tree: StringTree) -> StringTree { + tree + |> to_string + |> do_to_graphemes + |> list.reverse + |> from_strings +} + +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +fn do_to_graphemes(string: String) -> List(String) + +type Direction { + All +} + +/// Splits a `StringTree` on a given pattern into a list of trees. +/// +@external(javascript, "../gleam_stdlib.mjs", "split") +pub fn split(tree: StringTree, on pattern: String) -> List(StringTree) { + erl_split(tree, pattern, All) +} + +@external(erlang, "string", "split") +fn erl_split(a: StringTree, b: String, c: Direction) -> List(StringTree) + +/// Replaces all instances of a pattern with a given string substitute. +/// +@external(erlang, "gleam_stdlib", "string_replace") +@external(javascript, "../gleam_stdlib.mjs", "string_replace") +pub fn replace( + in tree: StringTree, + each pattern: String, + with substitute: String, +) -> StringTree + +/// Compares two string trees to determine if they have the same textual +/// content. +/// +/// Comparing two string trees using the `==` operator may return `False` even +/// if they have the same content as they may have been build in different ways, +/// so using this function is often preferred. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_strings(["a", "b"]) != from_string("ab") +/// ``` +/// +/// ```gleam +/// assert is_equal(from_strings(["a", "b"]), from_string("ab")) +/// ``` +/// +@external(erlang, "string", "equal") +pub fn is_equal(a: StringTree, b: StringTree) -> Bool { + a == b +} + +/// Inspects a `StringTree` to determine if it is equivalent to an empty string. +/// +/// ## Examples +/// +/// ```gleam +/// assert !{ from_string("ok") |> is_empty } +/// ``` +/// +/// ```gleam +/// assert from_string("") |> is_empty +/// ``` +/// +/// ```gleam +/// assert from_strings([]) |> is_empty +/// ``` +/// +@external(erlang, "string", "is_empty") +pub fn is_empty(tree: StringTree) -> Bool { + from_string("") == tree +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/uri.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/uri.gleam new file mode 100644 index 0000000..cc0c3ac --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam/uri.gleam @@ -0,0 +1,769 @@ +//// Utilities for working with URIs +//// +//// This module provides functions for working with URIs (for example, parsing +//// URIs or encoding query strings). The functions in this module are implemented +//// according to [RFC 3986](https://tools.ietf.org/html/rfc3986). +//// +//// Query encoding (Form encoding) is defined in the +//// [W3C specification](https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data). + +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/string +import gleam/string_tree.{type StringTree} + +/// Type representing the parsed components of an URI. +/// All components of a URI are optional, except the path. +/// +pub type Uri { + Uri( + scheme: Option(String), + userinfo: Option(String), + host: Option(String), + port: Option(Int), + path: String, + query: Option(String), + fragment: Option(String), + ) +} + +/// Constant representing an empty URI, equivalent to "". +/// +/// ## Examples +/// +/// ```gleam +/// assert Uri(..empty, scheme: Some("https"), host: Some("example.com")) +/// == Uri( +/// scheme: Some("https"), +/// userinfo: None, +/// host: Some("example.com"), +/// port: None, +/// path: "", +/// query: None, +/// fragment: None, +/// ) +/// ``` +/// +pub const empty = Uri( + scheme: None, + userinfo: None, + host: None, + port: None, + path: "", + query: None, + fragment: None, +) + +/// Parses a compliant URI string into the `Uri` type. +/// If the string is not a valid URI string then an error is returned. +/// +/// The opposite operation is `uri.to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("https://example.com:1234/a/b?query=true#fragment") +/// == Ok( +/// Uri( +/// scheme: Some("https"), +/// userinfo: None, +/// host: Some("example.com"), +/// port: Some(1234), +/// path: "/a/b", +/// query: Some("query=true"), +/// fragment: Some("fragment") +/// ) +/// ) +/// ``` +/// +@external(erlang, "gleam_stdlib", "uri_parse") +pub fn parse(uri_string: String) -> Result(Uri, Nil) { + // This parses a uri_string following the regex defined in + // https://tools.ietf.org/html/rfc3986#appendix-B + // + // TODO: This is not perfect and will be more permissive than its Erlang + // counterpart, ideally we want to replicate Erlang's implementation on the js + // target as well. + parse_scheme_loop(uri_string, uri_string, empty, 0) +} + +fn parse_scheme_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `/` is not allowed to appear in a scheme so we know it's over and we can + // start parsing the authority with slashes. + "/" <> _ if size == 0 -> parse_authority_with_slashes(uri_string, pieces) + "/" <> _ -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_authority_with_slashes(uri_string, pieces) + } + + // `?` is not allowed to appear in a scheme, in an authority, or in a path; + // so if we see it we know it marks the beginning of the query part. + "?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces) + "?" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_query_with_question_mark(rest, pieces) + } + + // `#` is not allowed to appear in a scheme, in an authority, in a path or + // in a query; so if we see it we know it marks the beginning of the final + // fragment. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_fragment(rest, pieces) + } + + // A colon marks the end of a uri scheme, but if it is not preceded by any + // character then it's not a valid URI. + ":" <> _ if size == 0 -> Error(Nil) + ":" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_authority_with_slashes(rest, pieces) + } + + // If we could get to the end of the string and we've met no special + // chars whatsoever, that means the entire string is just a long path. + "" -> Ok(Uri(..pieces, path: original)) + + // In all other cases the first character is just a valid URI scheme + // character and we just keep munching characters until we reach the end of + // the uri scheme (or the end of the string and that would mean this is not + // a valid uri scheme since we found no `:`). + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_scheme_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_authority_with_slashes( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + case uri_string { + // To be a valid authority the string must start with a `//`, otherwise + // there's no authority and we just skip ahead to parsing the path. + "//" -> Ok(Uri(..pieces, host: Some(""))) + "//" <> rest -> parse_authority_pieces(rest, pieces) + _ -> parse_path(uri_string, pieces) + } +} + +fn parse_authority_pieces(string: String, pieces: Uri) -> Result(Uri, Nil) { + parse_userinfo_loop(string, string, pieces, 0) +} + +fn parse_userinfo_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `@` marks the end of the userinfo and the start of the host part in the + // authority string. + "@" <> rest if size == 0 -> parse_host(rest, pieces) + "@" <> rest -> { + let userinfo = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, userinfo: Some(userinfo)) + parse_host(rest, pieces) + } + + // If we reach the end of the authority string without finding an `@` + // special character, then we know that the authority doesn't actually + // contain the userinfo part. + // The entire string we just went through was a host! So we parse it as + // such. + "" | "/" <> _ | "?" <> _ | "#" <> _ -> parse_host(original, pieces) + + // In all other cases we just keep munching characters increasing the size + // of the userinfo bit. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_userinfo_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_host(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + // A host string can be in two formats: + // - \[[:.a-zA-Z0-9]*\] + // - [^:] + case uri_string { + // If we find an opening bracket we know it's the first format. + "[" <> _ -> parse_host_within_brackets(uri_string, pieces) + + // A `:` marks the beginning of the port part of the authority string. + ":" <> _ -> { + let pieces = Uri(..pieces, host: Some("")) + parse_port(uri_string, pieces) + } + + // If the string is empty then there's no need to keep going. The host is + // empty. + "" -> Ok(Uri(..pieces, host: Some(""))) + + // Otherwise it's the second format + _ -> parse_host_outside_of_brackets(uri_string, pieces) + } +} + +fn parse_host_within_brackets( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_host_within_brackets_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_host_within_brackets_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // If the string is over the entire string we were iterating through is the + // host part. + "" -> Ok(Uri(..pieces, host: Some(uri_string))) + + // A `]` marks the end of the host and the start of the port part. + "]" <> rest if size == 0 -> parse_port(rest, pieces) + "]" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size + 1) + let pieces = Uri(..pieces, host: Some(host)) + parse_port(rest, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ if size == 0 -> parse_path(uri_string, pieces) + "/" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_path(uri_string, pieces) + } + + // `?` marks the beginning of the query with question mark. + "?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces) + "?" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_fragment(rest, pieces) + } + + // In all other cases we just keep iterating. + _ -> { + let #(char, rest) = pop_codeunit(uri_string) + // Inside `[...]` there can only be some characters, if we find a special + // one then we know that we're actually parsing the other format for the + // host and we switch to that! + case is_valid_host_within_brackets_char(char) { + True -> + parse_host_within_brackets_loop(original, rest, pieces, size + 1) + + False -> + parse_host_outside_of_brackets_loop(original, original, pieces, 0) + } + } + } +} + +fn is_valid_host_within_brackets_char(char: Int) -> Bool { + // [0-9] + { 48 >= char && char <= 57 } + // [A-Z] + || { 65 >= char && char <= 90 } + // [a-z] + || { 97 >= char && char <= 122 } + // : + || char == 58 + // . + || char == 46 +} + +fn parse_host_outside_of_brackets( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_host_outside_of_brackets_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_host_outside_of_brackets_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + "" -> Ok(Uri(..pieces, host: Some(original))) + + // `:` marks the beginning of the port. + ":" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_port(uri_string, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_path(uri_string, pieces) + } + + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_fragment(rest, pieces) + } + + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_host_outside_of_brackets_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_port(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + case uri_string { + ":0" <> rest -> parse_port_loop(rest, pieces, 0) + ":1" <> rest -> parse_port_loop(rest, pieces, 1) + ":2" <> rest -> parse_port_loop(rest, pieces, 2) + ":3" <> rest -> parse_port_loop(rest, pieces, 3) + ":4" <> rest -> parse_port_loop(rest, pieces, 4) + ":5" <> rest -> parse_port_loop(rest, pieces, 5) + ":6" <> rest -> parse_port_loop(rest, pieces, 6) + ":7" <> rest -> parse_port_loop(rest, pieces, 7) + ":8" <> rest -> parse_port_loop(rest, pieces, 8) + ":9" <> rest -> parse_port_loop(rest, pieces, 9) + + // The port could be empty and be followed by any of the next delimiters. + // Like `:#`, `:?` or `:/` + ":" | "" -> Ok(pieces) + + // `?` marks the beginning of the query with question mark. + "?" <> rest | ":?" <> rest -> parse_query_with_question_mark(rest, pieces) + + // `#` marks the beginning of the fragment part. + "#" <> rest | ":#" <> rest -> parse_fragment(rest, pieces) + + // `/` marks the beginning of a path. + "/" <> _ -> parse_path(uri_string, pieces) + ":" <> rest -> + case rest { + "/" <> _ -> parse_path(rest, pieces) + _ -> Error(Nil) + } + + _ -> Error(Nil) + } +} + +fn parse_port_loop( + uri_string: String, + pieces: Uri, + port: Int, +) -> Result(Uri, Nil) { + case uri_string { + // As long as we find port numbers we keep accumulating those. + "0" <> rest -> parse_port_loop(rest, pieces, port * 10) + "1" <> rest -> parse_port_loop(rest, pieces, port * 10 + 1) + "2" <> rest -> parse_port_loop(rest, pieces, port * 10 + 2) + "3" <> rest -> parse_port_loop(rest, pieces, port * 10 + 3) + "4" <> rest -> parse_port_loop(rest, pieces, port * 10 + 4) + "5" <> rest -> parse_port_loop(rest, pieces, port * 10 + 5) + "6" <> rest -> parse_port_loop(rest, pieces, port * 10 + 6) + "7" <> rest -> parse_port_loop(rest, pieces, port * 10 + 7) + "8" <> rest -> parse_port_loop(rest, pieces, port * 10 + 8) + "9" <> rest -> parse_port_loop(rest, pieces, port * 10 + 9) + + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_fragment(rest, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_path(uri_string, pieces) + } + + // The string (and so the port) is over, we return what we parsed so far. + "" -> Ok(Uri(..pieces, port: Some(port))) + + // In all other cases we've ran into some invalid character inside the port + // so the uri is invalid! + _ -> Error(Nil) + } +} + +fn parse_path(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + parse_path_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_path_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let path = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, path: path) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let path = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, path: path) + parse_fragment(rest, pieces) + } + + // If the string is over that means the entirety of the string was the path + // and it has an empty query and fragment. + "" -> Ok(Uri(..pieces, path: original)) + + // In all other cases the character is allowed to be part of the path so we + // just keep munching until we reach to its end. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_path_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_query_with_question_mark( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_query_with_question_mark_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_query_with_question_mark_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `#` marks the beginning of the fragment part. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let query = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, query: Some(query)) + parse_fragment(rest, pieces) + } + + // If the string is over that means the entirety of the string was the query + // and it has an empty fragment. + "" -> Ok(Uri(..pieces, query: Some(original))) + + // In all other cases the character is allowed to be part of the query so we + // just keep munching until we reach to its end. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_query_with_question_mark_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_fragment(rest: String, pieces: Uri) -> Result(Uri, Nil) { + Ok(Uri(..pieces, fragment: Some(rest))) +} + +// WARN: this function returns invalid strings! +// We need to return a String anyways to have this as the representation on the +// JavaScript target. +// Alternatively, we could rewrite the entire code to use a single +// `fold_codeunits`-style loop and a state machine. +@external(erlang, "gleam_stdlib", "string_pop_codeunit") +@external(javascript, "../gleam_stdlib.mjs", "pop_codeunit") +fn pop_codeunit(str: String) -> #(Int, String) + +@external(erlang, "binary", "part") +@external(javascript, "../gleam_stdlib.mjs", "string_codeunit_slice") +fn codeunit_slice(str: String, at_index from: Int, length length: Int) -> String + +/// Parses an URL-encoded query string into a list of key value pairs. +/// Returns an error for invalid encoding. +/// +/// The opposite operation is `uri.query_to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse_query("a=1&b=2") == Ok([#("a", "1"), #("b", "2")]) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_query") +@external(javascript, "../gleam_stdlib.mjs", "parse_query") +pub fn parse_query(query: String) -> Result(List(#(String, String)), Nil) + +/// Encodes a list of key value pairs as a URI query string. +/// +/// The opposite operation is `uri.parse_query`. +/// +/// ## Examples +/// +/// ```gleam +/// assert query_to_string([#("a", "1"), #("b", "2")]) == "a=1&b=2" +/// ``` +/// +pub fn query_to_string(query: List(#(String, String))) -> String { + query + |> list.map(query_pair) + |> list.intersperse(string_tree.from_string("&")) + |> string_tree.concat + |> string_tree.to_string +} + +fn query_pair(pair: #(String, String)) -> StringTree { + [percent_encode_query(pair.0), "=", percent_encode_query(pair.1)] + |> string_tree.from_strings +} + +fn percent_encode_query(part: String) -> String { + percent_encode(part) + |> string.replace(each: "+", with: "%2B") +} + +/// Encodes a string into a percent encoded representation. +/// +/// ## Examples +/// +/// ```gleam +/// assert percent_encode("100% great") == "100%25%20great" +/// ``` +/// +@external(erlang, "gleam_stdlib", "percent_encode") +@external(javascript, "../gleam_stdlib.mjs", "percent_encode") +pub fn percent_encode(value: String) -> String + +/// Decodes a percent encoded string. +/// +/// ## Examples +/// +/// ```gleam +/// assert percent_decode("100%25%20great+fun") == Ok("100% great+fun") +/// ``` +/// +@external(erlang, "gleam_stdlib", "percent_decode") +@external(javascript, "../gleam_stdlib.mjs", "percent_decode") +pub fn percent_decode(value: String) -> Result(String, Nil) + +/// Splits the path section of a URI into its constituent segments. +/// +/// Removes empty segments and resolves dot-segments as specified in +/// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC. +/// +/// ## Examples +/// +/// ```gleam +/// assert path_segments("/users/1") == ["users" ,"1"] +/// ``` +/// +pub fn path_segments(path: String) -> List(String) { + remove_dot_segments(string.split(path, "/")) +} + +fn remove_dot_segments(input: List(String)) -> List(String) { + remove_dot_segments_loop(input, []) +} + +fn remove_dot_segments_loop( + input: List(String), + accumulator: List(String), +) -> List(String) { + case input { + [] -> list.reverse(accumulator) + [segment, ..rest] -> { + let accumulator = case segment, accumulator { + "", accumulator -> accumulator + ".", accumulator -> accumulator + "..", [] -> [] + "..", [_, ..accumulator] -> accumulator + segment, accumulator -> [segment, ..accumulator] + } + remove_dot_segments_loop(rest, accumulator) + } + } +} + +/// Encodes a `Uri` value as a URI string. +/// +/// The opposite operation is `uri.parse`. +/// +/// ## Examples +/// +/// ```gleam +/// let uri = Uri(..empty, scheme: Some("https"), host: Some("example.com")) +/// assert to_string(uri) == "https://example.com" +/// ``` +/// +pub fn to_string(uri: Uri) -> String { + let parts = case uri.fragment { + Some(fragment) -> ["#", fragment] + None -> [] + } + let parts = case uri.query { + Some(query) -> ["?", query, ..parts] + None -> parts + } + let parts = [uri.path, ..parts] + let parts = case uri.host, string.starts_with(uri.path, "/") { + Some(host), False if host != "" -> ["/", ..parts] + _, _ -> parts + } + let parts = case uri.host, uri.port { + Some(_), Some(port) -> [":", int.to_string(port), ..parts] + _, _ -> parts + } + let parts = case uri.scheme, uri.userinfo, uri.host { + Some(s), Some(u), Some(h) -> [s, "://", u, "@", h, ..parts] + Some(s), None, Some(h) -> [s, "://", h, ..parts] + Some(s), Some(_), None | Some(s), None, None -> [s, ":", ..parts] + None, None, Some(h) -> ["//", h, ..parts] + _, _, _ -> parts + } + string.concat(parts) +} + +/// Fetches the origin of a URI. +/// +/// Returns the origin of a uri as defined in +/// [RFC 6454](https://tools.ietf.org/html/rfc6454) +/// +/// The supported URI schemes are `http` and `https`. +/// URLs without a scheme will return `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(uri) = parse("https://example.com/path?foo#bar") +/// assert origin(uri) == Ok("https://example.com") +/// ``` +/// +pub fn origin(uri: Uri) -> Result(String, Nil) { + let Uri(scheme: scheme, host: host, port: port, ..) = uri + case host, scheme { + Some(h), Some("https") if port == Some(443) -> + Ok(string.concat(["https://", h])) + Some(h), Some("http") if port == Some(80) -> + Ok(string.concat(["http://", h])) + Some(h), Some(s) if s == "http" || s == "https" -> { + case port { + Some(p) -> Ok(string.concat([s, "://", h, ":", int.to_string(p)])) + None -> Ok(string.concat([s, "://", h])) + } + } + _, _ -> Error(Nil) + } +} + +/// Resolves a URI with respect to the given base URI. +/// +/// The base URI must be an absolute URI or this function will return an error. +/// The algorithm for merging URIs is described in +/// [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2). +/// +pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) { + case base { + Uri(scheme: Some(_), host: Some(_), ..) -> + case relative { + Uri(host: Some(_), ..) -> { + let path = + relative.path + |> string.split("/") + |> remove_dot_segments() + |> join_segments() + let resolved = + Uri( + option.or(relative.scheme, base.scheme), + None, + relative.host, + option.or(relative.port, base.port), + path, + relative.query, + relative.fragment, + ) + Ok(resolved) + } + _ -> { + let #(new_path, new_query) = case relative.path { + "" -> #(base.path, option.or(relative.query, base.query)) + _ -> { + let path_segments = case string.starts_with(relative.path, "/") { + True -> string.split(relative.path, "/") + False -> + base.path + |> string.split("/") + |> drop_last() + |> list.append(string.split(relative.path, "/")) + } + let path = + path_segments + |> remove_dot_segments() + |> join_segments() + #(path, relative.query) + } + } + let resolved = + Uri( + base.scheme, + None, + base.host, + base.port, + new_path, + new_query, + relative.fragment, + ) + Ok(resolved) + } + } + _ -> Error(Nil) + } +} + +fn drop_last(elements: List(a)) -> List(a) { + list.take(from: elements, up_to: list.length(elements) - 1) +} + +fn join_segments(segments: List(String)) -> String { + string.join(["", ..segments], "/") +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bit_array.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bit_array.erl new file mode 100644 index 0000000..0f4b57b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bit_array.erl @@ -0,0 +1,347 @@ +-module(gleam@bit_array). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bit_array.gleam"). +-export([from_string/1, bit_size/1, byte_size/1, pad_to_bytes/1, slice/3, is_utf8/1, to_string/1, concat/1, append/2, base64_encode/2, base64_decode/1, base64_url_encode/2, base64_url_decode/1, base16_encode/1, base16_decode/1, inspect/1, compare/2, starts_with/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(" BitArrays are a sequence of binary data of any length.\n"). + +-file("src/gleam/bit_array.gleam", 11). +?DOC(" Converts a UTF-8 `String` type into a `BitArray`.\n"). +-spec from_string(binary()) -> bitstring(). +from_string(X) -> + gleam_stdlib:identity(X). + +-file("src/gleam/bit_array.gleam", 17). +?DOC(" Returns an integer which is the number of bits in the bit array.\n"). +-spec bit_size(bitstring()) -> integer(). +bit_size(X) -> + erlang:bit_size(X). + +-file("src/gleam/bit_array.gleam", 23). +?DOC(" Returns an integer which is the number of bytes in the bit array.\n"). +-spec byte_size(bitstring()) -> integer(). +byte_size(X) -> + erlang:byte_size(X). + +-file("src/gleam/bit_array.gleam", 29). +?DOC(" Pads a bit array with zeros so that it is a whole number of bytes.\n"). +-spec pad_to_bytes(bitstring()) -> bitstring(). +pad_to_bytes(X) -> + gleam_stdlib:bit_array_pad_to_bytes(X). + +-file("src/gleam/bit_array.gleam", 54). +?DOC( + " Extracts a sub-section of a bit array.\n" + "\n" + " The slice will start at given position and continue up to specified\n" + " length.\n" + " A negative length can be used to extract bytes at the end of a bit array.\n" + "\n" + " This function runs in constant time.\n" +). +-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} | + {error, nil}. +slice(String, Position, Length) -> + gleam_stdlib:bit_array_slice(String, Position, Length). + +-file("src/gleam/bit_array.gleam", 67). +-spec is_utf8_loop(bitstring()) -> boolean(). +is_utf8_loop(Bits) -> + case Bits of + <<>> -> + true; + + <<_/utf8, Rest/binary>> -> + is_utf8_loop(Rest); + + _ -> + false + end. + +-file("src/gleam/bit_array.gleam", 62). +?DOC(" Tests to see whether a bit array is valid UTF-8.\n"). +-spec is_utf8(bitstring()) -> boolean(). +is_utf8(Bits) -> + is_utf8_loop(Bits). + +-file("src/gleam/bit_array.gleam", 88). +?DOC( + " Converts a bit array to a string.\n" + "\n" + " Returns an error if the bit array is invalid UTF-8 data.\n" +). +-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}. +to_string(Bits) -> + case is_utf8(Bits) of + true -> + {ok, gleam_stdlib:identity(Bits)}; + + false -> + {error, nil} + end. + +-file("src/gleam/bit_array.gleam", 109). +?DOC( + " Creates a new bit array by joining multiple binaries.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert concat([from_string(\"butter\"), from_string(\"fly\")])\n" + " == from_string(\"butterfly\")\n" + " ```\n" +). +-spec concat(list(bitstring())) -> bitstring(). +concat(Bit_arrays) -> + gleam_stdlib:bit_array_concat(Bit_arrays). + +-file("src/gleam/bit_array.gleam", 40). +?DOC( + " Creates a new bit array by joining two bit arrays.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append(to: from_string(\"butter\"), suffix: from_string(\"fly\"))\n" + " == from_string(\"butterfly\")\n" + " ```\n" +). +-spec append(bitstring(), bitstring()) -> bitstring(). +append(First, Second) -> + gleam_stdlib:bit_array_concat([First, Second]). + +-file("src/gleam/bit_array.gleam", 118). +?DOC( + " Encodes a BitArray into a base 64 encoded string.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base64_encode(bitstring(), boolean()) -> binary(). +base64_encode(Input, Padding) -> + gleam_stdlib:base64_encode(Input, Padding). + +-file("src/gleam/bit_array.gleam", 122). +?DOC(" Decodes a base 64 encoded string into a `BitArray`.\n"). +-spec base64_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_decode(Encoded) -> + Padded = case erlang:byte_size(gleam_stdlib:identity(Encoded)) rem 4 of + 0 -> + Encoded; + + N -> + gleam@string:append( + Encoded, + gleam@string:repeat(<<"="/utf8>>, 4 - N) + ) + end, + gleam_stdlib:base64_decode(Padded). + +-file("src/gleam/bit_array.gleam", 140). +?DOC( + " Encodes a `BitArray` into a base 64 encoded string with URL and filename\n" + " safe alphabet.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base64_url_encode(bitstring(), boolean()) -> binary(). +base64_url_encode(Input, Padding) -> + _pipe = Input, + _pipe@1 = gleam_stdlib:base64_encode(_pipe, Padding), + _pipe@2 = gleam@string:replace(_pipe@1, <<"+"/utf8>>, <<"-"/utf8>>), + gleam@string:replace(_pipe@2, <<"/"/utf8>>, <<"_"/utf8>>). + +-file("src/gleam/bit_array.gleam", 150). +?DOC( + " Decodes a base 64 encoded string with URL and filename safe alphabet into a\n" + " `BitArray`.\n" +). +-spec base64_url_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_url_decode(Encoded) -> + _pipe = Encoded, + _pipe@1 = gleam@string:replace(_pipe, <<"-"/utf8>>, <<"+"/utf8>>), + _pipe@2 = gleam@string:replace(_pipe@1, <<"_"/utf8>>, <<"/"/utf8>>), + base64_decode(_pipe@2). + +-file("src/gleam/bit_array.gleam", 164). +?DOC( + " Encodes a `BitArray` into a base 16 encoded string.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base16_encode(bitstring()) -> binary(). +base16_encode(Input) -> + gleam_stdlib:base16_encode(Input). + +-file("src/gleam/bit_array.gleam", 170). +?DOC(" Decodes a base 16 encoded string into a `BitArray`.\n"). +-spec base16_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base16_decode(Input) -> + gleam_stdlib:base16_decode(Input). + +-file("src/gleam/bit_array.gleam", 191). +-spec inspect_loop(bitstring(), binary()) -> binary(). +inspect_loop(Input, Accumulator) -> + case Input of + <<>> -> + Accumulator; + + <> -> + <<<>/binary, + ":size(1)"/utf8>>; + + <> -> + <<<>/binary, + ":size(2)"/utf8>>; + + <> -> + <<<>/binary, + ":size(3)"/utf8>>; + + <> -> + <<<>/binary, + ":size(4)"/utf8>>; + + <> -> + <<<>/binary, + ":size(5)"/utf8>>; + + <> -> + <<<>/binary, + ":size(6)"/utf8>>; + + <> -> + <<<>/binary, + ":size(7)"/utf8>>; + + <> -> + Suffix = case Rest of + <<>> -> + <<""/utf8>>; + + _ -> + <<", "/utf8>> + end, + Accumulator@1 = <<<>/binary, + Suffix/binary>>, + inspect_loop(Rest, Accumulator@1); + + _ -> + Accumulator + end. + +-file("src/gleam/bit_array.gleam", 187). +?DOC( + " Converts a bit array to a string containing the decimal value of each byte.\n" + "\n" + " Use this over `string.inspect` when you have a bit array you want printed\n" + " in the array syntax even if it is valid UTF-8.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert inspect(<<0, 20, 0x20, 255>>) == \"<<0, 20, 32, 255>>\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert inspect(<<100, 5:3>>) == \"<<100, 5:size(3)>>\"\n" + " ```\n" +). +-spec inspect(bitstring()) -> binary(). +inspect(Input) -> + <<(inspect_loop(Input, <<"<<"/utf8>>))/binary, ">>"/utf8>>. + +-file("src/gleam/bit_array.gleam", 233). +?DOC( + " Compare two bit arrays as sequences of bytes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(<<1>>, <<2>>) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(<<\"AB\":utf8>>, <<\"AA\":utf8>>) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>) == Eq\n" + " ```\n" +). +-spec compare(bitstring(), bitstring()) -> gleam@order:order(). +compare(A, B) -> + case {A, B} of + {<>, + <>} -> + case {First_byte, Second_byte} of + {F, S} when F > S -> + gt; + + {F@1, S@1} when F@1 < S@1 -> + lt; + + {_, _} -> + compare(First_rest, Second_rest) + end; + + {<<>>, <<>>} -> + eq; + + {_, <<>>} -> + gt; + + {<<>>, _} -> + lt; + + {First, Second} -> + case {gleam_stdlib:bit_array_to_int_and_size(First), + gleam_stdlib:bit_array_to_int_and_size(Second)} of + {{A@1, _}, {B@1, _}} when A@1 > B@1 -> + gt; + + {{A@2, _}, {B@2, _}} when A@2 < B@2 -> + lt; + + {{_, Size_a}, {_, Size_b}} when Size_a > Size_b -> + gt; + + {{_, Size_a@1}, {_, Size_b@1}} when Size_a@1 < Size_b@1 -> + lt; + + {_, _} -> + eq + end + end. + +-file("src/gleam/bit_array.gleam", 273). +?DOC( + " Checks whether the first `BitArray` starts with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert starts_with(<<1, 2, 3, 4>>, <<1, 2>>)\n" + " ```\n" +). +-spec starts_with(bitstring(), bitstring()) -> boolean(). +starts_with(Bits, Prefix) -> + Prefix_size = erlang:bit_size(Prefix), + case Bits of + <> when Pref =:= Prefix -> + true; + + _ -> + false + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bool.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bool.erl new file mode 100644 index 0000000..edb3be8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bool.erl @@ -0,0 +1,334 @@ +-module(gleam@bool). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bool.gleam"). +-export(['and'/2, 'or'/2, negate/1, nor/2, nand/2, exclusive_or/2, exclusive_nor/2, to_string/1, guard/3, lazy_guard/3]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A type with two possible values, `True` and `False`. Used to indicate whether\n" + " things are... true or false!\n" + "\n" + " It is often clearer and offers more type safety to define a custom type\n" + " than to use `Bool`. For example, rather than having a `is_teacher: Bool`\n" + " field consider having a `role: SchoolRole` field where `SchoolRole` is a custom\n" + " type that can be either `Student` or `Teacher`.\n" +). + +-file("src/gleam/bool.gleam", 32). +?DOC( + " Returns the and of two bools, but it evaluates both arguments.\n" + "\n" + " It's the function equivalent of the `&&` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert and(True, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, False)\n" + " ```\n" +). +-spec 'and'(boolean(), boolean()) -> boolean(). +'and'(A, B) -> + A andalso B. + +-file("src/gleam/bool.gleam", 59). +?DOC( + " Returns the or of two bools, but it evaluates both arguments.\n" + "\n" + " It's the function equivalent of the `||` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(True, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !or(False, False)\n" + " ```\n" +). +-spec 'or'(boolean(), boolean()) -> boolean(). +'or'(A, B) -> + A orelse B. + +-file("src/gleam/bool.gleam", 77). +?DOC( + " Returns the opposite bool value.\n" + "\n" + " This is the same as the `!` or `not` operators in some other languages.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !negate(True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(False)\n" + " ```\n" +). +-spec negate(boolean()) -> boolean(). +negate(Bool) -> + not Bool. + +-file("src/gleam/bool.gleam", 101). +?DOC( + " Returns the nor of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert nor(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(True, True)\n" + " ```\n" +). +-spec nor(boolean(), boolean()) -> boolean(). +nor(A, B) -> + not (A orelse B). + +-file("src/gleam/bool.gleam", 125). +?DOC( + " Returns the nand of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert nand(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert nand(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert nand(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nand(True, True)\n" + " ```\n" +). +-spec nand(boolean(), boolean()) -> boolean(). +nand(A, B) -> + not (A andalso B). + +-file("src/gleam/bool.gleam", 149). +?DOC( + " Returns the exclusive or of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !exclusive_or(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_or(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_or(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_or(True, True)\n" + " ```\n" +). +-spec exclusive_or(boolean(), boolean()) -> boolean(). +exclusive_or(A, B) -> + A /= B. + +-file("src/gleam/bool.gleam", 173). +?DOC( + " Returns the exclusive nor of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert exclusive_nor(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_nor(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_nor(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_nor(True, True)\n" + " ```\n" +). +-spec exclusive_nor(boolean(), boolean()) -> boolean(). +exclusive_nor(A, B) -> + A =:= B. + +-file("src/gleam/bool.gleam", 189). +?DOC( + " Returns a string representation of the given bool.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(True) == \"True\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_string(False) == \"False\"\n" + " ```\n" +). +-spec to_string(boolean()) -> binary(). +to_string(Bool) -> + case Bool of + false -> + <<"False"/utf8>>; + + true -> + <<"True"/utf8>> + end. + +-file("src/gleam/bool.gleam", 248). +?DOC( + " Run a callback function if the given bool is `False`, otherwise return a\n" + " default value.\n" + "\n" + " With a `use` expression this function can simulate the early-return pattern\n" + " found in some other programming languages.\n" + "\n" + " In a procedural language:\n" + "\n" + " ```js\n" + " if (predicate) return value;\n" + " // ...\n" + " ```\n" + "\n" + " In Gleam with a `use` expression:\n" + "\n" + " ```gleam\n" + " use <- guard(when: predicate, return: value)\n" + " // ...\n" + " ```\n" + "\n" + " Like everything in Gleam `use` is an expression, so it short circuits the\n" + " current block, not the entire function. As a result you can assign the value\n" + " to a variable:\n" + "\n" + " ```gleam\n" + " let x = {\n" + " use <- guard(when: predicate, return: value)\n" + " // ...\n" + " }\n" + " ```\n" + "\n" + " Note that unlike in procedural languages the `return` value is evaluated\n" + " even when the predicate is `False`, so it is advisable not to perform\n" + " expensive computation nor side-effects there.\n" + "\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let name = \"\"\n" + " use <- guard(when: name == \"\", return: \"Welcome!\")\n" + " \"Hello, \" <> name\n" + " // -> \"Welcome!\"\n" + " ```\n" + "\n" + " ```gleam\n" + " let name = \"Kamaka\"\n" + " use <- guard(when: name == \"\", return: \"Welcome!\")\n" + " \"Hello, \" <> name\n" + " // -> \"Hello, Kamaka\"\n" + " ```\n" +). +-spec guard(boolean(), BTP, fun(() -> BTP)) -> BTP. +guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence; + + false -> + Alternative() + end. + +-file("src/gleam/bool.gleam", 289). +?DOC( + " Runs a callback function if the given bool is `True`, otherwise runs an\n" + " alternative callback function.\n" + "\n" + " Useful when further computation should be delayed regardless of the given\n" + " bool's value.\n" + "\n" + " See [`guard`](#guard) for more info.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let name = \"Kamaka\"\n" + " let inquiry = fn() { \"How may we address you?\" }\n" + " use <- lazy_guard(when: name == \"\", return: inquiry)\n" + " \"Hello, \" <> name\n" + " // -> \"Hello, Kamaka\"\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " let name = \"\"\n" + " let greeting = fn() { \"Hello, \" <> name }\n" + " use <- lazy_guard(when: name == \"\", otherwise: greeting)\n" + " let number = int.random(99)\n" + " let name = \"User \" <> int.to_string(number)\n" + " \"Welcome, \" <> name\n" + " // -> \"Welcome, User 54\"\n" + " ```\n" +). +-spec lazy_guard(boolean(), fun(() -> BTQ), fun(() -> BTQ)) -> BTQ. +lazy_guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence(); + + false -> + Alternative() + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl new file mode 100644 index 0000000..e9c906b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl @@ -0,0 +1,211 @@ +-module(gleam@bytes_tree). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bytes_tree.gleam"). +-export([append_tree/2, prepend_tree/2, concat/1, new/0, from_string/1, prepend_string/2, append_string/2, from_string_tree/1, from_bit_array/1, prepend/2, append/2, concat_bit_arrays/1, to_bit_array/1, byte_size/1]). +-export_type([bytes_tree/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " `BytesTree` is a type used for efficiently building binary content to be\n" + " written to a file or a socket. Internally it is represented as a tree so to\n" + " append or prepend to a bytes tree is a constant time operation that\n" + " allocates a new node in the tree without copying any of the content. When\n" + " writing to an output stream the tree is traversed and the content is sent\n" + " directly rather than copying it into a single buffer beforehand.\n" + "\n" + " If we append one bit array to another the bit arrays must be copied to a\n" + " new location in memory so that they can sit together. This behaviour\n" + " enables efficient reading of the data but copying can be expensive,\n" + " especially if we want to join many bit arrays together.\n" + "\n" + " BytesTree is different in that it can be joined together in constant\n" + " time using minimal memory, and then can be efficiently converted to a\n" + " bit array using the `to_bit_array` function.\n" + "\n" + " Byte trees are always byte aligned, so that a number of bits that is not\n" + " divisible by 8 will be padded with 0s.\n" + "\n" + " On Erlang this type is compatible with Erlang's iolists.\n" +). + +-opaque bytes_tree() :: {bytes, bitstring()} | + {text, gleam@string_tree:string_tree()} | + {many, list(bytes_tree())}. + +-file("src/gleam/bytes_tree.gleam", 68). +?DOC( + " Appends a bytes tree onto the end of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec append_tree(bytes_tree(), bytes_tree()) -> bytes_tree(). +append_tree(First, Second) -> + gleam_stdlib:iodata_append(First, Second). + +-file("src/gleam/bytes_tree.gleam", 59). +?DOC( + " Prepends a bytes tree onto the start of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend_tree(bytes_tree(), bytes_tree()) -> bytes_tree(). +prepend_tree(Second, First) -> + gleam_stdlib:iodata_append(First, Second). + +-file("src/gleam/bytes_tree.gleam", 98). +?DOC( + " Joins a list of bytes trees into a single one.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat(list(bytes_tree())) -> bytes_tree(). +concat(Trees) -> + gleam_stdlib:identity(Trees). + +-file("src/gleam/bytes_tree.gleam", 35). +?DOC( + " Create an empty `BytesTree`. Useful as the start of a pipe chaining many\n" + " trees together.\n" +). +-spec new() -> bytes_tree(). +new() -> + gleam_stdlib:identity([]). + +-file("src/gleam/bytes_tree.gleam", 118). +?DOC( + " Creates a new bytes tree from a string.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time otherwise.\n" +). +-spec from_string(binary()) -> bytes_tree(). +from_string(String) -> + gleam_stdlib:wrap_list(String). + +-file("src/gleam/bytes_tree.gleam", 80). +?DOC( + " Prepends a string onto the start of a bytes tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time with the length of the string otherwise.\n" +). +-spec prepend_string(bytes_tree(), binary()) -> bytes_tree(). +prepend_string(Second, First) -> + gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second). + +-file("src/gleam/bytes_tree.gleam", 89). +?DOC( + " Appends a string onto the end of a bytes tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time with the length of the string otherwise.\n" +). +-spec append_string(bytes_tree(), binary()) -> bytes_tree(). +append_string(First, Second) -> + gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)). + +-file("src/gleam/bytes_tree.gleam", 128). +?DOC( + " Creates a new bytes tree from a string tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time otherwise.\n" +). +-spec from_string_tree(gleam@string_tree:string_tree()) -> bytes_tree(). +from_string_tree(Tree) -> + gleam_stdlib:wrap_list(Tree). + +-file("src/gleam/bytes_tree.gleam", 136). +?DOC( + " Creates a new bytes tree from a bit array.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_bit_array(bitstring()) -> bytes_tree(). +from_bit_array(Bits) -> + _pipe = Bits, + _pipe@1 = gleam_stdlib:bit_array_pad_to_bytes(_pipe), + gleam_stdlib:wrap_list(_pipe@1). + +-file("src/gleam/bytes_tree.gleam", 43). +?DOC( + " Prepends a bit array to the start of a bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend(bytes_tree(), bitstring()) -> bytes_tree(). +prepend(Second, First) -> + gleam_stdlib:iodata_append(from_bit_array(First), Second). + +-file("src/gleam/bytes_tree.gleam", 51). +?DOC( + " Appends a bit array to the end of a bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec append(bytes_tree(), bitstring()) -> bytes_tree(). +append(First, Second) -> + gleam_stdlib:iodata_append(First, from_bit_array(Second)). + +-file("src/gleam/bytes_tree.gleam", 106). +?DOC( + " Joins a list of bit arrays into a single bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat_bit_arrays(list(bitstring())) -> bytes_tree(). +concat_bit_arrays(Bits) -> + _pipe = Bits, + _pipe@1 = gleam@list:map(_pipe, fun from_bit_array/1), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/bytes_tree.gleam", 162). +-spec to_list(list(list(bytes_tree())), list(bitstring())) -> list(bitstring()). +to_list(Stack, Acc) -> + case Stack of + [] -> + Acc; + + [[] | Remaining_stack] -> + to_list(Remaining_stack, Acc); + + [[{bytes, Bits} | Rest] | Remaining_stack@1] -> + to_list([Rest | Remaining_stack@1], [Bits | Acc]); + + [[{text, Tree} | Rest@1] | Remaining_stack@2] -> + Bits@1 = gleam_stdlib:identity(unicode:characters_to_binary(Tree)), + to_list([Rest@1 | Remaining_stack@2], [Bits@1 | Acc]); + + [[{many, Trees} | Rest@2] | Remaining_stack@3] -> + to_list([Trees, Rest@2 | Remaining_stack@3], Acc) + end. + +-file("src/gleam/bytes_tree.gleam", 155). +?DOC( + " Turns a bytes tree into a bit array.\n" + "\n" + " Runs in linear time.\n" + "\n" + " When running on Erlang this function is implemented natively by the\n" + " virtual machine and is highly optimised.\n" +). +-spec to_bit_array(bytes_tree()) -> bitstring(). +to_bit_array(Tree) -> + erlang:list_to_bitstring(Tree). + +-file("src/gleam/bytes_tree.gleam", 186). +?DOC( + " Returns the size of the bytes tree's content in bytes.\n" + "\n" + " Runs in linear time.\n" +). +-spec byte_size(bytes_tree()) -> integer(). +byte_size(Tree) -> + erlang:iolist_size(Tree). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dict.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dict.erl new file mode 100644 index 0000000..e3e818e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dict.erl @@ -0,0 +1,513 @@ +-module(gleam@dict). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dict.gleam"). +-export([size/1, is_empty/1, has_key/2, new/0, get/2, insert/3, from_list/1, map_values/2, take/2, delete/2, drop/2, upsert/3, fold/3, to_list/1, keys/1, values/1, filter/2, each/2, combine/3, merge/2, group/2]). +-export_type([dict/2, transient_dict/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type dict(JL, JM) :: any() | {gleam_phantom, JL, JM}. + +-type transient_dict(JN, JO) :: any() | {gleam_phantom, JN, JO}. + +-file("src/gleam/dict.gleam", 53). +?DOC( + " Determines the number of key-value pairs in the dict.\n" + " This function runs in constant time and does not need to iterate the dict.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> size == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"key\", \"value\") |> size == 1\n" + " ```\n" +). +-spec size(dict(any(), any())) -> integer(). +size(Dict) -> + maps:size(Dict). + +-file("src/gleam/dict.gleam", 67). +?DOC( + " Determines whether or not the dict is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(\"b\", 1) |> is_empty }\n" + " ```\n" +). +-spec is_empty(dict(any(), any())) -> boolean(). +is_empty(Dict) -> + maps:size(Dict) =:= 0. + +-file("src/gleam/dict.gleam", 135). +?DOC( + " Determines whether or not a value is present in the dict for a given key.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> has_key(\"a\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(\"a\", 0) |> has_key(\"b\") }\n" + " ```\n" +). +-spec has_key(dict(LA, any()), LA) -> boolean(). +has_key(Dict, Key) -> + maps:is_key(Key, Dict). + +-file("src/gleam/dict.gleam", 146). +?DOC(" Creates a fresh dict that contains no values.\n"). +-spec new() -> dict(any(), any()). +new() -> + maps:new(). + +-file("src/gleam/dict.gleam", 165). +?DOC( + " Fetches a value from a dict for a given key.\n" + "\n" + " The dict may not have a value for the key, so the value is wrapped in a\n" + " `Result`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> get(\"a\") == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> get(\"b\") == Error(Nil)\n" + " ```\n" +). +-spec get(dict(LM, LN), LM) -> {ok, LN} | {error, nil}. +get(From, Get) -> + gleam_stdlib:map_get(From, Get). + +-file("src/gleam/dict.gleam", 183). +?DOC( + " Inserts a value into the dict with the given key.\n" + "\n" + " If the dict already has a value for the given key then the value is\n" + " replaced with the new value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) == from_list([#(\"a\", 0)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> insert(\"a\", 5) == from_list([#(\"a\", 5)])\n" + " ```\n" +). +-spec insert(dict(LS, LT), LS, LT) -> dict(LS, LT). +insert(Dict, Key, Value) -> + maps:put(Key, Value, Dict). + +-file("src/gleam/dict.gleam", 111). +-spec from_list_loop(transient_dict(KT, KU), list({KT, KU})) -> dict(KT, KU). +from_list_loop(Transient, List) -> + case List of + [] -> + gleam_stdlib:identity(Transient); + + [{Key, Value} | Rest] -> + from_list_loop(maps:put(Key, Value, Transient), Rest) + end. + +-file("src/gleam/dict.gleam", 107). +?DOC( + " Converts a list of 2-element tuples `#(key, value)` to a dict.\n" + "\n" + " If two tuples have the same key the last one in the list will be the one\n" + " that is present in the dict.\n" +). +-spec from_list(list({KO, KP})) -> dict(KO, KP). +from_list(List) -> + maps:from_list(List). + +-file("src/gleam/dict.gleam", 210). +?DOC( + " Updates all values in a given dict by calling a given function on each key\n" + " and value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(3, 3), #(2, 4)])\n" + " |> map_values(fn(key, value) { key * value })\n" + " == from_list([#(3, 9), #(2, 8)])\n" + " ```\n" +). +-spec map_values(dict(MK, ML), fun((MK, ML) -> MO)) -> dict(MK, MO). +map_values(Dict, Fun) -> + maps:map(Fun, Dict). + +-file("src/gleam/dict.gleam", 313). +-spec do_take_loop(dict(OI, OJ), list(OI), transient_dict(OI, OJ)) -> dict(OI, OJ). +do_take_loop(Dict, Desired_keys, Acc) -> + case Desired_keys of + [] -> + gleam_stdlib:identity(Acc); + + [Key | Rest] -> + case gleam_stdlib:map_get(Dict, Key) of + {ok, Value} -> + do_take_loop(Dict, Rest, maps:put(Key, Value, Acc)); + + {error, _} -> + do_take_loop(Dict, Rest, Acc) + end + end. + +-file("src/gleam/dict.gleam", 304). +?DOC( + " Creates a new dict from a given dict, only including any entries for which the\n" + " keys are in a given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> take([\"b\"])\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> take([\"a\", \"b\", \"c\"])\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec take(dict(NU, NV), list(NU)) -> dict(NU, NV). +take(Dict, Desired_keys) -> + maps:with(Desired_keys, Dict). + +-file("src/gleam/dict.gleam", 361). +?DOC( + " Creates a new dict from a given dict with all the same entries except for the\n" + " one with a given key, if it exists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"a\")\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"c\")\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec delete(dict(OZ, PA), OZ) -> dict(OZ, PA). +delete(Dict, Key) -> + _pipe = gleam_stdlib:identity(Dict), + _pipe@1 = maps:remove(Key, _pipe), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/dict.gleam", 398). +-spec drop_loop(transient_dict(PZ, QA), list(PZ)) -> dict(PZ, QA). +drop_loop(Transient, Disallowed_keys) -> + case Disallowed_keys of + [] -> + gleam_stdlib:identity(Transient); + + [Key | Rest] -> + drop_loop(maps:remove(Key, Transient), Rest) + end. + +-file("src/gleam/dict.gleam", 389). +?DOC( + " Creates a new dict from a given dict with all the same entries except any with\n" + " keys found in a given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\"])\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"c\"])\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\", \"b\", \"c\"])\n" + " == from_list([])\n" + " ```\n" +). +-spec drop(dict(PL, PM), list(PL)) -> dict(PL, PM). +drop(Dict, Disallowed_keys) -> + maps:without(Disallowed_keys, Dict). + +-file("src/gleam/dict.gleam", 431). +?DOC( + " Creates a new dict with one entry inserted or updated using a given function.\n" + "\n" + " If there was not an entry in the dict for the given key then the function\n" + " gets `None` as its argument, otherwise it gets `Some(value)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let dict = from_list([#(\"a\", 0)])\n" + " let increment = fn(x) {\n" + " case x {\n" + " Some(i) -> i + 1\n" + " None -> 0\n" + " }\n" + " }\n" + "\n" + " assert upsert(dict, \"a\", increment) == from_list([#(\"a\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert upsert(dict, \"b\", increment) == from_list([#(\"a\", 0), #(\"b\", 0)])\n" + " ```\n" +). +-spec upsert(dict(QG, QH), QG, fun((gleam@option:option(QH)) -> QH)) -> dict(QG, QH). +upsert(Dict, Key, Fun) -> + case gleam_stdlib:map_get(Dict, Key) of + {ok, Value} -> + insert(Dict, Key, Fun({some, Value})); + + {error, _} -> + insert(Dict, Key, Fun(none)) + end. + +-file("src/gleam/dict.gleam", 469). +?DOC( + " Combines all entries into a single value by calling a given function on each\n" + " one.\n" + "\n" + " Dicts are not ordered so the values are not returned in any specific order. Do\n" + " not write code that relies on the order entries are used by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n" + " assert fold(dict, 0, fn(accumulator, key, value) { accumulator + value })\n" + " == 13\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/string\n" + "\n" + " let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n" + " assert\n" + " fold(dict, \"\", fn(accumulator, key, value) {\n" + " string.append(accumulator, key)\n" + " })\n" + " == \"abc\"\n" + " ```\n" +). +-spec fold(dict(QN, QO), QR, fun((QR, QN, QO) -> QR)) -> QR. +fold(Dict, Initial, Fun) -> + Fun@1 = fun(Key, Value, Acc) -> Fun(Acc, Key, Value) end, + maps:fold(Fun@1, Initial, Dict). + +-file("src/gleam/dict.gleam", 97). +?DOC( + " Converts the dict to a list of 2-element tuples `#(key, value)`, one for\n" + " each key-value pair in the dict.\n" + "\n" + " The tuples in the list have no specific order.\n" + "\n" + " ## Examples\n" + "\n" + " Calling `to_list` on an empty `dict` returns an empty list.\n" + "\n" + " ```gleam\n" + " assert new() |> to_list == []\n" + " ```\n" + "\n" + " The ordering of elements in the resulting list is an implementation detail\n" + " that should not be relied upon.\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(\"b\", 1)\n" + " |> insert(\"a\", 0)\n" + " |> insert(\"c\", 2)\n" + " |> to_list\n" + " == [#(\"a\", 0), #(\"b\", 1), #(\"c\", 2)]\n" + " ```\n" +). +-spec to_list(dict(KJ, KK)) -> list({KJ, KK}). +to_list(Dict) -> + maps:to_list(Dict). + +-file("src/gleam/dict.gleam", 230). +?DOC( + " Gets a list of all keys in a given dict.\n" + "\n" + " Dicts are not ordered so the keys are not returned in any specific order. Do\n" + " not write code that relies on the order keys are returned by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> keys == [\"a\", \"b\"]\n" + " ```\n" +). +-spec keys(dict(MY, any())) -> list(MY). +keys(Dict) -> + maps:keys(Dict). + +-file("src/gleam/dict.gleam", 247). +?DOC( + " Gets a list of all values in a given dict.\n" + "\n" + " Dicts are not ordered so the values are not returned in any specific order. Do\n" + " not write code that relies on the order values are returned by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> values == [0, 1]\n" + " ```\n" +). +-spec values(dict(any(), NE)) -> list(NE). +values(Dict) -> + maps:values(Dict). + +-file("src/gleam/dict.gleam", 268). +?DOC( + " Creates a new dict from a given dict, minus any entries that a given function\n" + " returns `False` for.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> filter(fn(key, value) { value != 0 })\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> filter(fn(key, value) { True })\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec filter(dict(NI, NJ), fun((NI, NJ) -> boolean())) -> dict(NI, NJ). +filter(Dict, Predicate) -> + maps:filter(Predicate, Dict). + +-file("src/gleam/dict.gleam", 504). +?DOC( + " Calls a function for each key and value in a dict, discarding the return\n" + " value.\n" + "\n" + " Useful for producing a side effect for every item of a dict.\n" + "\n" + " ```gleam\n" + " import gleam/io\n" + "\n" + " let dict = from_list([#(\"a\", \"apple\"), #(\"b\", \"banana\"), #(\"c\", \"cherry\")])\n" + "\n" + " assert\n" + " each(dict, fn(k, v) {\n" + " io.println(k <> \" => \" <> v)\n" + " })\n" + " == Nil\n" + " // a => apple\n" + " // b => banana\n" + " // c => cherry\n" + " ```\n" + "\n" + " The order of elements in the iteration is an implementation detail that\n" + " should not be relied upon.\n" +). +-spec each(dict(QX, QY), fun((QX, QY) -> any())) -> nil. +each(Dict, Fun) -> + fold( + Dict, + nil, + fun(Nil, K, V) -> + Fun(K, V), + Nil + end + ). + +-file("src/gleam/dict.gleam", 525). +?DOC( + " Creates a new dict from a pair of given dicts by combining their entries.\n" + "\n" + " If there are entries with the same keys in both dicts the given function is\n" + " used to determine the new value to use in the resulting dict.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " let b = from_list([#(\"a\", 2), #(\"c\", 3)])\n" + " assert combine(a, b, fn(one, other) { one + other })\n" + " == from_list([#(\"a\", 2), #(\"b\", 1), #(\"c\", 3)])\n" + " ```\n" +). +-spec combine(dict(RC, RD), dict(RC, RD), fun((RD, RD) -> RD)) -> dict(RC, RD). +combine(Dict, Other, Fun) -> + maps:merge_with(fun(_, L, R) -> Fun(L, R) end, Dict, Other). + +-file("src/gleam/dict.gleam", 342). +?DOC( + " Creates a new dict from a pair of given dicts by combining their entries.\n" + "\n" + " If there are entries with the same keys in both dicts the entry from the\n" + " second dict takes precedence.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " let b = from_list([#(\"b\", 2), #(\"c\", 3)])\n" + " assert merge(a, b) == from_list([#(\"a\", 0), #(\"b\", 2), #(\"c\", 3)])\n" + " ```\n" +). +-spec merge(dict(OR, OS), dict(OR, OS)) -> dict(OR, OS). +merge(Dict, New_entries) -> + maps:merge(Dict, New_entries). + +-file("src/gleam/dict.gleam", 566). +-spec group_loop(transient_dict(SE, list(SF)), fun((SF) -> SE), list(SF)) -> dict(SE, list(SF)). +group_loop(Transient, To_key, List) -> + case List of + [] -> + gleam_stdlib:identity(Transient); + + [Value | Rest] -> + Key = To_key(Value), + Update = fun(Existing) -> [Value | Existing] end, + _pipe = Transient, + _pipe@1 = maps:update_with(Key, Update, [Value], _pipe), + group_loop(_pipe@1, To_key, Rest) + end. + +-file("src/gleam/dict.gleam", 562). +?DOC(false). +-spec group(fun((RY) -> RZ), list(RY)) -> dict(RZ, list(RY)). +group(Key, List) -> + group_loop(gleam_stdlib:identity(maps:new()), Key, List). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic.erl new file mode 100644 index 0000000..5c260ef --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic.erl @@ -0,0 +1,105 @@ +-module(gleam@dynamic). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dynamic.gleam"). +-export([classify/1, bool/1, string/1, float/1, int/1, bit_array/1, list/1, array/1, properties/1, nil/0]). +-export_type([dynamic_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type dynamic_() :: any(). + +-file("src/gleam/dynamic.gleam", 29). +?DOC( + " Return a string indicating the type of the dynamic value.\n" + "\n" + " This function may be useful for constructing error messages or logs. If you\n" + " want to turn dynamic data into well typed data then you want the\n" + " `gleam/dynamic/decode` module.\n" + "\n" + " ```gleam\n" + " assert classify(string(\"Hello\")) == \"String\"\n" + " ```\n" +). +-spec classify(dynamic_()) -> binary(). +classify(Data) -> + gleam_stdlib:classify_dynamic(Data). + +-file("src/gleam/dynamic.gleam", 35). +?DOC(" Create a dynamic value from a bool.\n"). +-spec bool(boolean()) -> dynamic_(). +bool(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 43). +?DOC( + " Create a dynamic value from a string.\n" + "\n" + " On Erlang this will be a binary string rather than a character list.\n" +). +-spec string(binary()) -> dynamic_(). +string(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 49). +?DOC(" Create a dynamic value from a float.\n"). +-spec float(float()) -> dynamic_(). +float(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 55). +?DOC(" Create a dynamic value from an int.\n"). +-spec int(integer()) -> dynamic_(). +int(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 61). +?DOC(" Create a dynamic value from a bit array.\n"). +-spec bit_array(bitstring()) -> dynamic_(). +bit_array(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 67). +?DOC(" Create a dynamic value from a list.\n"). +-spec list(list(dynamic_())) -> dynamic_(). +list(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 76). +?DOC( + " Create a dynamic value from a list, converting it to a sequential runtime\n" + " format rather than the regular list format.\n" + "\n" + " On Erlang this will be a tuple, on JavaScript this will be an array.\n" +). +-spec array(list(dynamic_())) -> dynamic_(). +array(A) -> + erlang:list_to_tuple(A). + +-file("src/gleam/dynamic.gleam", 84). +?DOC( + " Create a dynamic value made of an unordered series of keys and values, where\n" + " the keys are unique.\n" + "\n" + " On Erlang this will be a map, on JavaScript this will be a Gleam dict\n" + " object.\n" +). +-spec properties(list({dynamic_(), dynamic_()})) -> dynamic_(). +properties(Entries) -> + gleam_stdlib:identity(maps:from_list(Entries)). + +-file("src/gleam/dynamic.gleam", 93). +?DOC( + " A dynamic value representing nothing.\n" + "\n" + " On Erlang this will be the atom `nil`, on JavaScript this will be\n" + " `undefined`.\n" +). +-spec nil() -> dynamic_(). +nil() -> + gleam_stdlib:identity(nil). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl new file mode 100644 index 0000000..e881579 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl @@ -0,0 +1,1121 @@ +-module(gleam@dynamic@decode). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dynamic/decode.gleam"). +-export([run/2, success/1, decode_dynamic/1, map/2, map_errors/2, then/2, one_of/2, recursive/1, optional/1, decode_error/2, decode_bool/1, decode_int/1, decode_float/1, decode_bit_array/1, collapse_errors/2, failure/2, new_primitive_decoder/2, decode_string/1, dict/2, list/1, subfield/3, at/2, field/3, optional_field/4, optionally_at/3]). +-export_type([decode_error/0, decoder/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " The `Dynamic` type is used to represent dynamically typed data. That is, data\n" + " that we don't know the precise type of yet, so we need to introspect the data to\n" + " see if it is of the desired type before we can use it. Typically data like this\n" + " would come from user input or from untyped languages such as Erlang or JavaScript.\n" + "\n" + " This module provides the `Decoder` type and associated functions, which provides\n" + " a type-safe and composable way to convert dynamic data into some desired type,\n" + " or into errors if the data doesn't have the desired structure.\n" + "\n" + " The `Decoder` type is generic and has 1 type parameter, which is the type that\n" + " it attempts to decode. A `Decoder(String)` can be used to decode strings, and a\n" + " `Decoder(Option(Int))` can be used to decode `Option(Int)`s\n" + "\n" + " Decoders work using _runtime reflection_ and the data structures of the target\n" + " platform. Differences between Erlang and JavaScript data structures may impact\n" + " your decoders, so it is important to test your decoders on all supported\n" + " platforms.\n" + "\n" + " The decoding technique used by this module was inspired by Juraj Petráš'\n" + " [Toy](https://github.com/Hackder/toy), Go's `encoding/json`, and Elm's\n" + " `Json.Decode`. Thank you to them!\n" + "\n" + " # Generating decoders\n" + "\n" + " The language server has the \"generate dynamic decoder\" code action, which\n" + " will generate a decoder function when run on a custom type definition.\n" + " This generated decoder function can be a convenient shortcut when creating\n" + " your own decoders, and you can edit the generated function to suit your needs.\n" + "\n" + " # Examples\n" + "\n" + " Dynamic data may come from various sources and so many different syntaxes could\n" + " be used to describe or construct them. In these examples a pseudocode\n" + " syntax is used to describe the data.\n" + "\n" + " ## Simple types\n" + "\n" + " This module defines decoders for simple data types such as [`string`](#string),\n" + " [`int`](#int), [`float`](#float), [`bit_array`](#bit_array), and [`bool`](#bool).\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // \"Hello, Joe!\"\n" + "\n" + " let result = decode.run(data, decode.string)\n" + " assert result == Ok(\"Hello, Joe!\")\n" + " ```\n" + "\n" + " ## Lists\n" + "\n" + " The [`list`](#list) decoder decodes `List`s. To use it you must construct it by\n" + " passing in another decoder into the `list` function, which is the decoder that\n" + " is to be used for the elements of the list, type checking both the list and its\n" + " elements.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // [1, 2, 3, 4]\n" + "\n" + " let result = decode.run(data, decode.list(decode.int))\n" + " assert result == Ok([1, 2, 3, 4])\n" + " ```\n" + "\n" + " On Erlang this decoder can decode from lists, and on JavaScript it can\n" + " decode from lists as well as JavaScript arrays.\n" + "\n" + " ## Options\n" + "\n" + " The [`optional`](#optional) decoder is used to decode values that may or may not\n" + " be present. In other environments these might be called \"nullable\" values.\n" + "\n" + " Like the `list` decoder, the `optional` decoder takes another decoder,\n" + " which is used to decode the value if it is present.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // 12.45\n" + "\n" + " let result = decode.run(data, decode.optional(decode.float))\n" + " assert result == Ok(option.Some(12.45))\n" + " ```\n" + " ```gleam\n" + " // Data:\n" + " // null\n" + "\n" + " let result = decode.run(data, decode.optional(decode.int))\n" + " assert result == Ok(option.None)\n" + " ```\n" + "\n" + " This decoder knows how to handle multiple different runtime representations of\n" + " absent values, including `Nil`, `None`, `null`, and `undefined`.\n" + "\n" + " ## Dicts\n" + "\n" + " The [`dict`](#dict) decoder decodes `Dicts` and contains two other decoders, one\n" + " for the keys, one for the values.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // { \"Lucy\" -> 10, \"Nubi\" -> 20 }\n" + "\n" + " let result = decode.run(data, decode.dict(decode.string, decode.int))\n" + " assert result == Ok(dict.from_list([\n" + " #(\"Lucy\", 10),\n" + " #(\"Nubi\", 20),\n" + " ]))\n" + " ```\n" + "\n" + " ## Indexing objects\n" + "\n" + " The [`at`](#at) decoder can be used to decode a value that is nested within\n" + " key-value containers such as Gleam dicts, Erlang maps, or JavaScript objects.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // { \"one\" -> { \"two\" -> 123 } }\n" + "\n" + " let result = decode.run(data, decode.at([\"one\", \"two\"], decode.int))\n" + " assert result == Ok(123)\n" + " ```\n" + "\n" + " ## Indexing arrays\n" + "\n" + " If you use ints as keys then the [`at`](#at) decoder can be used to index into\n" + " array-like containers such as Gleam or Erlang tuples, or JavaScript arrays.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // [\"one\", \"two\", \"three\"]\n" + "\n" + " let result = decode.run(data, decode.at([1], decode.string))\n" + " assert result == Ok(\"two\")\n" + " ```\n" + "\n" + " ## Records\n" + "\n" + " Decoding records from dynamic data is more complex and requires combining a\n" + " decoder for each field and a special constructor that builds your records with\n" + " the decoded field values.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // {\n" + " // \"score\" -> 180,\n" + " // \"name\" -> \"Mel Smith\",\n" + " // \"is-admin\" -> false,\n" + " // \"enrolled\" -> true,\n" + " // \"colour\" -> \"Red\",\n" + " // }\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use score <- decode.field(\"score\", decode.int)\n" + " use colour <- decode.field(\"colour\", decode.string)\n" + " use enrolled <- decode.field(\"enrolled\", decode.bool)\n" + " decode.success(Player(name:, score:, colour:, enrolled:))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(Player(\"Mel Smith\", 180, \"Red\", True))\n" + " ```\n" + "\n" + " ## Enum variants\n" + "\n" + " Imagine you have a custom type where all the variants do not contain any values.\n" + "\n" + " ```gleam\n" + " pub type PocketMonsterType {\n" + " Fire\n" + " Water\n" + " Grass\n" + " Electric\n" + " }\n" + " ```\n" + "\n" + " You might choose to encode these variants as strings, `\"fire\"` for `Fire`,\n" + " `\"water\"` for `Water`, and so on. To decode them you'll need to decode the dynamic\n" + " data as a string, but then you'll need to decode it further still as not all\n" + " strings are valid values for the enum. This can be done with the `then`\n" + " function, which enables running a second decoder after the first one\n" + " succeeds.\n" + "\n" + " ```gleam\n" + " let decoder = {\n" + " use decoded_string <- decode.then(decode.string)\n" + " case decoded_string {\n" + " // Return succeeding decoders for valid strings\n" + " \"fire\" -> decode.success(Fire)\n" + " \"water\" -> decode.success(Water)\n" + " \"grass\" -> decode.success(Grass)\n" + " \"electric\" -> decode.success(Electric)\n" + " // Return a failing decoder for any other strings\n" + " _ -> decode.failure(Fire, expected: \"PocketMonsterType\")\n" + " }\n" + " }\n" + "\n" + " let result = decode.run(dynamic.string(\"water\"), decoder)\n" + " assert result == Ok(Water)\n" + "\n" + " let result = decode.run(dynamic.string(\"wobble\"), decoder)\n" + " assert result == Error([DecodeError(\"PocketMonsterType\", \"String\", [])])\n" + " ```\n" + "\n" + " ## Record variants\n" + "\n" + " Decoding type variants that contain other values is done by combining the\n" + " techniques from the \"enum variants\" and \"records\" examples. Imagine you have\n" + " this custom type that you want to decode:\n" + "\n" + " ```gleam\n" + " pub type PocketMonsterPerson {\n" + " Trainer(name: String, badge_count: Int)\n" + " GymLeader(name: String, speciality: PocketMonsterType)\n" + " }\n" + " ```\n" + " And you would like to be able to decode these from dynamic data like this:\n" + " ```erlang\n" + " {\n" + " \"type\" -> \"trainer\",\n" + " \"name\" -> \"Ash\",\n" + " \"badge-count\" -> 1,\n" + " }\n" + " ```\n" + " ```erlang\n" + " {\n" + " \"type\" -> \"gym-leader\",\n" + " \"name\" -> \"Misty\",\n" + " \"speciality\" -> \"water\",\n" + " }\n" + " ```\n" + "\n" + " Notice how both documents have a `\"type\"` field, which is used to indicate which\n" + " variant the data is for.\n" + "\n" + " First, define decoders for each of the variants:\n" + "\n" + " ```gleam\n" + " let trainer_decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use badge_count <- decode.field(\"badge-count\", decode.int)\n" + " decode.success(Trainer(name, badge_count))\n" + " }\n" + "\n" + " let gym_leader_decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use speciality <- decode.field(\"speciality\", pocket_monster_type_decoder)\n" + " decode.success(GymLeader(name, speciality))\n" + " }\n" + " ```\n" + "\n" + " A third decoder can be used to extract and decode the `\"type\"` field, and the\n" + " expression can evaluate to whichever decoder is suitable for the document.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // {\n" + " // \"type\" -> \"gym-leader\",\n" + " // \"name\" -> \"Misty\",\n" + " // \"speciality\" -> \"water\",\n" + " // }\n" + "\n" + " let decoder = {\n" + " use tag <- decode.field(\"type\", decode.string)\n" + " case tag {\n" + " \"gym-leader\" -> gym_leader_decoder\n" + " _ -> trainer_decoder\n" + " }\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(GymLeader(\"Misty\", Water))\n" + " ```\n" +). + +-type decode_error() :: {decode_error, binary(), binary(), list(binary())}. + +-opaque decoder(BVN) :: {decoder, + fun((gleam@dynamic:dynamic_()) -> {BVN, list(decode_error())})}. + +-file("src/gleam/dynamic/decode.gleam", 364). +?DOC( + " Run a decoder on a `Dynamic` value, decoding the value if it is of the\n" + " desired type, or returning errors.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use email <- decode.field(\"email\", decode.string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " decode.run(data, decoder)\n" + " ```\n" +). +-spec run(gleam@dynamic:dynamic_(), decoder(BVV)) -> {ok, BVV} | + {error, list(decode_error())}. +run(Data, Decoder) -> + {Maybe_invalid_data, Errors} = (erlang:element(2, Decoder))(Data), + case Errors of + [] -> + {ok, Maybe_invalid_data}; + + [_ | _] -> + {error, Errors} + end. + +-file("src/gleam/dynamic/decode.gleam", 489). +?DOC( + " Finalise a decoder having successfully extracted a value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.field(\"email\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" +). +-spec success(BWW) -> decoder(BWW). +success(Data) -> + {decoder, fun(_) -> {Data, []} end}. + +-file("src/gleam/dynamic/decode.gleam", 742). +-spec decode_dynamic(gleam@dynamic:dynamic_()) -> {gleam@dynamic:dynamic_(), + list(decode_error())}. +decode_dynamic(Data) -> + {Data, []}. + +-file("src/gleam/dynamic/decode.gleam", 902). +?DOC( + " Apply a transformation function to any value decoded by the decoder.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.int |> decode.map(int.to_string)\n" + " let result = decode.run(dynamic.int(1000), decoder)\n" + " assert result == Ok(\"1000\")\n" + " ```\n" +). +-spec map(decoder(BZT), fun((BZT) -> BZV)) -> decoder(BZV). +map(Decoder, Transformer) -> + {decoder, + fun(D) -> + {Data, Errors} = (erlang:element(2, Decoder))(D), + {Transformer(Data), Errors} + end}. + +-file("src/gleam/dynamic/decode.gleam", 911). +?DOC(" Apply a transformation function to any errors returned by the decoder.\n"). +-spec map_errors( + decoder(BZX), + fun((list(decode_error())) -> list(decode_error())) +) -> decoder(BZX). +map_errors(Decoder, Transformer) -> + {decoder, + fun(D) -> + {Data, Errors} = (erlang:element(2, Decoder))(D), + {Data, Transformer(Errors)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 949). +?DOC( + " Create a new decoder based upon the value of a previous decoder.\n" + "\n" + " This may be useful to run one previous decoder to use in further decoding.\n" +). +-spec then(decoder(CAF), fun((CAF) -> decoder(CAH))) -> decoder(CAH). +then(Decoder, Next) -> + {decoder, + fun(Dynamic_data) -> + {Data, Errors} = (erlang:element(2, Decoder))(Dynamic_data), + Decoder@1 = Next(Data), + {Data@1, _} = Layer = (erlang:element(2, Decoder@1))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + {Data@1, Errors} + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 991). +-spec run_decoders( + gleam@dynamic:dynamic_(), + {CAP, list(decode_error())}, + list(decoder(CAP)) +) -> {CAP, list(decode_error())}. +run_decoders(Data, Failure, Decoders) -> + case Decoders of + [] -> + Failure; + + [Decoder | Decoders@1] -> + {_, Errors} = Layer = (erlang:element(2, Decoder))(Data), + case Errors of + [] -> + Layer; + + [_ | _] -> + run_decoders(Data, Failure, Decoders@1) + end + end. + +-file("src/gleam/dynamic/decode.gleam", 978). +?DOC( + " Create a new decoder from several other decoders. Each of the inner\n" + " decoders is run in turn, and the value from the first to succeed is used.\n" + "\n" + " If no decoder succeeds then the errors from the first decoder are used.\n" + " If you wish for different errors then you may wish to use the\n" + " `collapse_errors` or `map_errors` functions.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.one_of(decode.string, or: [\n" + " decode.int |> decode.map(int.to_string),\n" + " decode.float |> decode.map(float.to_string),\n" + " ])\n" + " assert decode.run(dynamic.int(1000), decoder) == Ok(\"1000\")\n" + " ```\n" +). +-spec one_of(decoder(CAK), list(decoder(CAK))) -> decoder(CAK). +one_of(First, Alternatives) -> + {decoder, + fun(Dynamic_data) -> + {_, Errors} = Layer = (erlang:element(2, First))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + run_decoders(Dynamic_data, Layer, Alternatives) + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 1102). +?DOC( + " Create a decoder that can refer to itself, useful for decoding deeply\n" + " nested data.\n" + "\n" + " Attempting to create a recursive decoder without this function could result\n" + " in an infinite loop. If you are using `field` or other `use`able functions\n" + " then you may not need to use this function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " type Nested {\n" + " Nested(List(Nested))\n" + " Value(String)\n" + " }\n" + "\n" + " fn nested_decoder() -> decode.Decoder(Nested) {\n" + " use <- decode.recursive\n" + " decode.one_of(decode.string |> decode.map(Value), [\n" + " decode.list(nested_decoder()) |> decode.map(Nested),\n" + " ])\n" + " }\n" + " ```\n" +). +-spec recursive(fun(() -> decoder(CBA))) -> decoder(CBA). +recursive(Inner) -> + {decoder, + fun(Data) -> + Decoder = Inner(), + (erlang:element(2, Decoder))(Data) + end}. + +-file("src/gleam/dynamic/decode.gleam", 880). +?DOC( + " A decoder that decodes nullable values of a type decoded by with a given\n" + " decoder.\n" + "\n" + " This function can handle common representations of null on all runtimes, such as\n" + " `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on\n" + " JavaScript.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let result = decode.run(dynamic.int(100), decode.optional(decode.int))\n" + " assert result == Ok(option.Some(100))\n" + " ```\n" + "\n" + " ```gleam\n" + " let result = decode.run(dynamic.nil(), decode.optional(decode.int))\n" + " assert result == Ok(option.None)\n" + " ```\n" +). +-spec optional(decoder(BZP)) -> decoder(gleam@option:option(BZP)). +optional(Inner) -> + {decoder, fun(Data) -> case gleam_stdlib:is_null(Data) of + true -> + {none, []}; + + false -> + {Data@1, Errors} = (erlang:element(2, Inner))(Data), + {{some, Data@1}, Errors} + end end}. + +-file("src/gleam/dynamic/decode.gleam", 495). +?DOC(" Construct a decode error for some unexpected dynamic data.\n"). +-spec decode_error(binary(), gleam@dynamic:dynamic_()) -> list(decode_error()). +decode_error(Expected, Found) -> + [{decode_error, Expected, gleam_stdlib:classify_dynamic(Found), []}]. + +-file("src/gleam/dynamic/decode.gleam", 617). +-spec run_dynamic_function( + gleam@dynamic:dynamic_(), + binary(), + fun((gleam@dynamic:dynamic_()) -> {ok, BXQ} | {error, BXQ}) +) -> {BXQ, list(decode_error())}. +run_dynamic_function(Data, Name, F) -> + case F(Data) of + {ok, Data@1} -> + {Data@1, []}; + + {error, Placeholder} -> + {Placeholder, + [{decode_error, Name, gleam_stdlib:classify_dynamic(Data), []}]} + end. + +-file("src/gleam/dynamic/decode.gleam", 668). +-spec decode_bool(gleam@dynamic:dynamic_()) -> {boolean(), list(decode_error())}. +decode_bool(Data) -> + case gleam_stdlib:identity(true) =:= Data of + true -> + {true, []}; + + false -> + case gleam_stdlib:identity(false) =:= Data of + true -> + {false, []}; + + false -> + {false, decode_error(<<"Bool"/utf8>>, Data)} + end + end. + +-file("src/gleam/dynamic/decode.gleam", 697). +-spec decode_int(gleam@dynamic:dynamic_()) -> {integer(), list(decode_error())}. +decode_int(Data) -> + run_dynamic_function(Data, <<"Int"/utf8>>, fun gleam_stdlib:int/1). + +-file("src/gleam/dynamic/decode.gleam", 723). +-spec decode_float(gleam@dynamic:dynamic_()) -> {float(), list(decode_error())}. +decode_float(Data) -> + run_dynamic_function(Data, <<"Float"/utf8>>, fun gleam_stdlib:float/1). + +-file("src/gleam/dynamic/decode.gleam", 757). +-spec decode_bit_array(gleam@dynamic:dynamic_()) -> {bitstring(), + list(decode_error())}. +decode_bit_array(Data) -> + run_dynamic_function( + Data, + <<"BitArray"/utf8>>, + fun gleam_stdlib:bit_array/1 + ). + +-file("src/gleam/dynamic/decode.gleam", 935). +?DOC( + " Replace all errors produced by a decoder with one single error for a named\n" + " expected type.\n" + "\n" + " This function may be useful if you wish to simplify errors before\n" + " presenting them to a user, particularly when using the `one_of` function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.string |> decode.collapse_errors(\"MyThing\")\n" + " let result = decode.run(dynamic.int(1000), decoder)\n" + " assert result == Error([DecodeError(\"MyThing\", \"Int\", [])])\n" + " ```\n" +). +-spec collapse_errors(decoder(CAC), binary()) -> decoder(CAC). +collapse_errors(Decoder, Name) -> + {decoder, + fun(Dynamic_data) -> + {Data, Errors} = Layer = (erlang:element(2, Decoder))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + {Data, decode_error(Name, Dynamic_data)} + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 1024). +?DOC( + " Define a decoder that always fails.\n" + "\n" + " The first parameter is a \"placeholder\" value, which is some default value that the\n" + " decoder uses internally in place of the value that would have been produced\n" + " if the decoder was successful. It doesn't matter what this value is, it is\n" + " never returned by the decoder or shown to the user, so pick some arbitrary\n" + " value. If it is an int you might pick `0`, if it is a list you might pick\n" + " `[]`.\n" + "\n" + " The second parameter is the name of the type that has failed to decode.\n" + "\n" + " ```gleam\n" + " decode.failure(User(name: \"\", score: 0, tags: []), expected: \"User\")\n" + " ```\n" +). +-spec failure(CAU, binary()) -> decoder(CAU). +failure(Placeholder, Name) -> + {decoder, fun(D) -> {Placeholder, decode_error(Name, D)} end}. + +-file("src/gleam/dynamic/decode.gleam", 1065). +?DOC( + " Create a decoder for a new data type from a decoding function.\n" + "\n" + " This function is used for new primitive types. For example, you might\n" + " define a decoder for Erlang's pid type.\n" + "\n" + " A default \"placeholder\" value is also required to make a decoder. When this\n" + " decoder is used as part of a larger decoder this placeholder value is used\n" + " so that the rest of the decoder can continue to run and\n" + " collect all decoding errors. It doesn't matter what this value is, it is\n" + " never returned by the decoder or shown to the user, so pick some arbitrary\n" + " value. If it is an int you might pick `0`, if it is a list you might pick\n" + " `[]`.\n" + "\n" + " If you were to make a decoder for the `Int` type (rather than using the\n" + " build-in `Int` decoder) you would define it like so:\n" + "\n" + " ```gleam\n" + " pub fn int_decoder() -> decode.Decoder(Int) {\n" + " let default = \"\"\n" + " decode.new_primitive_decoder(\"Int\", int_from_dynamic)\n" + " }\n" + "\n" + " @external(erlang, \"my_module\", \"int_from_dynamic\")\n" + " fn int_from_dynamic(data: Int) -> Result(Int, Int)\n" + " ```\n" + "\n" + " ```erlang\n" + " -module(my_module).\n" + " -export([int_from_dynamic/1]).\n" + "\n" + " int_from_dynamic(Data) ->\n" + " case is_integer(Data) of\n" + " true -> {ok, Data};\n" + " false -> {error, 0}\n" + " end.\n" + " ```\n" +). +-spec new_primitive_decoder( + binary(), + fun((gleam@dynamic:dynamic_()) -> {ok, CAW} | {error, CAW}) +) -> decoder(CAW). +new_primitive_decoder(Name, Decoding_function) -> + {decoder, fun(D) -> case Decoding_function(D) of + {ok, T} -> + {T, []}; + + {error, Placeholder} -> + {Placeholder, + [{decode_error, + Name, + gleam_stdlib:classify_dynamic(D), + []}]} + end end}. + +-file("src/gleam/dynamic/decode.gleam", 646). +-spec dynamic_string(gleam@dynamic:dynamic_()) -> {ok, binary()} | + {error, binary()}. +dynamic_string(Data) -> + case gleam_stdlib:bit_array(Data) of + {ok, Data@1} -> + case gleam@bit_array:to_string(Data@1) of + {ok, String} -> + {ok, String}; + + {error, _} -> + {error, <<""/utf8>>} + end; + + {error, _} -> + {error, <<""/utf8>>} + end. + +-file("src/gleam/dynamic/decode.gleam", 641). +-spec decode_string(gleam@dynamic:dynamic_()) -> {binary(), + list(decode_error())}. +decode_string(Data) -> + run_dynamic_function(Data, <<"String"/utf8>>, fun dynamic_string/1). + +-file("src/gleam/dynamic/decode.gleam", 457). +-spec path_segment_to_string(gleam@dynamic:dynamic_()) -> binary(). +path_segment_to_string(Key) -> + Decoder = one_of( + {decoder, fun decode_string/1}, + [begin + _pipe = {decoder, fun decode_int/1}, + map(_pipe, fun erlang:integer_to_binary/1) + end, + begin + _pipe@1 = {decoder, fun decode_float/1}, + map(_pipe@1, fun gleam_stdlib:float_to_string/1) + end] + ), + case run(Key, Decoder) of + {ok, Key@1} -> + Key@1; + + {error, _} -> + <<<<"<"/utf8, (gleam_stdlib:classify_dynamic(Key))/binary>>/binary, + ">"/utf8>> + end. + +-file("src/gleam/dynamic/decode.gleam", 831). +-spec fold_dict( + {gleam@dict:dict(BZB, BZC), list(decode_error())}, + gleam@dynamic:dynamic_(), + gleam@dynamic:dynamic_(), + fun((gleam@dynamic:dynamic_()) -> {BZB, list(decode_error())}), + fun((gleam@dynamic:dynamic_()) -> {BZC, list(decode_error())}) +) -> {gleam@dict:dict(BZB, BZC), list(decode_error())}. +fold_dict(Acc, Key, Value, Key_decoder, Value_decoder) -> + case Key_decoder(Key) of + {Key_decoded, []} -> + case Value_decoder(Value) of + {Value@1, []} -> + Dict = gleam@dict:insert( + erlang:element(1, Acc), + Key_decoded, + Value@1 + ), + {Dict, erlang:element(2, Acc)}; + + {_, Errors} -> + Key_identifier = path_segment_to_string(Key), + push_path({maps:new(), Errors}, [Key_identifier]) + end; + + {_, Errors@1} -> + push_path({maps:new(), Errors@1}, [<<"keys"/utf8>>]) + end. + +-file("src/gleam/dynamic/decode.gleam", 811). +?DOC( + " A decoder that decodes dicts where all keys and values are decoded with\n" + " given decoders.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let values = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.int(1)),\n" + " #(dynamic.string(\"two\"), dynamic.int(2)),\n" + " ])\n" + "\n" + " let result =\n" + " decode.run(values, decode.dict(decode.string, decode.int))\n" + " assert result == Ok(values)\n" + " ```\n" +). +-spec dict(decoder(BYU), decoder(BYW)) -> decoder(gleam@dict:dict(BYU, BYW)). +dict(Key, Value) -> + {decoder, fun(Data) -> case gleam_stdlib:dict(Data) of + {error, _} -> + {maps:new(), decode_error(<<"Dict"/utf8>>, Data)}; + + {ok, Dict} -> + gleam@dict:fold( + Dict, + {maps:new(), []}, + fun(A, K, V) -> case erlang:element(2, A) of + [] -> + fold_dict( + A, + K, + V, + erlang:element(2, Key), + erlang:element(2, Value) + ); + + [_ | _] -> + A + end end + ) + end end}. + +-file("src/gleam/dynamic/decode.gleam", 779). +?DOC( + " A decoder that decodes lists where all elements are decoded with a given\n" + " decoder.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let result =\n" + " [1, 2, 3]\n" + " |> list.map(dynamic.int)\n" + " |> dynamic.list\n" + " |> decode.run(decode.list(of: decode.int))\n" + " assert result == Ok([1, 2, 3])\n" + " ```\n" +). +-spec list(decoder(BYI)) -> decoder(list(BYI)). +list(Inner) -> + {decoder, + fun(Data) -> + gleam_stdlib:list( + Data, + erlang:element(2, Inner), + fun(P, K) -> push_path(P, [K]) end, + 0, + [] + ) + end}. + +-file("src/gleam/dynamic/decode.gleam", 445). +-spec push_path({BWR, list(decode_error())}, list(any())) -> {BWR, + list(decode_error())}. +push_path(Layer, Path) -> + Path@1 = gleam@list:map(Path, fun(Key) -> _pipe = Key, + _pipe@1 = gleam_stdlib:identity(_pipe), + path_segment_to_string(_pipe@1) end), + Errors = gleam@list:map( + erlang:element(2, Layer), + fun(Error) -> + {decode_error, + erlang:element(2, Error), + erlang:element(3, Error), + lists:append(Path@1, erlang:element(4, Error))} + end + ), + {erlang:element(1, Layer), Errors}. + +-file("src/gleam/dynamic/decode.gleam", 409). +-spec index( + list(BWF), + list(BWF), + fun((gleam@dynamic:dynamic_()) -> {BWI, list(decode_error())}), + gleam@dynamic:dynamic_(), + fun((gleam@dynamic:dynamic_(), list(BWF)) -> {BWI, list(decode_error())}) +) -> {BWI, list(decode_error())}. +index(Path, Position, Inner, Data, Handle_miss) -> + case Path of + [] -> + _pipe = Data, + _pipe@1 = Inner(_pipe), + push_path(_pipe@1, lists:reverse(Position)); + + [Key | Path@1] -> + case gleam_stdlib:index(Data, Key) of + {ok, {some, Data@1}} -> + index(Path@1, [Key | Position], Inner, Data@1, Handle_miss); + + {ok, none} -> + Handle_miss(Data, [Key | Position]); + + {error, Kind} -> + {Default, _} = Inner(Data), + _pipe@2 = {Default, + [{decode_error, + Kind, + gleam_stdlib:classify_dynamic(Data), + []}]}, + push_path(_pipe@2, lists:reverse(Position)) + end + end. + +-file("src/gleam/dynamic/decode.gleam", 332). +?DOC( + " The same as [`field`](#field), except taking a path to the value rather\n" + " than a field name.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"data\"), dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.subfield([\"data\", \"name\"], decode.string)\n" + " use email <- decode.subfield([\"data\", \"email\"], decode.string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" +). +-spec subfield(list(any()), decoder(BVQ), fun((BVQ) -> decoder(BVS))) -> decoder(BVS). +subfield(Field_path, Field_decoder, Next) -> + {decoder, + fun(Data) -> + {Out, Errors1} = index( + Field_path, + [], + erlang:element(2, Field_decoder), + Data, + fun(Data@1, Position) -> + {Default, _} = (erlang:element(2, Field_decoder))(Data@1), + _pipe = {Default, + [{decode_error, + <<"Field"/utf8>>, + <<"Nothing"/utf8>>, + []}]}, + push_path(_pipe, lists:reverse(Position)) + end + ), + {Out@1, Errors2} = (erlang:element(2, Next(Out)))(Data), + {Out@1, lists:append(Errors1, Errors2)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 399). +?DOC( + " A decoder that decodes a value that is nested within other values. For\n" + " example, decoding a value that is within some deeply nested JSON objects.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.at([\"one\", \"two\"], decode.int)\n" + "\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.properties([\n" + " #(dynamic.string(\"two\"), dynamic.int(1000)),\n" + " ]),\n" + " ])\n" + "\n" + " assert decode.run(data, decoder) == Ok(1000)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert dynamic.nil()\n" + " |> decode.run(decode.optional(decode.int))\n" + " == Ok(option.None)\n" + " ```\n" +). +-spec at(list(any()), decoder(BWC)) -> decoder(BWC). +at(Path, Inner) -> + {decoder, + fun(Data) -> + index( + Path, + [], + erlang:element(2, Inner), + Data, + fun(Data@1, Position) -> + {Default, _} = (erlang:element(2, Inner))(Data@1), + _pipe = {Default, + [{decode_error, + <<"Field"/utf8>>, + <<"Nothing"/utf8>>, + []}]}, + push_path(_pipe, lists:reverse(Position)) + end + ) + end}. + +-file("src/gleam/dynamic/decode.gleam", 534). +?DOC( + " Run a decoder on a field of a `Dynamic` value, decoding the value if it is\n" + " of the desired type, or returning errors. An error is returned if there is\n" + " no field for the specified key.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.field(\"email\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" + "\n" + " If you wish to decode a value that is more deeply nested within the dynamic\n" + " data, see [`subfield`](#subfield) and [`at`](#at).\n" + "\n" + " If you wish to return a default in the event that a field is not present,\n" + " see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at).\n" +). +-spec field(any(), decoder(BXA), fun((BXA) -> decoder(BXC))) -> decoder(BXC). +field(Field_name, Field_decoder, Next) -> + subfield([Field_name], Field_decoder, Next). + +-file("src/gleam/dynamic/decode.gleam", 567). +?DOC( + " Run a decoder on a field of a `Dynamic` value, decoding the value if it is\n" + " of the desired type, or returning errors. The given default value is\n" + " returned if there is no field for the specified key.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.optional_field(\"email\", \"n/a\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"n/a\"))\n" + " ```\n" +). +-spec optional_field(any(), BXG, decoder(BXG), fun((BXG) -> decoder(BXI))) -> decoder(BXI). +optional_field(Key, Default, Field_decoder, Next) -> + {decoder, + fun(Data) -> + {Out, Errors1} = begin + _pipe = case gleam_stdlib:index(Data, Key) of + {ok, {some, Data@1}} -> + (erlang:element(2, Field_decoder))(Data@1); + + {ok, none} -> + {Default, []}; + + {error, Kind} -> + {Default, + [{decode_error, + Kind, + gleam_stdlib:classify_dynamic(Data), + []}]} + end, + push_path(_pipe, [Key]) + end, + {Out@1, Errors2} = (erlang:element(2, Next(Out)))(Data), + {Out@1, lists:append(Errors1, Errors2)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 607). +?DOC( + " A decoder that decodes a value that is nested within other values. For\n" + " example, decoding a value that is within some deeply nested JSON objects.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.optionally_at([\"one\", \"two\"], 100, decode.int)\n" + "\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.properties([])),\n" + " ])\n" + "\n" + " assert decode.run(data, decoder) == Ok(100)\n" + " ```\n" +). +-spec optionally_at(list(any()), BXN, decoder(BXN)) -> decoder(BXN). +optionally_at(Path, Default, Inner) -> + {decoder, + fun(Data) -> + index( + Path, + [], + erlang:element(2, Inner), + Data, + fun(_, _) -> {Default, []} end + ) + end}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@float.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@float.erl new file mode 100644 index 0000000..33256a2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@float.erl @@ -0,0 +1,711 @@ +-module(gleam@float). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/float.gleam"). +-export([parse/1, to_string/1, compare/2, min/2, max/2, clamp/3, ceiling/1, floor/1, truncate/1, absolute_value/1, loosely_compare/3, loosely_equals/3, power/2, square_root/1, negate/1, round/1, to_precision/2, sum/1, product/1, random/0, modulo/2, divide/2, add/2, multiply/2, subtract/2, logarithm/1, exponential/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Functions for working with floats.\n" + "\n" + " ## Float representation\n" + "\n" + " Floats are represented as 64 bit floating point numbers on both the Erlang\n" + " and JavaScript runtimes. The floating point behaviour is native to their\n" + " respective runtimes, so their exact behaviour will be slightly different on\n" + " the two runtimes.\n" + "\n" + " ### Infinity and NaN\n" + "\n" + " Under the JavaScript runtime, exceeding the maximum (or minimum)\n" + " representable value for a floating point value will result in Infinity (or\n" + " -Infinity). Should you try to divide two infinities you will get NaN as a\n" + " result.\n" + "\n" + " When running on BEAM, exceeding the maximum (or minimum) representable\n" + " value for a floating point value will raise an error.\n" + "\n" + " ## Division by zero\n" + "\n" + " Gleam runs on the Erlang virtual machine, which does not follow the IEEE\n" + " 754 standard for floating point arithmetic and does not have an `Infinity`\n" + " value. In Erlang division by zero results in a crash, however Gleam does\n" + " not have partial functions and operators in core so instead division by zero\n" + " returns zero, a behaviour taken from Pony, Coq, and Lean.\n" + "\n" + " This may seem unexpected at first, but it is no less mathematically valid\n" + " than crashing or returning a special value. Division by zero is undefined\n" + " in mathematics.\n" +). + +-file("src/gleam/float.gleam", 49). +?DOC( + " Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was\n" + " not possible.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"2.3\") == Ok(2.3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert parse(\"ABC\") == Error(Nil)\n" + " ```\n" +). +-spec parse(binary()) -> {ok, float()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_float(String). + +-file("src/gleam/float.gleam", 61). +?DOC( + " Returns the string representation of the provided `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(2.3) == \"2.3\"\n" + " ```\n" +). +-spec to_string(float()) -> binary(). +to_string(X) -> + gleam_stdlib:float_to_string(X). + +-file("src/gleam/float.gleam", 100). +?DOC( + " Compares two `Float`s, returning an `Order`:\n" + " `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(2.0, 2.3) == Lt\n" + " ```\n" + "\n" + " To handle\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems)\n" + " you may use [`loosely_compare`](#loosely_compare) instead.\n" +). +-spec compare(float(), float()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/float.gleam", 177). +?DOC( + " Compares two `Float`s, returning the smaller of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert min(2.0, 2.3) == 2.0\n" + " ```\n" +). +-spec min(float(), float()) -> float(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/float.gleam", 192). +?DOC( + " Compares two `Float`s, returning the larger of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert max(2.0, 2.3) == 2.3\n" + " ```\n" +). +-spec max(float(), float()) -> float(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/float.gleam", 80). +?DOC( + " Restricts a float between two bounds.\n" + "\n" + " Note: If the `min` argument is larger than the `max` argument then they\n" + " will be swapped, so the minimum bound is always lower than the maximum\n" + " bound.\n" + "\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert clamp(1.2, min: 1.4, max: 1.6) == 1.4\n" + " ```\n" + "\n" + " ```gleam\n" + " assert clamp(1.2, min: 1.4, max: 0.6) == 1.2\n" + " ```\n" +). +-spec clamp(float(), float(), float()) -> float(). +clamp(X, Min_bound, Max_bound) -> + case Min_bound >= Max_bound of + true -> + _pipe = X, + _pipe@1 = min(_pipe, Min_bound), + max(_pipe@1, Max_bound); + + false -> + _pipe@2 = X, + _pipe@3 = min(_pipe@2, Max_bound), + max(_pipe@3, Min_bound) + end. + +-file("src/gleam/float.gleam", 209). +?DOC( + " Rounds the value to the next highest whole number as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert ceiling(2.3) == 3.0\n" + " ```\n" +). +-spec ceiling(float()) -> float(). +ceiling(X) -> + math:ceil(X). + +-file("src/gleam/float.gleam", 221). +?DOC( + " Rounds the value to the next lowest whole number as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert floor(2.3) == 2.0\n" + " ```\n" +). +-spec floor(float()) -> float(). +floor(X) -> + math:floor(X). + +-file("src/gleam/float.gleam", 256). +?DOC( + " Returns the value as an `Int`, truncating all decimal digits.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert truncate(2.4343434847383438) == 2\n" + " ```\n" +). +-spec truncate(float()) -> integer(). +truncate(X) -> + erlang:trunc(X). + +-file("src/gleam/float.gleam", 302). +?DOC( + " Returns the absolute value of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert absolute_value(-12.5) == 12.5\n" + " ```\n" + "\n" + " ```gleam\n" + " assert absolute_value(10.2) == 10.2\n" + " ```\n" +). +-spec absolute_value(float()) -> float(). +absolute_value(X) -> + case X >= +0.0 of + true -> + X; + + false -> + +0.0 - X + end. + +-file("src/gleam/float.gleam", 129). +?DOC( + " Compares two `Float`s within a tolerance, returning an `Order`:\n" + " `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n" + "\n" + " This function allows Float comparison while handling\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n" + "\n" + " Notice: For `Float`s the tolerance won't be exact:\n" + " `5.3 - 5.0` is not exactly `0.3`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert loosely_compare(5.0, with: 5.3, tolerating: 0.5) == Eq\n" + " ```\n" + "\n" + " If you want to check only for equality you may use\n" + " [`loosely_equals`](#loosely_equals) instead.\n" +). +-spec loosely_compare(float(), float(), float()) -> gleam@order:order(). +loosely_compare(A, B, Tolerance) -> + Difference = absolute_value(A - B), + case Difference =< Tolerance of + true -> + eq; + + false -> + compare(A, B) + end. + +-file("src/gleam/float.gleam", 160). +?DOC( + " Checks for equality of two `Float`s within a tolerance,\n" + " returning a `Bool`.\n" + "\n" + " This function allows Float comparison while handling\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n" + "\n" + " Notice: For `Float`s the tolerance won't be exact:\n" + " `5.3 - 5.0` is not exactly `0.3`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert loosely_equals(5.0, with: 5.3, tolerating: 0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !loosely_equals(5.0, with: 5.1, tolerating: 0.1)\n" + " ```\n" +). +-spec loosely_equals(float(), float(), float()) -> boolean(). +loosely_equals(A, B, Tolerance) -> + Difference = absolute_value(A - B), + Difference =< Tolerance. + +-file("src/gleam/float.gleam", 334). +?DOC( + " Returns the result of the base being raised to the power of the\n" + " exponent, as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert power(2.0, -1.0) == Ok(0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(2.0, 2.0) == Ok(4.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(8.0, 1.5) == Ok(22.627416997969522)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 4.0 |> power(of: 2.0) == Ok(16.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(-1.0, 0.5) == Error(Nil)\n" + " ```\n" +). +-spec power(float(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + Fractional = (math:ceil(Exponent) - Exponent) > +0.0, + case ((Base < +0.0) andalso Fractional) orelse ((Base =:= +0.0) andalso (Exponent + < +0.0)) of + true -> + {error, nil}; + + false -> + {ok, math:pow(Base, Exponent)} + end. + +-file("src/gleam/float.gleam", 364). +?DOC( + " Returns the square root of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert square_root(4.0) == Ok(2.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert square_root(-16.0) == Error(Nil)\n" + " ```\n" +). +-spec square_root(float()) -> {ok, float()} | {error, nil}. +square_root(X) -> + power(X, 0.5). + +-file("src/gleam/float.gleam", 376). +?DOC( + " Returns the negative of the value provided.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(1.0) == -1.0\n" + " ```\n" +). +-spec negate(float()) -> float(). +negate(X) -> + -1.0 * X. + +-file("src/gleam/float.gleam", 236). +?DOC( + " Rounds the value to the nearest whole number as an `Int`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert round(2.3) == 2\n" + " ```\n" + "\n" + " ```gleam\n" + " assert round(2.5) == 3\n" + " ```\n" +). +-spec round(float()) -> integer(). +round(X) -> + erlang:round(X). + +-file("src/gleam/float.gleam", 273). +?DOC( + " Converts the value to a given precision as a `Float`.\n" + " The precision is the number of allowed decimal places.\n" + " Negative precisions are allowed and force rounding\n" + " to the nearest tenth, hundredth, thousandth etc.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_precision(2.43434348473, 2) == 2.43\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_precision(547890.453444, -3) == 548000.0\n" + " ```\n" +). +-spec to_precision(float(), integer()) -> float(). +to_precision(X, Precision) -> + case Precision =< 0 of + true -> + Factor = math:pow(10.0, erlang:float(- Precision)), + erlang:float(erlang:round(case Factor of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> X / Gleam@denominator + end)) * Factor; + + false -> + Factor@1 = math:pow(10.0, erlang:float(Precision)), + case Factor@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator@1 -> erlang:float(erlang:round(X * Factor@1)) + / Gleam@denominator@1 + end + end. + +-file("src/gleam/float.gleam", 392). +-spec sum_loop(list(float()), float()) -> float(). +sum_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + sum_loop(Rest, First + Initial); + + [] -> + Initial + end. + +-file("src/gleam/float.gleam", 388). +?DOC( + " Sums a list of `Float`s.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert sum([1.0, 2.2, 3.3]) == 6.5\n" + " ```\n" +). +-spec sum(list(float())) -> float(). +sum(Numbers) -> + sum_loop(Numbers, +0.0). + +-file("src/gleam/float.gleam", 411). +-spec product_loop(list(float()), float()) -> float(). +product_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + product_loop(Rest, First * Initial); + + [] -> + Initial + end. + +-file("src/gleam/float.gleam", 407). +?DOC( + " Multiplies a list of `Float`s and returns the product.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert product([2.5, 3.2, 4.2]) == 33.6\n" + " ```\n" +). +-spec product(list(float())) -> float(). +product(Numbers) -> + product_loop(Numbers, 1.0). + +-file("src/gleam/float.gleam", 433). +?DOC( + " Generates a random float between the given zero (inclusive) and one\n" + " (exclusive).\n" + "\n" + " On Erlang this updates the random state in the process dictionary.\n" + " See: \n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " random()\n" + " // -> 0.646355926896028\n" + " ```\n" +). +-spec random() -> float(). +random() -> + rand:uniform(). + +-file("src/gleam/float.gleam", 460). +?DOC( + " Computes the modulo of a float division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " The computed value will always have the same sign as the `divisor`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert modulo(13.3, by: 3.3) == Ok(0.1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13.3, by: 3.3) == Ok(3.2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13.3, by: -3.3) == Ok(-3.2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13.3, by: -3.3) == Ok(-0.1)\n" + " ```\n" +). +-spec modulo(float(), float()) -> {ok, float()} | {error, nil}. +modulo(Dividend, Divisor) -> + case Divisor of + +0.0 -> + {error, nil}; + + _ -> + {ok, Dividend - (math:floor(case Divisor of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> Dividend / Gleam@denominator + end) * Divisor)} + end. + +-file("src/gleam/float.gleam", 479). +?DOC( + " Returns division of the inputs as a `Result`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert divide(0.0, 1.0) == Ok(0.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(1.0, 0.0) == Error(Nil)\n" + " ```\n" +). +-spec divide(float(), float()) -> {ok, float()} | {error, nil}. +divide(A, B) -> + case B of + +0.0 -> + {error, nil}; + + B@1 -> + {ok, case B@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> A / Gleam@denominator + end} + end. + +-file("src/gleam/float.gleam", 507). +?DOC( + " Adds two floats together.\n" + "\n" + " It's the function equivalent of the `+.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert add(1.0, 2.0) == 3.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1.0, 2.0, 3.0], 0.0, add) == 6.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> add(2.0) == 5.0\n" + " ```\n" +). +-spec add(float(), float()) -> float(). +add(A, B) -> + A + B. + +-file("src/gleam/float.gleam", 532). +?DOC( + " Multiplies two floats together.\n" + "\n" + " It's the function equivalent of the `*.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert multiply(2.0, 4.0) == 8.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([2.0, 3.0, 4.0], 1.0, multiply) == 24.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> multiply(2.0) == 6.0\n" + " ```\n" +). +-spec multiply(float(), float()) -> float(). +multiply(A, B) -> + A * B. + +-file("src/gleam/float.gleam", 561). +?DOC( + " Subtracts one float from another.\n" + "\n" + " It's the function equivalent of the `-.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert subtract(3.0, 1.0) == 2.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1.0, 2.0, 3.0], 10.0, subtract) == 4.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> subtract(_, 2.0) == 1.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> subtract(2.0, _) == -1.0\n" + " ```\n" +). +-spec subtract(float(), float()) -> float(). +subtract(A, B) -> + A - B. + +-file("src/gleam/float.gleam", 586). +?DOC( + " Returns the natural logarithm (base e) of the given `Float` as a `Result`. If the\n" + " input is less than or equal to 0, returns `Error(Nil)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert logarithm(1.0) == Ok(0.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(2.718281828459045) == Ok(1.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(0.0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(-1.0) == Error(Nil)\n" + " ```\n" +). +-spec logarithm(float()) -> {ok, float()} | {error, nil}. +logarithm(X) -> + case X =< +0.0 of + true -> + {error, nil}; + + false -> + {ok, math:log(X)} + end. + +-file("src/gleam/float.gleam", 621). +?DOC( + " Returns e (Euler's number) raised to the power of the given exponent, as\n" + " a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert exponential(0.0) == Ok(1.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exponential(1.0) == Ok(2.718281828459045)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exponential(-1.0) == Ok(0.36787944117144233)\n" + " ```\n" +). +-spec exponential(float()) -> float(). +exponential(X) -> + math:exp(X). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@function.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@function.erl new file mode 100644 index 0000000..63fff58 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@function.erl @@ -0,0 +1,18 @@ +-module(gleam@function). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/function.gleam"). +-export([identity/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/function.gleam", 3). +?DOC(" Takes a single argument and always returns its input value.\n"). +-spec identity(CLV) -> CLV. +identity(X) -> + X. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@int.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@int.erl new file mode 100644 index 0000000..1d5306a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@int.erl @@ -0,0 +1,972 @@ +-module(gleam@int). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/int.gleam"). +-export([absolute_value/1, parse/1, base_parse/2, to_string/1, to_base_string/2, to_base2/1, to_base8/1, to_base16/1, to_base36/1, to_float/1, power/2, square_root/1, compare/2, min/2, max/2, clamp/3, is_even/1, is_odd/1, negate/1, sum/1, product/1, random/1, divide/2, remainder/2, modulo/2, floor_divide/2, add/2, multiply/2, subtract/2, bitwise_and/2, bitwise_not/1, bitwise_or/2, bitwise_exclusive_or/2, bitwise_shift_left/2, bitwise_shift_right/2, range/4]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Functions for working with integers.\n" + "\n" + " ## Division by zero\n" + "\n" + " In Erlang division by zero results in a crash, however Gleam does not have\n" + " partial functions and operators in core so instead division by zero returns\n" + " zero, a behaviour taken from Pony, Coq, and Lean.\n" + "\n" + " This may seem unexpected at first, but it is no less mathematically valid\n" + " than crashing or returning a special value. Division by zero is undefined\n" + " in mathematics.\n" +). + +-file("src/gleam/int.gleam", 28). +?DOC( + " Returns the absolute value of the input.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert absolute_value(-12) == 12\n" + " ```\n" + "\n" + " ```gleam\n" + " assert absolute_value(10) == 10\n" + " ```\n" +). +-spec absolute_value(integer()) -> integer(). +absolute_value(X) -> + case X >= 0 of + true -> + X; + + false -> + X * -1 + end. + +-file("src/gleam/int.gleam", 98). +?DOC( + " Parses a given string as an int if possible.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"2\") == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert parse(\"ABC\") == Error(Nil)\n" + " ```\n" +). +-spec parse(binary()) -> {ok, integer()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_int(String). + +-file("src/gleam/int.gleam", 125). +?DOC( + " Parses a given string as an int in a given base if possible.\n" + " Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert base_parse(\"10\", 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"30\", 16) == Ok(48)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"1C\", 36) == Ok(48)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"48\", 1) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"48\", 37) == Error(Nil)\n" + " ```\n" +). +-spec base_parse(binary(), integer()) -> {ok, integer()} | {error, nil}. +base_parse(String, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + gleam_stdlib:int_from_base_string(String, Base); + + false -> + {error, nil} + end. + +-file("src/gleam/int.gleam", 146). +?DOC( + " Prints a given int to a string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(2) == \"2\"\n" + " ```\n" +). +-spec to_string(integer()) -> binary(). +to_string(X) -> + erlang:integer_to_binary(X). + +-file("src/gleam/int.gleam", 174). +?DOC( + " Prints a given int to a string using the base number provided.\n" + " Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n" + " For common bases (2, 8, 16, 36), use the `to_baseN` functions.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base_string(2, 2) == Ok(\"10\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 16) == Ok(\"30\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 36) == Ok(\"1C\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 1) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 37) == Error(Nil)\n" + " ```\n" +). +-spec to_base_string(integer(), integer()) -> {ok, binary()} | {error, nil}. +to_base_string(X, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + {ok, erlang:integer_to_binary(X, Base)}; + + false -> + {error, nil} + end. + +-file("src/gleam/int.gleam", 193). +?DOC( + " Prints a given int to a string using base-2.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base2(2) == \"10\"\n" + " ```\n" +). +-spec to_base2(integer()) -> binary(). +to_base2(X) -> + erlang:integer_to_binary(X, 2). + +-file("src/gleam/int.gleam", 205). +?DOC( + " Prints a given int to a string using base-8.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base8(15) == \"17\"\n" + " ```\n" +). +-spec to_base8(integer()) -> binary(). +to_base8(X) -> + erlang:integer_to_binary(X, 8). + +-file("src/gleam/int.gleam", 217). +?DOC( + " Prints a given int to a string using base-16.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base16(48) == \"30\"\n" + " ```\n" +). +-spec to_base16(integer()) -> binary(). +to_base16(X) -> + erlang:integer_to_binary(X, 16). + +-file("src/gleam/int.gleam", 229). +?DOC( + " Prints a given int to a string using base-36.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base36(48) == \"1C\"\n" + " ```\n" +). +-spec to_base36(integer()) -> binary(). +to_base36(X) -> + erlang:integer_to_binary(X, 36). + +-file("src/gleam/int.gleam", 251). +?DOC( + " Takes an int and returns its value as a float.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_float(5) == 5.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_float(0) == 0.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_float(-3) == -3.0\n" + " ```\n" +). +-spec to_float(integer()) -> float(). +to_float(X) -> + erlang:float(X). + +-file("src/gleam/int.gleam", 60). +?DOC( + " Returns the result of the base being raised to the power of the\n" + " exponent, as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert power(2, -1.0) == Ok(0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(2, 2.0) == Ok(4.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(8, 1.5) == Ok(22.627416997969522)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 4 |> power(of: 2.0) == Ok(16.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(-1, 0.5) == Error(Nil)\n" + " ```\n" +). +-spec power(integer(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + _pipe = Base, + _pipe@1 = erlang:float(_pipe), + gleam@float:power(_pipe@1, Exponent). + +-file("src/gleam/int.gleam", 78). +?DOC( + " Returns the square root of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert square_root(4) == Ok(2.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert square_root(-16) == Error(Nil)\n" + " ```\n" +). +-spec square_root(integer()) -> {ok, float()} | {error, nil}. +square_root(X) -> + _pipe = X, + _pipe@1 = erlang:float(_pipe), + gleam@float:square_root(_pipe@1). + +-file("src/gleam/int.gleam", 292). +?DOC( + " Compares two ints, returning an order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(2, 3) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(4, 3) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(3, 3) == Eq\n" + " ```\n" +). +-spec compare(integer(), integer()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/int.gleam", 311). +?DOC( + " Compares two ints, returning the smaller of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert min(2, 3) == 2\n" + " ```\n" +). +-spec min(integer(), integer()) -> integer(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/int.gleam", 326). +?DOC( + " Compares two ints, returning the larger of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert max(2, 3) == 3\n" + " ```\n" +). +-spec max(integer(), integer()) -> integer(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/int.gleam", 269). +?DOC( + " Restricts an int between two bounds.\n" + "\n" + " Note: If the `min` argument is larger than the `max` argument then they\n" + " will be swapped, so the minimum bound is always lower than the maximum\n" + " bound.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert clamp(40, min: 50, max: 60) == 50\n" + " ```\n" + "\n" + " ```gleam\n" + " assert clamp(40, min: 50, max: 30) == 40\n" + " ```\n" +). +-spec clamp(integer(), integer(), integer()) -> integer(). +clamp(X, Min_bound, Max_bound) -> + case Min_bound >= Max_bound of + true -> + _pipe = X, + _pipe@1 = min(_pipe, Min_bound), + max(_pipe@1, Max_bound); + + false -> + _pipe@2 = X, + _pipe@3 = min(_pipe@2, Max_bound), + max(_pipe@3, Min_bound) + end. + +-file("src/gleam/int.gleam", 345). +?DOC( + " Returns whether the value provided is even.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_even(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_even(3)\n" + " ```\n" +). +-spec is_even(integer()) -> boolean(). +is_even(X) -> + (X rem 2) =:= 0. + +-file("src/gleam/int.gleam", 361). +?DOC( + " Returns whether the value provided is odd.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_odd(3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_odd(2)\n" + " ```\n" +). +-spec is_odd(integer()) -> boolean(). +is_odd(X) -> + (X rem 2) /= 0. + +-file("src/gleam/int.gleam", 373). +?DOC( + " Returns the negative of the value provided.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(1) == -1\n" + " ```\n" +). +-spec negate(integer()) -> integer(). +negate(X) -> + -1 * X. + +-file("src/gleam/int.gleam", 389). +-spec sum_loop(list(integer()), integer()) -> integer(). +sum_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + sum_loop(Rest, First + Initial); + + [] -> + Initial + end. + +-file("src/gleam/int.gleam", 385). +?DOC( + " Sums a list of ints.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert sum([1, 2, 3]) == 6\n" + " ```\n" +). +-spec sum(list(integer())) -> integer(). +sum(Numbers) -> + sum_loop(Numbers, 0). + +-file("src/gleam/int.gleam", 408). +-spec product_loop(list(integer()), integer()) -> integer(). +product_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + product_loop(Rest, First * Initial); + + [] -> + Initial + end. + +-file("src/gleam/int.gleam", 404). +?DOC( + " Multiplies a list of ints and returns the product.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert product([2, 3, 4]) == 24\n" + " ```\n" +). +-spec product(list(integer())) -> integer(). +product(Numbers) -> + product_loop(Numbers, 1). + +-file("src/gleam/int.gleam", 436). +?DOC( + " Generates a random int between zero and the given maximum.\n" + "\n" + " The lower number is inclusive, the upper number is exclusive.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " random(10)\n" + " // -> 4\n" + " ```\n" + "\n" + " ```gleam\n" + " random(1)\n" + " // -> 0\n" + " ```\n" + "\n" + " ```gleam\n" + " random(-1)\n" + " // -> -1\n" + " ```\n" +). +-spec random(integer()) -> integer(). +random(Max) -> + _pipe = (rand:uniform() * erlang:float(Max)), + _pipe@1 = math:floor(_pipe), + erlang:round(_pipe@1). + +-file("src/gleam/int.gleam", 465). +?DOC( + " Performs a truncated integer division.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert divide(0, 1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(5, 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(-99, 2) == Ok(-49)\n" + " ```\n" +). +-spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend div Gleam@denominator + end} + end. + +-file("src/gleam/int.gleam", 510). +?DOC( + " Computes the remainder of an integer division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " Most of the time you will want to use the `%` operator instead of this\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remainder(3, 2) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(10, -1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(13, by: 3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(-13, by: 3) == Ok(-1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(13, by: -3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(-13, by: -3) == Ok(-1)\n" + " ```\n" +). +-spec remainder(integer(), integer()) -> {ok, integer()} | {error, nil}. +remainder(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end} + end. + +-file("src/gleam/int.gleam", 551). +?DOC( + " Computes the modulo of an integer division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " Note that this is different from `int.remainder` and `%` in that the\n" + " computed value will always have the same sign as the `divisor`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert modulo(3, 2) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(10, -1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13, by: 3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13, by: 3) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13, by: -3) == Ok(-2)\n" + " ```\n" +). +-spec modulo(integer(), integer()) -> {ok, integer()} | {error, nil}. +modulo(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + _ -> + Remainder = case Divisor of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end, + case (Remainder * Divisor) < 0 of + true -> + {ok, Remainder + Divisor}; + + false -> + {ok, Remainder} + end + end. + +-file("src/gleam/int.gleam", 591). +?DOC( + " Performs a *floored* integer division, which means that the result will\n" + " always be rounded towards negative infinity.\n" + "\n" + " If you want to perform truncated integer division (rounding towards zero),\n" + " use `int.divide()` or the `/` operator instead.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert floor_divide(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(5, 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(6, -4) == Ok(-2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(-99, 2) == Ok(-50)\n" + " ```\n" +). +-spec floor_divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +floor_divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + case ((Dividend * Divisor@1) < 0) andalso ((case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end) /= 0) of + true -> + {ok, (case Divisor@1 of + 0 -> 0; + Gleam@denominator@1 -> Dividend div Gleam@denominator@1 + end) - 1}; + + false -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator@2 -> Dividend div Gleam@denominator@2 + end} + end + end. + +-file("src/gleam/int.gleam", 622). +?DOC( + " Adds two integers together.\n" + "\n" + " It's the function equivalent of the `+` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert add(1, 2) == 3\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + " assert list.fold([1, 2, 3], 0, add) == 6\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> add(2) == 5\n" + " ```\n" +). +-spec add(integer(), integer()) -> integer(). +add(A, B) -> + A + B. + +-file("src/gleam/int.gleam", 647). +?DOC( + " Multiplies two integers together.\n" + "\n" + " It's the function equivalent of the `*` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert multiply(2, 4) == 8\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([2, 3, 4], 1, multiply) == 24\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> multiply(2) == 6\n" + " ```\n" +). +-spec multiply(integer(), integer()) -> integer(). +multiply(A, B) -> + A * B. + +-file("src/gleam/int.gleam", 676). +?DOC( + " Subtracts one int from another.\n" + "\n" + " It's the function equivalent of the `-` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert subtract(3, 1) == 2\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1, 2, 3], 10, subtract) == 4\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> subtract(2) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> subtract(2, _) == -1\n" + " ```\n" +). +-spec subtract(integer(), integer()) -> integer(). +subtract(A, B) -> + A - B. + +-file("src/gleam/int.gleam", 695). +?DOC( + " Calculates the bitwise AND of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_and(integer(), integer()) -> integer(). +bitwise_and(X, Y) -> + erlang:'band'(X, Y). + +-file("src/gleam/int.gleam", 712). +?DOC( + " Calculates the bitwise NOT of its argument.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_not(integer()) -> integer(). +bitwise_not(X) -> + erlang:'bnot'(X). + +-file("src/gleam/int.gleam", 729). +?DOC( + " Calculates the bitwise OR of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_or(integer(), integer()) -> integer(). +bitwise_or(X, Y) -> + erlang:'bor'(X, Y). + +-file("src/gleam/int.gleam", 746). +?DOC( + " Calculates the bitwise XOR of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_exclusive_or(integer(), integer()) -> integer(). +bitwise_exclusive_or(X, Y) -> + erlang:'bxor'(X, Y). + +-file("src/gleam/int.gleam", 763). +?DOC( + " Calculates the result of an arithmetic left bitshift.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_shift_left(integer(), integer()) -> integer(). +bitwise_shift_left(X, Y) -> + erlang:'bsl'(X, Y). + +-file("src/gleam/int.gleam", 780). +?DOC( + " Calculates the result of an arithmetic right bitshift.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_shift_right(integer(), integer()) -> integer(). +bitwise_shift_right(X, Y) -> + erlang:'bsr'(X, Y). + +-file("src/gleam/int.gleam", 813). +-spec range_loop( + integer(), + integer(), + integer(), + CM, + fun((CM, integer()) -> CM) +) -> CM. +range_loop(Current, Stop, Increment, Acc, Reducer) -> + case Current =:= Stop of + true -> + Acc; + + false -> + Acc@1 = Reducer(Acc, Current), + Current@1 = Current + Increment, + range_loop(Current@1, Stop, Increment, Acc@1, Reducer) + end. + +-file("src/gleam/int.gleam", 800). +?DOC( + " Run a function for each int between ints `from` and `to`.\n" + "\n" + " `from` is inclusive, and `to` is exclusive.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " range(from: 0, to: 3, with: \"\", run: fn(acc, i) {\n" + " acc <> to_string(i)\n" + " })\n" + " == \"012\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert range(from: 1, to: -2, with: [], run: list.prepend) == [-1, 0, 1]\n" + " ```\n" +). +-spec range(integer(), integer(), CL, fun((CL, integer()) -> CL)) -> CL. +range(Start, Stop, Acc, Reducer) -> + Increment = case Start < Stop of + true -> + 1; + + false -> + -1 + end, + range_loop(Start, Stop, Increment, Acc, Reducer). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@io.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@io.erl new file mode 100644 index 0000000..4b68e26 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@io.erl @@ -0,0 +1,76 @@ +-module(gleam@io). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/io.gleam"). +-export([print/1, print_error/1, println/1, println_error/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/io.gleam", 14). +?DOC( + " Writes a string to standard output (stdout).\n" + "\n" + " If you want your output to be printed on its own line see `println`.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.print(\"Hi mum\") == Nil\n" + " // Hi mum\n" + " ```\n" +). +-spec print(binary()) -> nil. +print(String) -> + gleam_stdlib:print(String). + +-file("src/gleam/io.gleam", 29). +?DOC( + " Writes a string to standard error (stderr).\n" + "\n" + " If you want your output to be printed on its own line see `println_error`.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.print_error(\"Hi pop\") == Nil\n" + " // Hi pop\n" + " ```\n" +). +-spec print_error(binary()) -> nil. +print_error(String) -> + gleam_stdlib:print_error(String). + +-file("src/gleam/io.gleam", 42). +?DOC( + " Writes a string to standard output (stdout), appending a newline to the end.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.println(\"Hi mum\") == Nil\n" + " // Hi mum\n" + " ```\n" +). +-spec println(binary()) -> nil. +println(String) -> + gleam_stdlib:println(String). + +-file("src/gleam/io.gleam", 55). +?DOC( + " Writes a string to standard error (stderr), appending a newline to the end.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.println_error(\"Hi pop\") == Nil\n" + " // Hi pop\n" + " ```\n" +). +-spec println_error(binary()) -> nil. +println_error(String) -> + gleam_stdlib:println_error(String). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@list.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@list.erl new file mode 100644 index 0000000..de82548 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@list.erl @@ -0,0 +1,2716 @@ +-module(gleam@list). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/list.gleam"). +-export([length/1, count/2, reverse/1, is_empty/1, contains/2, first/1, rest/1, group/2, filter/2, filter_map/2, map/2, map2/3, map_fold/3, index_map/2, try_map/2, drop/2, take/2, new/0, wrap/1, append/2, prepend/2, flatten/1, flat_map/2, fold/3, fold_right/3, index_fold/3, try_fold/3, fold_until/3, find/2, find_map/2, all/2, any/2, zip/2, strict_zip/2, unzip/1, intersperse/2, unique/1, sort/2, repeat/2, split/2, split_while/2, key_find/2, key_filter/2, key_pop/2, key_set/3, each/2, try_each/2, partition/2, window/2, window_by_2/1, drop_while/2, take_while/2, chunk/2, sized_chunk/2, reduce/2, scan/3, last/1, combinations/2, combination_pairs/1, transpose/1, interleave/1, shuffle/1, max/2, sample/2, permutations/1]). +-export_type([continue_or_stop/1, sorting/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Lists are an ordered sequence of elements and are one of the most common\n" + " data types in Gleam.\n" + "\n" + " New elements can be added and removed from the front of a list in\n" + " constant time, while adding and removing from the end requires traversing\n" + " and copying the whole list, so keep this in mind when designing your\n" + " programs.\n" + "\n" + " There is a dedicated syntax for prefixing to a list:\n" + "\n" + " ```gleam\n" + " let new_list = [1, 2, ..existing_list]\n" + " ```\n" + "\n" + " And a matching syntax for getting the first elements of a list:\n" + "\n" + " ```gleam\n" + " case list {\n" + " [first_element, ..rest] -> first_element\n" + " _ -> \"this pattern matches when the list is empty\"\n" + " }\n" + " ```\n" + "\n" +). + +-type continue_or_stop(AAE) :: {continue, AAE} | {stop, AAE}. + +-type sorting() :: ascending | descending. + +-file("src/gleam/list.gleam", 57). +-spec length_loop(list(any()), integer()) -> integer(). +length_loop(List, Count) -> + case List of + [_ | List@1] -> + length_loop(List@1, Count + 1); + + [] -> + Count + end. + +-file("src/gleam/list.gleam", 53). +?DOC( + " Counts the number of elements in a given list.\n" + "\n" + " This function has to traverse the list to determine the number of elements,\n" + " so it runs in linear time.\n" + "\n" + " This function is natively implemented by the virtual machine and is highly\n" + " optimised.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert length([]) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length([1]) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length([1, 2]) == 2\n" + " ```\n" +). +-spec length(list(any())) -> integer(). +length(List) -> + erlang:length(List). + +-file("src/gleam/list.gleam", 87). +-spec count_loop(list(AAL), fun((AAL) -> boolean()), integer()) -> integer(). +count_loop(List, Predicate, Acc) -> + case List of + [] -> + Acc; + + [First | Rest] -> + case Predicate(First) of + true -> + count_loop(Rest, Predicate, Acc + 1); + + false -> + count_loop(Rest, Predicate, Acc) + end + end. + +-file("src/gleam/list.gleam", 83). +?DOC( + " Counts the number of elements in a given list satisfying a given predicate.\n" + "\n" + " This function has to traverse the list to determine the number of elements,\n" + " so it runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert count([], fn(a) { a > 0 }) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert count([1], fn(a) { a > 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert count([1, 2, 3], int.is_odd) == 2\n" + " ```\n" +). +-spec count(list(AAJ), fun((AAJ) -> boolean())) -> integer(). +count(List, Predicate) -> + count_loop(List, Predicate, 0). + +-file("src/gleam/list.gleam", 122). +?DOC( + " Creates a new list from a given list containing the same elements but in the\n" + " opposite order.\n" + "\n" + " This function has to traverse the list to create the new reversed list, so\n" + " it runs in linear time.\n" + "\n" + " This function is natively implemented by the virtual machine and is highly\n" + " optimised.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert reverse([]) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert reverse([1]) == [1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert reverse([1, 2]) == [2, 1]\n" + " ```\n" +). +-spec reverse(list(AAN)) -> list(AAN). +reverse(List) -> + lists:reverse(List). + +-file("src/gleam/list.gleam", 156). +?DOC( + " Determines whether or not the list is empty.\n" + "\n" + " This function runs in constant time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_empty([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty([1])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty([1, 1])\n" + " ```\n" +). +-spec is_empty(list(any())) -> boolean(). +is_empty(List) -> + List =:= []. + +-file("src/gleam/list.gleam", 187). +?DOC( + " Determines whether or not a given element exists within a given list.\n" + "\n" + " This function traverses the list to find the element, so it runs in linear\n" + " time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !contains([], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [0] |> contains(any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains([1], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains([1, 1], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 0] |> contains(any: 0)\n" + " ```\n" +). +-spec contains(list(AAW), AAW) -> boolean(). +contains(List, Elem) -> + case List of + [] -> + false; + + [First | _] when First =:= Elem -> + true; + + [_ | Rest] -> + contains(Rest, Elem) + end. + +-file("src/gleam/list.gleam", 211). +?DOC( + " Gets the first element from the start of the list, if there is one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first([0]) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first([1, 2]) == Ok(1)\n" + " ```\n" +). +-spec first(list(AAY)) -> {ok, AAY} | {error, nil}. +first(List) -> + case List of + [] -> + {error, nil}; + + [First | _] -> + {ok, First} + end. + +-file("src/gleam/list.gleam", 237). +?DOC( + " Returns the list minus the first element. If the list is empty, `Error(Nil)` is\n" + " returned.\n" + "\n" + " This function runs in constant time and does not make a copy of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert rest([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert rest([0]) == Ok([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert rest([1, 2]) == Ok([2])\n" + " ```\n" +). +-spec rest(list(ABC)) -> {ok, list(ABC)} | {error, nil}. +rest(List) -> + case List of + [] -> + {error, nil}; + + [_ | Rest] -> + {ok, Rest} + end. + +-file("src/gleam/list.gleam", 276). +?DOC( + " Groups the elements from the given list by the given key function.\n" + "\n" + " Does not preserve the initial value order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/dict\n" + "\n" + " assert\n" + " [Ok(3), Error(\"Wrong\"), Ok(200), Ok(73)]\n" + " |> group(by: fn(i) {\n" + " case i {\n" + " Ok(_) -> \"Successful\"\n" + " Error(_) -> \"Failed\"\n" + " }\n" + " })\n" + " |> dict.to_list\n" + " == [\n" + " #(\"Failed\", [Error(\"Wrong\")]),\n" + " #(\"Successful\", [Ok(73), Ok(200), Ok(3)])\n" + " ]\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/dict\n" + "\n" + " assert group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 })\n" + " |> dict.to_list\n" + " == [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])]\n" + " ```\n" +). +-spec group(list(ABH), fun((ABH) -> ABJ)) -> gleam@dict:dict(ABJ, list(ABH)). +group(List, Key) -> + gleam@dict:group(Key, List). + +-file("src/gleam/list.gleam", 297). +-spec filter_loop(list(ABQ), fun((ABQ) -> boolean()), list(ABQ)) -> list(ABQ). +filter_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + New_acc = case Fun(First) of + true -> + [First | Acc]; + + false -> + Acc + end, + filter_loop(Rest, Fun, New_acc) + end. + +-file("src/gleam/list.gleam", 293). +?DOC( + " Returns a new list containing only the elements from the first list for\n" + " which the given functions returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert filter([2, 4, 6, 1], fn(x) { x > 2 }) == [4, 6]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert filter([2, 4, 6, 1], fn(x) { x > 6 }) == []\n" + " ```\n" +). +-spec filter(list(ABN), fun((ABN) -> boolean())) -> list(ABN). +filter(List, Predicate) -> + filter_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 327). +-spec filter_map_loop( + list(ACB), + fun((ACB) -> {ok, ACD} | {error, any()}), + list(ACD) +) -> list(ACD). +filter_map_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + New_acc = case Fun(First) of + {ok, First@1} -> + [First@1 | Acc]; + + {error, _} -> + Acc + end, + filter_map_loop(Rest, Fun, New_acc) + end. + +-file("src/gleam/list.gleam", 323). +?DOC( + " Returns a new list containing only the elements from the first list for\n" + " which the given functions returns `Ok(_)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert filter_map([2, 4, 6, 1], Error) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) == [3, 5, 7, 2]\n" + " ```\n" +). +-spec filter_map(list(ABU), fun((ABU) -> {ok, ABW} | {error, any()})) -> list(ABW). +filter_map(List, Fun) -> + filter_map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 356). +-spec map_loop(list(ACN), fun((ACN) -> ACP), list(ACP)) -> list(ACP). +map_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + map_loop(Rest, Fun, [Fun(First) | Acc]) + end. + +-file("src/gleam/list.gleam", 352). +?DOC( + " Returns a new list containing the results of applying the supplied function to each element.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map([2, 4, 6], fn(x) { x * 2 }) == [4, 8, 12]\n" + " ```\n" +). +-spec map(list(ACJ), fun((ACJ) -> ACL)) -> list(ACL). +map(List, Fun) -> + map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 382). +-spec map2_loop(list(ACY), list(ADA), fun((ACY, ADA) -> ADC), list(ADC)) -> list(ADC). +map2_loop(List1, List2, Fun, Acc) -> + case {List1, List2} of + {[], _} -> + lists:reverse(Acc); + + {_, []} -> + lists:reverse(Acc); + + {[A | As_], [B | Bs]} -> + map2_loop(As_, Bs, Fun, [Fun(A, B) | Acc]) + end. + +-file("src/gleam/list.gleam", 378). +?DOC( + " Combines two lists into a single list using the given function.\n" + "\n" + " If a list is longer than the other, the extra elements are dropped.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map2([1, 2, 3], [4, 5, 6], fn(x, y) { x + y }) == [5, 7, 9]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map2([1, 2], [\"a\", \"b\", \"c\"], fn(i, x) { #(i, x) })\n" + " == [#(1, \"a\"), #(2, \"b\")]\n" + " ```\n" +). +-spec map2(list(ACS), list(ACU), fun((ACS, ACU) -> ACW)) -> list(ACW). +map2(List1, List2, Fun) -> + map2_loop(List1, List2, Fun, []). + +-file("src/gleam/list.gleam", 416). +-spec map_fold_loop(list(ADK), fun((ADM, ADK) -> {ADM, ADN}), ADM, list(ADN)) -> {ADM, + list(ADN)}. +map_fold_loop(List, Fun, Acc, List_acc) -> + case List of + [] -> + {Acc, lists:reverse(List_acc)}; + + [First | Rest] -> + {Acc@1, First@1} = Fun(Acc, First), + map_fold_loop(Rest, Fun, Acc@1, [First@1 | List_acc]) + end. + +-file("src/gleam/list.gleam", 408). +?DOC( + " Similar to `map` but also lets you pass around an accumulated value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " map_fold(\n" + " over: [1, 2, 3],\n" + " from: 100,\n" + " with: fn(memo, i) { #(memo + i, i * 2) }\n" + " )\n" + " == #(106, [2, 4, 6])\n" + " ```\n" +). +-spec map_fold(list(ADF), ADH, fun((ADH, ADF) -> {ADH, ADI})) -> {ADH, + list(ADI)}. +map_fold(List, Initial, Fun) -> + map_fold_loop(List, Fun, Initial, []). + +-file("src/gleam/list.gleam", 447). +-spec index_map_loop( + list(ADU), + fun((ADU, integer()) -> ADW), + integer(), + list(ADW) +) -> list(ADW). +index_map_loop(List, Fun, Index, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + Acc@1 = [Fun(First, Index) | Acc], + index_map_loop(Rest, Fun, Index + 1, Acc@1) + end. + +-file("src/gleam/list.gleam", 443). +?DOC( + " Similar to `map`, but the supplied function will also be passed the index\n" + " of the element being mapped as an additional argument.\n" + "\n" + " The index starts at 0, so the first element is 0, the second is 1, and so\n" + " on.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert index_map([\"a\", \"b\"], fn(x, i) { #(i, x) }) == [#(0, \"a\"), #(1, \"b\")]\n" + " ```\n" +). +-spec index_map(list(ADQ), fun((ADQ, integer()) -> ADS)) -> list(ADS). +index_map(List, Fun) -> + index_map_loop(List, Fun, 0, []). + +-file("src/gleam/list.gleam", 497). +-spec try_map_loop(list(AEI), fun((AEI) -> {ok, AEK} | {error, AEL}), list(AEK)) -> {ok, + list(AEK)} | + {error, AEL}. +try_map_loop(List, Fun, Acc) -> + case List of + [] -> + {ok, lists:reverse(Acc)}; + + [First | Rest] -> + case Fun(First) of + {ok, First@1} -> + try_map_loop(Rest, Fun, [First@1 | Acc]); + + {error, Error} -> + {error, Error} + end + end. + +-file("src/gleam/list.gleam", 490). +?DOC( + " Takes a function that returns a `Result` and applies it to each element in a\n" + " given list in turn.\n" + "\n" + " If the function returns `Ok(new_value)` for all elements in the list then a\n" + " list of the new values is returned.\n" + "\n" + " If the function returns `Error(reason)` for any of the elements then it is\n" + " returned immediately. None of the elements in the list are processed after\n" + " one returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert try_map([1, 2, 3], fn(x) { Ok(x + 2) }) == Ok([3, 4, 5])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([1, 2, 3], fn(_) { Error(0) }) == Error(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([[1], [2, 3]], first) == Ok([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([[1], [], [2]], first) == Error(Nil)\n" + " ```\n" +). +-spec try_map(list(ADZ), fun((ADZ) -> {ok, AEB} | {error, AEC})) -> {ok, + list(AEB)} | + {error, AEC}. +try_map(List, Fun) -> + try_map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 530). +?DOC( + " Returns a list that is the given list with up to the given number of\n" + " elements removed from the front of the list.\n" + "\n" + " If the list has less than the number of elements an empty list is\n" + " returned.\n" + "\n" + " This function runs in linear time but does not copy the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop([1, 2, 3, 4], 2) == [3, 4]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert drop([1, 2, 3, 4], 9) == []\n" + " ```\n" +). +-spec drop(list(AES), integer()) -> list(AES). +drop(List, N) -> + case N =< 0 of + true -> + List; + + false -> + case List of + [] -> + []; + + [_ | Rest] -> + drop(Rest, N - 1) + end + end. + +-file("src/gleam/list.gleam", 563). +-spec take_loop(list(AEY), integer(), list(AEY)) -> list(AEY). +take_loop(List, N, Acc) -> + case N =< 0 of + true -> + lists:reverse(Acc); + + false -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + take_loop(Rest, N - 1, [First | Acc]) + end + end. + +-file("src/gleam/list.gleam", 559). +?DOC( + " Returns a list containing the first given number of elements from the given\n" + " list.\n" + "\n" + " If the list has less than the number of elements then the full list is\n" + " returned.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert take([1, 2, 3, 4], 2) == [1, 2]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert take([1, 2, 3, 4], 9) == [1, 2, 3, 4]\n" + " ```\n" +). +-spec take(list(AEV), integer()) -> list(AEV). +take(List, N) -> + take_loop(List, N, []). + +-file("src/gleam/list.gleam", 582). +?DOC( + " Returns a new empty list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() == []\n" + " ```\n" +). +-spec new() -> list(any()). +new() -> + []. + +-file("src/gleam/list.gleam", 603). +?DOC( + " Returns the given item wrapped in a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert wrap(1) == [1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert wrap([\"a\", \"b\", \"c\"]) == [[\"a\", \"b\", \"c\"]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert wrap([[]]) == [[[]]]\n" + " ```\n" +). +-spec wrap(AFE) -> list(AFE). +wrap(Item) -> + [Item]. + +-file("src/gleam/list.gleam", 623). +-spec append_loop(list(AFK), list(AFK)) -> list(AFK). +append_loop(First, Second) -> + case First of + [] -> + Second; + + [First@1 | Rest] -> + append_loop(Rest, [First@1 | Second]) + end. + +-file("src/gleam/list.gleam", 619). +?DOC( + " Joins one list onto the end of another.\n" + "\n" + " This function runs in linear time, and it traverses and copies the first\n" + " list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append([1, 2], [3]) == [1, 2, 3]\n" + " ```\n" +). +-spec append(list(AFG), list(AFG)) -> list(AFG). +append(First, Second) -> + lists:append(First, Second). + +-file("src/gleam/list.gleam", 643). +?DOC( + " Prefixes an item to a list. This can also be done using the dedicated\n" + " syntax instead.\n" + "\n" + " ```gleam\n" + " let existing_list = [2, 3, 4]\n" + " assert [1, ..existing_list] == [1, 2, 3, 4]\n" + " ```\n" + "\n" + " ```gleam\n" + " let existing_list = [2, 3, 4]\n" + " assert prepend(to: existing_list, this: 1) == [1, 2, 3, 4]\n" + " ```\n" +). +-spec prepend(list(AFO), AFO) -> list(AFO). +prepend(List, Item) -> + [Item | List]. + +-file("src/gleam/list.gleam", 663). +-spec flatten_loop(list(list(AFV)), list(AFV)) -> list(AFV). +flatten_loop(Lists, Acc) -> + case Lists of + [] -> + lists:reverse(Acc); + + [List | Further_lists] -> + flatten_loop(Further_lists, lists:reverse(List, Acc)) + end. + +-file("src/gleam/list.gleam", 659). +?DOC( + " Joins a list of lists into a single list.\n" + "\n" + " This function traverses all elements twice on the JavaScript target.\n" + " This function traverses all elements once on the Erlang target.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten([[1], [2, 3], []]) == [1, 2, 3]\n" + " ```\n" +). +-spec flatten(list(list(AFR))) -> list(AFR). +flatten(Lists) -> + lists:append(Lists). + +-file("src/gleam/list.gleam", 679). +?DOC( + " Maps the list with the given function into a list of lists, and then flattens it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flat_map([2, 4, 6], fn(x) { [x, x + 1] }) == [2, 3, 4, 5, 6, 7]\n" + " ```\n" +). +-spec flat_map(list(AGA), fun((AGA) -> list(AGC))) -> list(AGC). +flat_map(List, Fun) -> + lists:append(map(List, Fun)). + +-file("src/gleam/list.gleam", 691). +?DOC( + " Reduces a list of elements into a single value by calling a given function\n" + " on each element, going from left to right.\n" + "\n" + " `fold([1, 2, 3], 0, add)` is the equivalent of\n" + " `add(add(add(0, 1), 2), 3)`.\n" + "\n" + " This function runs in linear time.\n" +). +-spec fold(list(AGF), AGH, fun((AGH, AGF) -> AGH)) -> AGH. +fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + fold(Rest, Fun(Initial, First), Fun) + end. + +-file("src/gleam/list.gleam", 713). +?DOC( + " Reduces a list of elements into a single value by calling a given function\n" + " on each element, going from right to left.\n" + "\n" + " `fold_right([1, 2, 3], 0, add)` is the equivalent of\n" + " `add(add(add(0, 3), 2), 1)`.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " Unlike `fold` this function is not tail recursive. Where possible use\n" + " `fold` instead as it will use less memory.\n" +). +-spec fold_right(list(AGI), AGK, fun((AGK, AGI) -> AGK)) -> AGK. +fold_right(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + Fun(fold_right(Rest, Initial, Fun), First) + end. + +-file("src/gleam/list.gleam", 750). +-spec index_fold_loop( + list(AGO), + AGQ, + fun((AGQ, AGO, integer()) -> AGQ), + integer() +) -> AGQ. +index_fold_loop(Over, Acc, With, Index) -> + case Over of + [] -> + Acc; + + [First | Rest] -> + index_fold_loop(Rest, With(Acc, First, Index), With, Index + 1) + end. + +-file("src/gleam/list.gleam", 742). +?DOC( + " Like `fold` but the folding function also receives the index of the current element.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [\"a\", \"b\", \"c\"]\n" + " |> index_fold(\"\", fn(acc, item, index) {\n" + " acc <> int.to_string(index) <> \":\" <> item <> \" \"\n" + " })\n" + " == \"0:a 1:b 2:c\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [10, 20, 30]\n" + " |> index_fold(0, fn(acc, item, index) { acc + item * index })\n" + " == 80\n" + " ```\n" +). +-spec index_fold(list(AGL), AGN, fun((AGN, AGL, integer()) -> AGN)) -> AGN. +index_fold(List, Initial, Fun) -> + index_fold_loop(List, Initial, Fun, 0). + +-file("src/gleam/list.gleam", 782). +?DOC( + " A variant of fold that might fail.\n" + "\n" + " The folding function should return `Result(accumulator, error)`.\n" + " If the returned value is `Ok(accumulator)` try_fold will try the next value in the list.\n" + " If the returned value is `Error(error)` try_fold will stop and return that error.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4]\n" + " |> try_fold(0, fn(acc, i) {\n" + " case i < 3 {\n" + " True -> Ok(acc + i)\n" + " False -> Error(Nil)\n" + " }\n" + " })\n" + " == Error(Nil)\n" + " ```\n" +). +-spec try_fold(list(AGR), AGT, fun((AGT, AGR) -> {ok, AGT} | {error, AGU})) -> {ok, + AGT} | + {error, AGU}. +try_fold(List, Initial, Fun) -> + case List of + [] -> + {ok, Initial}; + + [First | Rest] -> + case Fun(Initial, First) of + {ok, Result} -> + try_fold(Rest, Result, Fun); + + {error, _} = Error -> + Error + end + end. + +-file("src/gleam/list.gleam", 821). +?DOC( + " A variant of fold that allows to stop folding earlier.\n" + "\n" + " The folding function should return `ContinueOrStop(accumulator)`.\n" + " If the returned value is `Continue(accumulator)` fold_until will try the next value in the list.\n" + " If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4]\n" + " |> fold_until(0, fn(acc, i) {\n" + " case i < 3 {\n" + " True -> Continue(acc + i)\n" + " False -> Stop(acc)\n" + " }\n" + " })\n" + " == 3\n" + " ```\n" +). +-spec fold_until(list(AGZ), AHB, fun((AHB, AGZ) -> continue_or_stop(AHB))) -> AHB. +fold_until(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + case Fun(Initial, First) of + {continue, Next_accumulator} -> + fold_until(Rest, Next_accumulator, Fun); + + {stop, B} -> + B + end + end. + +-file("src/gleam/list.gleam", 855). +?DOC( + " Finds the first element in a given list for which the given function returns\n" + " `True`.\n" + "\n" + " Returns `Error(Nil)` if no such element is found.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert find([1, 2, 3], fn(x) { x > 2 }) == Ok(3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find([1, 2, 3], fn(x) { x > 4 }) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find([], fn(_) { True }) == Error(Nil)\n" + " ```\n" +). +-spec find(list(AHD), fun((AHD) -> boolean())) -> {ok, AHD} | {error, nil}. +find(List, Is_desired) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + case Is_desired(First) of + true -> + {ok, First}; + + false -> + find(Rest, Is_desired) + end + end. + +-file("src/gleam/list.gleam", 888). +?DOC( + " Finds the first element in a given list for which the given function returns\n" + " `Ok(new_value)`, then returns the wrapped `new_value`.\n" + "\n" + " Returns `Error(Nil)` if no such element is found.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert find_map([[], [2], [3]], first) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find_map([[], []], first) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find_map([], first) == Error(Nil)\n" + " ```\n" +). +-spec find_map(list(AHH), fun((AHH) -> {ok, AHJ} | {error, any()})) -> {ok, AHJ} | + {error, nil}. +find_map(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + case Fun(First) of + {ok, First@1} -> + {ok, First@1}; + + {error, _} -> + find_map(Rest, Fun) + end + end. + +-file("src/gleam/list.gleam", 920). +?DOC( + " Returns `True` if the given function returns `True` for all the elements in\n" + " the given list. If the function returns `False` for any of the elements it\n" + " immediately returns `False` without checking the rest of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([4, 5], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !all([4, 3], fn(x) { x > 3 })\n" + " ```\n" +). +-spec all(list(AHP), fun((AHP) -> boolean())) -> boolean(). +all(List, Predicate) -> + case List of + [] -> + true; + + [First | Rest] -> + case Predicate(First) of + true -> + all(Rest, Predicate); + + false -> + false + end + end. + +-file("src/gleam/list.gleam", 953). +?DOC( + " Returns `True` if the given function returns `True` for any the elements in\n" + " the given list. If the function returns `True` for any of the elements it\n" + " immediately returns `True` without checking the rest of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !any([], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([4, 5], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([4, 3], fn(x) { x > 4 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([3, 4], fn(x) { x > 3 })\n" + " ```\n" +). +-spec any(list(AHR), fun((AHR) -> boolean())) -> boolean(). +any(List, Predicate) -> + case List of + [] -> + false; + + [First | Rest] -> + case Predicate(First) of + true -> + true; + + false -> + any(Rest, Predicate) + end + end. + +-file("src/gleam/list.gleam", 991). +-spec zip_loop(list(AHY), list(AIA), list({AHY, AIA})) -> list({AHY, AIA}). +zip_loop(One, Other, Acc) -> + case {One, Other} of + {[First_one | Rest_one], [First_other | Rest_other]} -> + zip_loop(Rest_one, Rest_other, [{First_one, First_other} | Acc]); + + {_, _} -> + lists:reverse(Acc) + end. + +-file("src/gleam/list.gleam", 987). +?DOC( + " Takes two lists and returns a single list of 2-element tuples.\n" + "\n" + " If one of the lists is longer than the other, the remaining elements from\n" + " the longer list are not used.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert zip([], []) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1, 2], [3]) == [#(1, 3)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1], [3, 4]) == [#(1, 3)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1, 2], [3, 4]) == [#(1, 3), #(2, 4)]\n" + " ```\n" +). +-spec zip(list(AHT), list(AHV)) -> list({AHT, AHV}). +zip(List, Other) -> + zip_loop(List, Other, []). + +-file("src/gleam/list.gleam", 1028). +-spec strict_zip_loop(list(AIL), list(AIN), list({AIL, AIN})) -> {ok, + list({AIL, AIN})} | + {error, nil}. +strict_zip_loop(One, Other, Acc) -> + case {One, Other} of + {[], []} -> + {ok, lists:reverse(Acc)}; + + {[], _} -> + {error, nil}; + + {_, []} -> + {error, nil}; + + {[First_one | Rest_one], [First_other | Rest_other]} -> + strict_zip_loop( + Rest_one, + Rest_other, + [{First_one, First_other} | Acc] + ) + end. + +-file("src/gleam/list.gleam", 1021). +?DOC( + " Takes two lists and returns a single list of 2-element tuples.\n" + "\n" + " If one of the lists is longer than the other, an `Error` is returned.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert strict_zip([], []) == Ok([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1, 2], [3]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1], [3, 4]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1, 2], [3, 4]) == Ok([#(1, 3), #(2, 4)])\n" + " ```\n" +). +-spec strict_zip(list(AIE), list(AIG)) -> {ok, list({AIE, AIG})} | {error, nil}. +strict_zip(List, Other) -> + strict_zip_loop(List, Other, []). + +-file("src/gleam/list.gleam", 1057). +-spec unzip_loop(list({AIY, AIZ}), list(AIY), list(AIZ)) -> {list(AIY), + list(AIZ)}. +unzip_loop(Input, One, Other) -> + case Input of + [] -> + {lists:reverse(One), lists:reverse(Other)}; + + [{First_one, First_other} | Rest] -> + unzip_loop(Rest, [First_one | One], [First_other | Other]) + end. + +-file("src/gleam/list.gleam", 1053). +?DOC( + " Takes a single list of 2-element tuples and returns two lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unzip([#(1, 2), #(3, 4)]) == #([1, 3], [2, 4])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unzip([]) == #([], [])\n" + " ```\n" +). +-spec unzip(list({AIT, AIU})) -> {list(AIT), list(AIU)}. +unzip(Input) -> + unzip_loop(Input, [], []). + +-file("src/gleam/list.gleam", 1090). +-spec intersperse_loop(list(AJI), AJI, list(AJI)) -> list(AJI). +intersperse_loop(List, Separator, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + intersperse_loop(Rest, Separator, [First, Separator | Acc]) + end. + +-file("src/gleam/list.gleam", 1083). +?DOC( + " Inserts a given value between each existing element in a given list.\n" + "\n" + " This function runs in linear time and copies the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert intersperse([1, 1, 1], 2) == [1, 2, 1, 2, 1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert intersperse([], 2) == []\n" + " ```\n" +). +-spec intersperse(list(AJF), AJF) -> list(AJF). +intersperse(List, Elem) -> + case List of + [] -> + List; + + [_] -> + List; + + [First | Rest] -> + intersperse_loop(Rest, Elem, [First]) + end. + +-file("src/gleam/list.gleam", 1112). +-spec unique_loop(list(AJP), gleam@dict:dict(AJP, nil), list(AJP)) -> list(AJP). +unique_loop(List, Seen, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + case gleam@dict:has_key(Seen, First) of + true -> + unique_loop(Rest, Seen, Acc); + + false -> + unique_loop( + Rest, + gleam@dict:insert(Seen, First, nil), + [First | Acc] + ) + end + end. + +-file("src/gleam/list.gleam", 1108). +?DOC( + " Removes any duplicate elements from a given list.\n" + "\n" + " This function returns in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unique([1, 1, 1, 4, 7, 3, 3, 4]) == [1, 4, 7, 3]\n" + " ```\n" +). +-spec unique(list(AJM)) -> list(AJM). +unique(List) -> + unique_loop(List, maps:new(), []). + +-file("src/gleam/list.gleam", 1197). +?DOC( + " Given a list it returns slices of it that are locally sorted in ascending\n" + " order.\n" + "\n" + " Imagine you have this list:\n" + "\n" + " ```\n" + " [1, 2, 3, 2, 1, 0]\n" + " ^^^^^^^ ^^^^^^^ This is a slice in descending order\n" + " |\n" + " | This is a slice that is sorted in ascending order\n" + " ```\n" + "\n" + " So the produced result will contain these two slices, each one sorted in\n" + " ascending order: `[[1, 2, 3], [0, 1, 2]]`.\n" + "\n" + " - `growing` is an accumulator with the current slice being grown\n" + " - `direction` is the growing direction of the slice being grown, it could\n" + " either be ascending or strictly descending\n" + " - `prev` is the previous element that needs to be added to the growing slice\n" + " it is carried around to check whether we have to keep growing the current\n" + " slice or not\n" + " - `acc` is the accumulator containing the slices sorted in ascending order\n" +). +-spec sequences( + list(AJY), + fun((AJY, AJY) -> gleam@order:order()), + list(AJY), + sorting(), + AJY, + list(list(AJY)) +) -> list(list(AJY)). +sequences(List, Compare, Growing, Direction, Prev, Acc) -> + Growing@1 = [Prev | Growing], + case List of + [] -> + case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end; + + [New | Rest] -> + case {Compare(Prev, New), Direction} of + {gt, descending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {lt, ascending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {eq, ascending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {gt, ascending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end; + + {lt, descending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end; + + {eq, descending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end + end + end. + +-file("src/gleam/list.gleam", 1345). +?DOC( + " Merges two lists sorted in ascending order into a single list sorted in\n" + " descending order according to the given comparator function.\n" + "\n" + " This reversing of the sort order is not avoidable if we want to implement\n" + " merge as a tail recursive function. We could reverse the accumulator before\n" + " returning it but that would end up being less efficient; so the merging\n" + " algorithm has to play around this.\n" +). +-spec merge_ascendings( + list(AKV), + list(AKV), + fun((AKV, AKV) -> gleam@order:order()), + list(AKV) +) -> list(AKV). +merge_ascendings(List1, List2, Compare, Acc) -> + case {List1, List2} of + {[], List} -> + lists:reverse(List, Acc); + + {List, []} -> + lists:reverse(List, Acc); + + {[First1 | Rest1], [First2 | Rest2]} -> + case Compare(First1, First2) of + lt -> + merge_ascendings(Rest1, List2, Compare, [First1 | Acc]); + + gt -> + merge_ascendings(List1, Rest2, Compare, [First2 | Acc]); + + eq -> + merge_ascendings(List1, Rest2, Compare, [First2 | Acc]) + end + end. + +-file("src/gleam/list.gleam", 1298). +?DOC( + " Given a list of ascending lists, it merges adjacent pairs into a single\n" + " descending list, halving their number.\n" + " It returns a list of the remaining descending lists.\n" +). +-spec merge_ascending_pairs( + list(list(AKJ)), + fun((AKJ, AKJ) -> gleam@order:order()), + list(list(AKJ)) +) -> list(list(AKJ)). +merge_ascending_pairs(Sequences, Compare, Acc) -> + case Sequences of + [] -> + lists:reverse(Acc); + + [Sequence] -> + lists:reverse([lists:reverse(Sequence) | Acc]); + + [Ascending1, Ascending2 | Rest] -> + Descending = merge_ascendings(Ascending1, Ascending2, Compare, []), + merge_ascending_pairs(Rest, Compare, [Descending | Acc]) + end. + +-file("src/gleam/list.gleam", 1372). +?DOC( + " This is exactly the same as merge_ascendings but mirrored: it merges two\n" + " lists sorted in descending order into a single list sorted in ascending\n" + " order according to the given comparator function.\n" + "\n" + " This reversing of the sort order is not avoidable if we want to implement\n" + " merge as a tail recursive function. We could reverse the accumulator before\n" + " returning it but that would end up being less efficient; so the merging\n" + " algorithm has to play around this.\n" +). +-spec merge_descendings( + list(ALA), + list(ALA), + fun((ALA, ALA) -> gleam@order:order()), + list(ALA) +) -> list(ALA). +merge_descendings(List1, List2, Compare, Acc) -> + case {List1, List2} of + {[], List} -> + lists:reverse(List, Acc); + + {List, []} -> + lists:reverse(List, Acc); + + {[First1 | Rest1], [First2 | Rest2]} -> + case Compare(First1, First2) of + lt -> + merge_descendings(List1, Rest2, Compare, [First2 | Acc]); + + gt -> + merge_descendings(Rest1, List2, Compare, [First1 | Acc]); + + eq -> + merge_descendings(Rest1, List2, Compare, [First1 | Acc]) + end + end. + +-file("src/gleam/list.gleam", 1320). +?DOC(" This is the same as merge_ascending_pairs but flipped for descending lists.\n"). +-spec merge_descending_pairs( + list(list(AKP)), + fun((AKP, AKP) -> gleam@order:order()), + list(list(AKP)) +) -> list(list(AKP)). +merge_descending_pairs(Sequences, Compare, Acc) -> + case Sequences of + [] -> + lists:reverse(Acc); + + [Sequence] -> + lists:reverse([lists:reverse(Sequence) | Acc]); + + [Descending1, Descending2 | Rest] -> + Ascending = merge_descendings(Descending1, Descending2, Compare, []), + merge_descending_pairs(Rest, Compare, [Ascending | Acc]) + end. + +-file("src/gleam/list.gleam", 1264). +?DOC( + " Given some some sorted sequences (assumed to be sorted in `direction`) it\n" + " merges them all together until we're left with just a list sorted in\n" + " ascending order.\n" +). +-spec merge_all( + list(list(AKF)), + sorting(), + fun((AKF, AKF) -> gleam@order:order()) +) -> list(AKF). +merge_all(Sequences, Direction, Compare) -> + case {Sequences, Direction} of + {[], _} -> + []; + + {[Sequence], ascending} -> + Sequence; + + {[Sequence@1], descending} -> + lists:reverse(Sequence@1); + + {_, ascending} -> + Sequences@1 = merge_ascending_pairs(Sequences, Compare, []), + merge_all(Sequences@1, descending, Compare); + + {_, descending} -> + Sequences@2 = merge_descending_pairs(Sequences, Compare, []), + merge_all(Sequences@2, ascending, Compare) + end. + +-file("src/gleam/list.gleam", 1135). +?DOC( + " Sorts from smallest to largest based upon the ordering specified by a given\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) == [1, 2, 3, 4, 4, 5, 6]\n" + " ```\n" +). +-spec sort(list(AJV), fun((AJV, AJV) -> gleam@order:order())) -> list(AJV). +sort(List, Compare) -> + case List of + [] -> + []; + + [X] -> + [X]; + + [X@1, Y | Rest] -> + Direction = case Compare(X@1, Y) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + Sequences = sequences(Rest, Compare, [X@1], Direction, Y, []), + merge_all(Sequences, ascending, Compare) + end. + +-file("src/gleam/list.gleam", 1405). +-spec repeat_loop(ALH, integer(), list(ALH)) -> list(ALH). +repeat_loop(Item, Times, Acc) -> + case Times =< 0 of + true -> + Acc; + + false -> + repeat_loop(Item, Times - 1, [Item | Acc]) + end. + +-file("src/gleam/list.gleam", 1401). +?DOC( + " Builds a list of a given value a given number of times.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert repeat(\"a\", times: 0) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert repeat(\"a\", times: 5) == [\"a\", \"a\", \"a\", \"a\", \"a\"]\n" + " ```\n" +). +-spec repeat(ALF, integer()) -> list(ALF). +repeat(A, Times) -> + repeat_loop(A, Times, []). + +-file("src/gleam/list.gleam", 1435). +-spec split_loop(list(ALO), integer(), list(ALO)) -> {list(ALO), list(ALO)}. +split_loop(List, N, Taken) -> + case N =< 0 of + true -> + {lists:reverse(Taken), List}; + + false -> + case List of + [] -> + {lists:reverse(Taken), []}; + + [First | Rest] -> + split_loop(Rest, N - 1, [First | Taken]) + end + end. + +-file("src/gleam/list.gleam", 1431). +?DOC( + " Splits a list in two before the given index.\n" + "\n" + " If the list is not long enough to have the given index the before list will\n" + " be the input list, and the after list will be empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 0) == #([], [6, 7, 8, 9])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 2) == #([6, 7], [8, 9])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 4) == #([6, 7, 8, 9], [])\n" + " ```\n" +). +-spec split(list(ALK), integer()) -> {list(ALK), list(ALK)}. +split(List, Index) -> + split_loop(List, Index, []). + +-file("src/gleam/list.gleam", 1471). +-spec split_while_loop(list(ALX), fun((ALX) -> boolean()), list(ALX)) -> {list(ALX), + list(ALX)}. +split_while_loop(List, F, Acc) -> + case List of + [] -> + {lists:reverse(Acc), []}; + + [First | Rest] -> + case F(First) of + true -> + split_while_loop(Rest, F, [First | Acc]); + + false -> + {lists:reverse(Acc), List} + end + end. + +-file("src/gleam/list.gleam", 1464). +?DOC( + " Splits a list in two before the first element that a given function returns\n" + " `False` for.\n" + "\n" + " If the function returns `True` for all elements the first list will be the\n" + " input list, and the second list will be empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 })\n" + " == #([1, 2, 3], [4, 5])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 })\n" + " == #([1, 2, 3, 4, 5], [])\n" + " ```\n" +). +-spec split_while(list(ALT), fun((ALT) -> boolean())) -> {list(ALT), list(ALT)}. +split_while(List, Predicate) -> + split_while_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 1508). +?DOC( + " Given a list of 2-element tuples, finds the first tuple that has a given\n" + " key as the first element and returns the second element.\n" + "\n" + " If no tuple is found with the given key then `Error(Nil)` is returned.\n" + "\n" + " This function may be useful for interacting with Erlang code where lists of\n" + " tuples are common.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"a\") == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"b\") == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"c\") == Error(Nil)\n" + " ```\n" +). +-spec key_find(list({AMC, AMD}), AMC) -> {ok, AMD} | {error, nil}. +key_find(Keyword_list, Desired_key) -> + find_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/list.gleam", 1537). +?DOC( + " Given a list of 2-element tuples, finds all tuples that have a given\n" + " key as the first element and returns the second element.\n" + "\n" + " This function may be useful for interacting with Erlang code where lists of\n" + " tuples are common.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_filter([#(\"a\", 0), #(\"b\", 1), #(\"a\", 2)], \"a\") == [0, 2]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_filter([#(\"a\", 0), #(\"b\", 1)], \"c\") == []\n" + " ```\n" +). +-spec key_filter(list({AMH, AMI}), AMH) -> list(AMI). +key_filter(Keyword_list, Desired_key) -> + filter_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/list.gleam", 1574). +-spec key_pop_loop(list({AMR, AMS}), AMR, list({AMR, AMS})) -> {ok, + {AMS, list({AMR, AMS})}} | + {error, nil}. +key_pop_loop(List, Key, Checked) -> + case List of + [] -> + {error, nil}; + + [{K, V} | Rest] when K =:= Key -> + {ok, {V, lists:reverse(Checked, Rest)}}; + + [First | Rest@1] -> + key_pop_loop(Rest@1, Key, [First | Checked]) + end. + +-file("src/gleam/list.gleam", 1570). +?DOC( + " Given a list of 2-element tuples, finds the first tuple that has a given\n" + " key as the first element. This function will return the second element\n" + " of the found tuple and list with tuple removed.\n" + "\n" + " If no tuple is found with the given key then `Error(Nil)` is returned.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"a\") == Ok(#(0, [#(\"b\", 1)]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"b\") == Ok(#(1, [#(\"a\", 0)]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"c\") == Error(Nil)\n" + " ```\n" +). +-spec key_pop(list({AML, AMM}), AML) -> {ok, {AMM, list({AML, AMM})}} | + {error, nil}. +key_pop(List, Key) -> + key_pop_loop(List, Key, []). + +-file("src/gleam/list.gleam", 1606). +-spec key_set_loop(list({ANC, AND}), ANC, AND, list({ANC, AND})) -> list({ANC, + AND}). +key_set_loop(List, Key, Value, Inspected) -> + case List of + [{K, _} | Rest] when K =:= Key -> + lists:reverse(Inspected, [{K, Value} | Rest]); + + [First | Rest@1] -> + key_set_loop(Rest@1, Key, Value, [First | Inspected]); + + [] -> + lists:reverse([{Key, Value} | Inspected]) + end. + +-file("src/gleam/list.gleam", 1602). +?DOC( + " Given a list of 2-element tuples, inserts a key and value into the list.\n" + "\n" + " If there was already a tuple with the key then it is replaced, otherwise it\n" + " is added to the end of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_set([#(5, 0), #(4, 1)], 4, 100) == [#(5, 0), #(4, 100)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_set([#(5, 0), #(4, 1)], 1, 100) == [#(5, 0), #(4, 1), #(1, 100)]\n" + " ```\n" +). +-spec key_set(list({AMY, AMZ}), AMY, AMZ) -> list({AMY, AMZ}). +key_set(List, Key, Value) -> + key_set_loop(List, Key, Value, []). + +-file("src/gleam/list.gleam", 1633). +?DOC( + " Calls a function for each element in a list, discarding the return value.\n" + "\n" + " Useful for calling a side effect for every item of a list.\n" + "\n" + " ```gleam\n" + " import gleam/io\n" + "\n" + " assert each([\"1\", \"2\", \"3\"], io.println) == Nil\n" + " // 1\n" + " // 2\n" + " // 3\n" + " ```\n" +). +-spec each(list(ANH), fun((ANH) -> any())) -> nil. +each(List, F) -> + case List of + [] -> + nil; + + [First | Rest] -> + F(First), + each(Rest, F) + end. + +-file("src/gleam/list.gleam", 1660). +?DOC( + " Calls a `Result` returning function for each element in a list, discarding\n" + " the return value. If the function returns `Error` then the iteration is\n" + " stopped and the error is returned.\n" + "\n" + " Useful for calling a side effect for every item of a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " try_each(\n" + " over: [1, 2, 3],\n" + " with: function_that_might_fail,\n" + " )\n" + " == Ok(Nil)\n" + " ```\n" +). +-spec try_each(list(ANK), fun((ANK) -> {ok, any()} | {error, ANN})) -> {ok, nil} | + {error, ANN}. +try_each(List, Fun) -> + case List of + [] -> + {ok, nil}; + + [First | Rest] -> + case Fun(First) of + {ok, _} -> + try_each(Rest, Fun); + + {error, E} -> + {error, E} + end + end. + +-file("src/gleam/list.gleam", 1692). +-spec partition_loop(list(BGI), fun((BGI) -> boolean()), list(BGI), list(BGI)) -> {list(BGI), + list(BGI)}. +partition_loop(List, Categorise, Trues, Falses) -> + case List of + [] -> + {lists:reverse(Trues), lists:reverse(Falses)}; + + [First | Rest] -> + case Categorise(First) of + true -> + partition_loop(Rest, Categorise, [First | Trues], Falses); + + false -> + partition_loop(Rest, Categorise, Trues, [First | Falses]) + end + end. + +-file("src/gleam/list.gleam", 1685). +?DOC( + " Partitions a list into a tuple/pair of lists\n" + " by a given categorisation function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert [1, 2, 3, 4, 5] |> partition(int.is_odd) == #([1, 3, 5], [2, 4])\n" + " ```\n" +). +-spec partition(list(ANS), fun((ANS) -> boolean())) -> {list(ANS), list(ANS)}. +partition(List, Categorise) -> + partition_loop(List, Categorise, [], []). + +-file("src/gleam/list.gleam", 1769). +-spec window_loop(list(list(AOZ)), list(AOZ), integer()) -> list(list(AOZ)). +window_loop(Acc, List, N) -> + Window = take(List, N), + case erlang:length(Window) =:= N of + true -> + window_loop([Window | Acc], drop(List, 1), N); + + false -> + lists:reverse(Acc) + end. + +-file("src/gleam/list.gleam", 1762). +?DOC( + " Returns a list of sliding windows.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert window([1,2,3,4,5], 3) == [[1, 2, 3], [2, 3, 4], [3, 4, 5]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert window([1, 2], 4) == []\n" + " ```\n" +). +-spec window(list(AOV), integer()) -> list(list(AOV)). +window(List, N) -> + case N =< 0 of + true -> + []; + + false -> + window_loop([], List, N) + end. + +-file("src/gleam/list.gleam", 1790). +?DOC( + " Returns a list of tuples containing two contiguous elements.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert window_by_2([1,2,3,4]) == [#(1, 2), #(2, 3), #(3, 4)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert window_by_2([1]) == []\n" + " ```\n" +). +-spec window_by_2(list(APF)) -> list({APF, APF}). +window_by_2(List) -> + zip(List, drop(List, 1)). + +-file("src/gleam/list.gleam", 1802). +?DOC( + " Drops the first elements in a given list for which the predicate function returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_while([1, 2, 3, 4], fn (x) { x < 3 }) == [3, 4]\n" + " ```\n" +). +-spec drop_while(list(API), fun((API) -> boolean())) -> list(API). +drop_while(List, Predicate) -> + case List of + [] -> + []; + + [First | Rest] -> + case Predicate(First) of + true -> + drop_while(Rest, Predicate); + + false -> + [First | Rest] + end + end. + +-file("src/gleam/list.gleam", 1831). +-spec take_while_loop(list(APO), fun((APO) -> boolean()), list(APO)) -> list(APO). +take_while_loop(List, Predicate, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + case Predicate(First) of + true -> + take_while_loop(Rest, Predicate, [First | Acc]); + + false -> + lists:reverse(Acc) + end + end. + +-file("src/gleam/list.gleam", 1824). +?DOC( + " Takes the first elements in a given list for which the predicate function returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert take_while([1, 2, 3, 2, 4], fn (x) { x < 3 }) == [1, 2]\n" + " ```\n" +). +-spec take_while(list(APL), fun((APL) -> boolean())) -> list(APL). +take_while(List, Predicate) -> + take_while_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 1863). +-spec chunk_loop(list(APX), fun((APX) -> APZ), APZ, list(APX), list(list(APX))) -> list(list(APX)). +chunk_loop(List, F, Previous_key, Current_chunk, Acc) -> + case List of + [First | Rest] -> + Key = F(First), + case Key =:= Previous_key of + true -> + chunk_loop(Rest, F, Key, [First | Current_chunk], Acc); + + false -> + New_acc = [lists:reverse(Current_chunk) | Acc], + chunk_loop(Rest, F, Key, [First], New_acc) + end; + + [] -> + lists:reverse([lists:reverse(Current_chunk) | Acc]) + end. + +-file("src/gleam/list.gleam", 1856). +?DOC( + " Returns a list of chunks in which\n" + " the return value of calling `f` on each element is the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 })\n" + " == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]\n" + " ```\n" +). +-spec chunk(list(APS), fun((APS) -> any())) -> list(list(APS)). +chunk(List, F) -> + case List of + [] -> + []; + + [First | Rest] -> + chunk_loop(Rest, F, F(First), [First], []) + end. + +-file("src/gleam/list.gleam", 1908). +-spec sized_chunk_loop( + list(AQJ), + integer(), + integer(), + list(AQJ), + list(list(AQJ)) +) -> list(list(AQJ)). +sized_chunk_loop(List, Count, Left, Current_chunk, Acc) -> + case List of + [] -> + case Current_chunk of + [] -> + lists:reverse(Acc); + + Remaining -> + lists:reverse([lists:reverse(Remaining) | Acc]) + end; + + [First | Rest] -> + Chunk = [First | Current_chunk], + case Left > 1 of + true -> + sized_chunk_loop(Rest, Count, Left - 1, Chunk, Acc); + + false -> + sized_chunk_loop( + Rest, + Count, + Count, + [], + [lists:reverse(Chunk) | Acc] + ) + end + end. + +-file("src/gleam/list.gleam", 1904). +?DOC( + " Returns a list of chunks containing `count` elements each.\n" + "\n" + " If the last chunk does not have `count` elements, it is instead\n" + " a partial chunk, with less than `count` elements.\n" + "\n" + " For any `count` less than 1 this function behaves as if it was set to 1.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5, 6] |> sized_chunk(into: 2)\n" + " == [[1, 2], [3, 4], [5, 6]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5, 6, 7, 8] |> sized_chunk(into: 3)\n" + " == [[1, 2, 3], [4, 5, 6], [7, 8]]\n" + " ```\n" +). +-spec sized_chunk(list(AQF), integer()) -> list(list(AQF)). +sized_chunk(List, Count) -> + sized_chunk_loop(List, Count, Count, [], []). + +-file("src/gleam/list.gleam", 1950). +?DOC( + " This function acts similar to fold, but does not take an initial state.\n" + " Instead, it starts from the first element in the list\n" + " and combines it with each subsequent element in turn using the given\n" + " function. The function is called as `fun(accumulator, current_element)`.\n" + "\n" + " Returns `Ok` to indicate a successful run, and `Error` if called on an\n" + " empty list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [] |> reduce(fn(acc, x) { acc + x }) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5] |> reduce(fn(acc, x) { acc + x }) == Ok(15)\n" + " ```\n" +). +-spec reduce(list(AQQ), fun((AQQ, AQQ) -> AQQ)) -> {ok, AQQ} | {error, nil}. +reduce(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, fold(Rest, First, Fun)} + end. + +-file("src/gleam/list.gleam", 1974). +-spec scan_loop(list(AQY), ARA, list(ARA), fun((ARA, AQY) -> ARA)) -> list(ARA). +scan_loop(List, Accumulator, Accumulated, Fun) -> + case List of + [] -> + lists:reverse(Accumulated); + + [First | Rest] -> + Next = Fun(Accumulator, First), + scan_loop(Rest, Next, [Next | Accumulated], Fun) + end. + +-file("src/gleam/list.gleam", 1966). +?DOC( + " Similar to `fold`, but yields the state of the accumulator at each stage.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert scan(over: [1, 2, 3], from: 100, with: fn(acc, i) { acc + i })\n" + " == [101, 103, 106]\n" + " ```\n" +). +-spec scan(list(AQU), AQW, fun((AQW, AQU) -> AQW)) -> list(AQW). +scan(List, Initial, Fun) -> + scan_loop(List, Initial, [], Fun). + +-file("src/gleam/list.gleam", 2005). +?DOC( + " Returns the last element in the given list.\n" + "\n" + " Returns `Error(Nil)` if the list is empty.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert last([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert last([1, 2, 3, 4, 5]) == Ok(5)\n" + " ```\n" +). +-spec last(list(ARD)) -> {ok, ARD} | {error, nil}. +last(List) -> + case List of + [] -> + {error, nil}; + + [Last] -> + {ok, Last}; + + [_ | Rest] -> + last(Rest) + end. + +-file("src/gleam/list.gleam", 2026). +?DOC( + " Return unique combinations of elements in the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert combinations([1, 2, 3], 2) == [[1, 2], [1, 3], [2, 3]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert combinations([1, 2, 3, 4], 3)\n" + " == [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]\n" + " ```\n" +). +-spec combinations(list(ARH), integer()) -> list(list(ARH)). +combinations(Items, N) -> + case {N, Items} of + {0, _} -> + [[]]; + + {_, []} -> + []; + + {_, [First | Rest]} -> + _pipe = Rest, + _pipe@1 = combinations(_pipe, N - 1), + _pipe@2 = map( + _pipe@1, + fun(Combination) -> [First | Combination] end + ), + _pipe@3 = lists:reverse(_pipe@2), + fold(_pipe@3, combinations(Rest, N), fun(Acc, C) -> [C | Acc] end) + end. + +-file("src/gleam/list.gleam", 2051). +-spec combination_pairs_loop(list(ARO), list({ARO, ARO})) -> list({ARO, ARO}). +combination_pairs_loop(Items, Acc) -> + case Items of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + First_combinations = map(Rest, fun(Other) -> {First, Other} end), + Acc@1 = lists:reverse(First_combinations, Acc), + combination_pairs_loop(Rest, Acc@1) + end. + +-file("src/gleam/list.gleam", 2047). +?DOC( + " Return unique pair combinations of elements in the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert combination_pairs([1, 2, 3]) == [#(1, 2), #(1, 3), #(2, 3)]\n" + " ```\n" +). +-spec combination_pairs(list(ARL)) -> list({ARL, ARL}). +combination_pairs(Items) -> + combination_pairs_loop(Items, []). + +-file("src/gleam/list.gleam", 2107). +-spec take_firsts(list(list(ASI)), list(ASI), list(list(ASI))) -> {list(ASI), + list(list(ASI))}. +take_firsts(Rows, Column, Remaining_rows) -> + case Rows of + [] -> + {lists:reverse(Column), lists:reverse(Remaining_rows)}; + + [[] | Rest] -> + take_firsts(Rest, Column, Remaining_rows); + + [[First | Remaining_row] | Rest_rows] -> + Remaining_rows@1 = [Remaining_row | Remaining_rows], + take_firsts(Rest_rows, [First | Column], Remaining_rows@1) + end. + +-file("src/gleam/list.gleam", 2094). +-spec transpose_loop(list(list(ASB)), list(list(ASB))) -> list(list(ASB)). +transpose_loop(Rows, Columns) -> + case Rows of + [] -> + lists:reverse(Columns); + + _ -> + {Column, Rest} = take_firsts(Rows, [], []), + case Column of + [_ | _] -> + transpose_loop(Rest, [Column | Columns]); + + [] -> + transpose_loop(Rest, Columns) + end + end. + +-file("src/gleam/list.gleam", 2090). +?DOC( + " Transpose rows and columns of the list of lists.\n" + "\n" + " Notice: This function is not tail recursive,\n" + " and thus may exceed stack size if called,\n" + " with large lists (on the JavaScript target).\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert transpose([[1, 2, 3], [101, 102, 103]])\n" + " == [[1, 101], [2, 102], [3, 103]]\n" + " ```\n" +). +-spec transpose(list(list(ARW))) -> list(list(ARW)). +transpose(List_of_lists) -> + transpose_loop(List_of_lists, []). + +-file("src/gleam/list.gleam", 2071). +?DOC( + " Make a list alternating the elements from the given lists\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert interleave([[1, 2], [101, 102], [201, 202]])\n" + " == [1, 101, 201, 2, 102, 202]\n" + " ```\n" +). +-spec interleave(list(list(ARS))) -> list(ARS). +interleave(List) -> + _pipe = List, + _pipe@1 = transpose(_pipe), + lists:append(_pipe@1). + +-file("src/gleam/list.gleam", 2140). +-spec shuffle_pair_unwrap_loop(list({float(), ASU}), list(ASU)) -> list(ASU). +shuffle_pair_unwrap_loop(List, Acc) -> + case List of + [] -> + Acc; + + [Elem_pair | Enumerable] -> + shuffle_pair_unwrap_loop( + Enumerable, + [erlang:element(2, Elem_pair) | Acc] + ) + end. + +-file("src/gleam/list.gleam", 2148). +-spec do_shuffle_by_pair_indexes(list({float(), ASY})) -> list({float(), ASY}). +do_shuffle_by_pair_indexes(List_of_pairs) -> + sort( + List_of_pairs, + fun(A_pair, B_pair) -> + gleam@float:compare( + erlang:element(1, A_pair), + erlang:element(1, B_pair) + ) + end + ). + +-file("src/gleam/list.gleam", 2133). +?DOC( + " Takes a list, randomly sorts all items and returns the shuffled list.\n" + "\n" + " This function uses `float.random` to decide the order of the elements.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> shuffle\n" + " // -> [1, 6, 9, 10, 3, 8, 4, 2, 7, 5]\n" + " ```\n" +). +-spec shuffle(list(ASR)) -> list(ASR). +shuffle(List) -> + _pipe = List, + _pipe@1 = fold(_pipe, [], fun(Acc, A) -> [{rand:uniform(), A} | Acc] end), + _pipe@2 = do_shuffle_by_pair_indexes(_pipe@1), + shuffle_pair_unwrap_loop(_pipe@2, []). + +-file("src/gleam/list.gleam", 2178). +-spec max_loop(list(ATI), fun((ATI, ATI) -> gleam@order:order()), ATI) -> ATI. +max_loop(List, Compare, Max) -> + case List of + [] -> + Max; + + [First | Rest] -> + case Compare(First, Max) of + gt -> + max_loop(Rest, Compare, First); + + lt -> + max_loop(Rest, Compare, Max); + + eq -> + max_loop(Rest, Compare, Max) + end + end. + +-file("src/gleam/list.gleam", 2168). +?DOC( + " Takes a list and a comparator, and returns the maximum element in the list\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5] |> list.max(int.compare) == Ok(5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [\"a\", \"c\", \"b\"] |> list.max(string.compare) == Ok(\"c\")\n" + " ```\n" +). +-spec max(list(ATB), fun((ATB, ATB) -> gleam@order:order())) -> {ok, ATB} | + {error, nil}. +max(List, Compare) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, max_loop(Rest, Compare, First)} + end. + +-file("src/gleam/list.gleam", 2259). +-spec build_reservoir_loop( + list(ATX), + integer(), + gleam@dict:dict(integer(), ATX) +) -> {gleam@dict:dict(integer(), ATX), list(ATX)}. +build_reservoir_loop(List, Size, Reservoir) -> + Reservoir_size = maps:size(Reservoir), + case Reservoir_size >= Size of + true -> + {Reservoir, List}; + + false -> + case List of + [] -> + {Reservoir, []}; + + [First | Rest] -> + Reservoir@1 = gleam@dict:insert( + Reservoir, + Reservoir_size, + First + ), + build_reservoir_loop(Rest, Size, Reservoir@1) + end + end. + +-file("src/gleam/list.gleam", 2255). +?DOC( + " Builds the initial reservoir used by Algorithm L.\n" + " This is a dictionary with keys ranging from `0` up to `n - 1` where each\n" + " value is the corresponding element at that position in `list`.\n" + "\n" + " This also returns the remaining elements of `list` that didn't end up in\n" + " the reservoir.\n" +). +-spec build_reservoir(list(ATS), integer()) -> {gleam@dict:dict(integer(), ATS), + list(ATS)}. +build_reservoir(List, N) -> + build_reservoir_loop(List, N, maps:new()). + +-file("src/gleam/list.gleam", 2243). +-spec log_random() -> float(). +log_random() -> + Random@1 = case gleam@float:logarithm( + rand:uniform() + 2.2250738585072014e-308 + ) of + {ok, Random} -> Random; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gleam/list"/utf8>>, + function => <<"log_random"/utf8>>, + line => 2244, + value => _assert_fail, + start => 55129, + 'end' => 55200, + pattern_start => 55140, + pattern_end => 55150}) + end, + Random@1. + +-file("src/gleam/list.gleam", 2220). +-spec sample_loop( + list(ATM), + gleam@dict:dict(integer(), ATM), + integer(), + float() +) -> gleam@dict:dict(integer(), ATM). +sample_loop(List, Reservoir, N, W) -> + Skip = begin + Log@1 = case gleam@float:logarithm(1.0 - W) of + {ok, Log} -> Log; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gleam/list"/utf8>>, + function => <<"sample_loop"/utf8>>, + line => 2227, + value => _assert_fail, + start => 54690, + 'end' => 54736, + pattern_start => 54701, + pattern_end => 54708}) + end, + erlang:round(math:floor(case Log@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> log_random() / Gleam@denominator + end)) + end, + case drop(List, Skip) of + [] -> + Reservoir; + + [First | Rest] -> + Reservoir@1 = gleam@dict:insert( + Reservoir, + gleam@int:random(N), + First + ), + W@1 = W * math:exp(case erlang:float(N) of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator@1 -> log_random() / Gleam@denominator@1 + end), + sample_loop(Rest, Reservoir@1, N, W@1) + end. + +-file("src/gleam/list.gleam", 2202). +?DOC( + " Returns a random sample of up to n elements from a list using reservoir\n" + " sampling via [Algorithm L](https://en.wikipedia.org/wiki/Reservoir_sampling#Optimal:_Algorithm_L).\n" + " Returns an empty list if the sample size is less than or equal to 0.\n" + "\n" + " Order is not random, only selection is.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " sample([1, 2, 3, 4, 5], 3)\n" + " // -> [2, 4, 5] // A random sample of 3 items\n" + " ```\n" +). +-spec sample(list(ATJ), integer()) -> list(ATJ). +sample(List, N) -> + {Reservoir, Rest} = build_reservoir(List, N), + case gleam@dict:is_empty(Reservoir) of + true -> + []; + + false -> + W = math:exp(case erlang:float(N) of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> log_random() / Gleam@denominator + end), + maps:values(sample_loop(Rest, Reservoir, N, W)) + end. + +-file("src/gleam/list.gleam", 1718). +-spec permutation_zip(list(AOF), list(AOF), list(list(AOF))) -> list(list(AOF)). +permutation_zip(List, Rest, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [Head | Tail] -> + permutation_prepend( + Head, + permutations(lists:reverse(Rest, Tail)), + Tail, + [Head | Rest], + Acc + ) + end. + +-file("src/gleam/list.gleam", 1711). +?DOC( + " Returns all the permutations of a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert permutations([1, 2]) == [[1, 2], [2, 1]]\n" + " ```\n" +). +-spec permutations(list(AOB)) -> list(list(AOB)). +permutations(List) -> + case List of + [] -> + [[]]; + + L -> + permutation_zip(L, [], []) + end. + +-file("src/gleam/list.gleam", 1736). +-spec permutation_prepend( + AOM, + list(list(AOM)), + list(AOM), + list(AOM), + list(list(AOM)) +) -> list(list(AOM)). +permutation_prepend(El, Permutations, List_1, List_2, Acc) -> + case Permutations of + [] -> + permutation_zip(List_1, List_2, Acc); + + [Head | Tail] -> + permutation_prepend(El, Tail, List_1, List_2, [[El | Head] | Acc]) + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@option.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@option.erl new file mode 100644 index 0000000..1204ae6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@option.erl @@ -0,0 +1,381 @@ +-module(gleam@option). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/option.gleam"). +-export([all/1, is_some/1, is_none/1, to_result/2, from_result/1, unwrap/2, lazy_unwrap/2, map/2, flatten/1, then/2, 'or'/2, lazy_or/2, values/1]). +-export_type([option/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type option(EL) :: {some, EL} | none. + +-file("src/gleam/option.gleam", 57). +-spec reverse_and_prepend(list(FA), list(FA)) -> list(FA). +reverse_and_prepend(Prefix, Suffix) -> + case Prefix of + [] -> + Suffix; + + [First | Rest] -> + reverse_and_prepend(Rest, [First | Suffix]) + end. + +-file("src/gleam/option.gleam", 42). +-spec all_loop(list(option(ER)), list(ER)) -> option(list(ER)). +all_loop(List, Acc) -> + case List of + [] -> + {some, lists:reverse(Acc)}; + + [none | _] -> + none; + + [{some, First} | Rest] -> + all_loop(Rest, [First | Acc]) + end. + +-file("src/gleam/option.gleam", 38). +?DOC( + " Combines a list of `Option`s into a single `Option`.\n" + " If all elements in the list are `Some` then returns a `Some` holding the list of values.\n" + " If any element is `None` then returns `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([Some(1), Some(2)]) == Some([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([Some(1), None]) == None\n" + " ```\n" +). +-spec all(list(option(EM))) -> option(list(EM)). +all(List) -> + all_loop(List, []). + +-file("src/gleam/option.gleam", 76). +?DOC( + " Checks whether the `Option` is a `Some` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_some(Some(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_some(None)\n" + " ```\n" +). +-spec is_some(option(any())) -> boolean(). +is_some(Option) -> + Option /= none. + +-file("src/gleam/option.gleam", 92). +?DOC( + " Checks whether the `Option` is a `None` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !is_none(Some(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_none(None)\n" + " ```\n" +). +-spec is_none(option(any())) -> boolean(). +is_none(Option) -> + Option =:= none. + +-file("src/gleam/option.gleam", 108). +?DOC( + " Converts an `Option` type to a `Result` type.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_result(Some(1), \"some_error\") == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_result(None, \"some_error\") == Error(\"some_error\")\n" + " ```\n" +). +-spec to_result(option(FI), FL) -> {ok, FI} | {error, FL}. +to_result(Option, E) -> + case Option of + {some, A} -> + {ok, A}; + + none -> + {error, E} + end. + +-file("src/gleam/option.gleam", 127). +?DOC( + " Converts a `Result` type to an `Option` type.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_result(Ok(1)) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_result(Error(\"some_error\")) == None\n" + " ```\n" +). +-spec from_result({ok, FO} | {error, any()}) -> option(FO). +from_result(Result) -> + case Result of + {ok, A} -> + {some, A}; + + {error, _} -> + none + end. + +-file("src/gleam/option.gleam", 146). +?DOC( + " Extracts the value from an `Option`, returning a default value if there is none.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap(Some(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap(None, 0) == 0\n" + " ```\n" +). +-spec unwrap(option(FT), FT) -> FT. +unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default + end. + +-file("src/gleam/option.gleam", 165). +?DOC( + " Extracts the value from an `Option`, evaluating the default function if the option is `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Some(1), fn() { 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(None, fn() { 0 }) == 0\n" + " ```\n" +). +-spec lazy_unwrap(option(FV), fun(() -> FV)) -> FV. +lazy_unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default() + end. + +-file("src/gleam/option.gleam", 188). +?DOC( + " Updates a value held within the `Some` of an `Option` by calling a given function\n" + " on it.\n" + "\n" + " If the `Option` is a `None` rather than `Some`, the function is not called and the\n" + " `Option` stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map(over: Some(1), with: fn(x) { x + 1 }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map(over: None, with: fn(x) { x + 1 }) == None\n" + " ```\n" +). +-spec map(option(FX), fun((FX) -> FZ)) -> option(FZ). +map(Option, Fun) -> + case Option of + {some, X} -> + {some, Fun(X)}; + + none -> + none + end. + +-file("src/gleam/option.gleam", 211). +?DOC( + " Merges a nested `Option` into a single layer.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten(Some(Some(1))) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Some(None)) == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(None) == None\n" + " ```\n" +). +-spec flatten(option(option(GB))) -> option(GB). +flatten(Option) -> + case Option of + {some, X} -> + X; + + none -> + none + end. + +-file("src/gleam/option.gleam", 246). +?DOC( + " Updates a value held within the `Some` of an `Option` by calling a given function\n" + " on it, where the given function also returns an `Option`. The two options are\n" + " then merged together into one `Option`.\n" + "\n" + " If the `Option` is a `None` rather than `Some` the function is not called and the\n" + " option stays the same.\n" + "\n" + " This function is the equivalent of calling `map` followed by `flatten`, and\n" + " it is useful for chaining together multiple functions that return `Option`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(x) { Some(x + 1) }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(x) { Some(#(\"a\", x)) }) == Some(#(\"a\", 1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(_) { None }) == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(None, fn(x) { Some(x + 1) }) == None\n" + " ```\n" +). +-spec then(option(GF), fun((GF) -> option(GH))) -> option(GH). +then(Option, Fun) -> + case Option of + {some, X} -> + Fun(X); + + none -> + none + end. + +-file("src/gleam/option.gleam", 273). +?DOC( + " Returns the first value if it is `Some`, otherwise returns the second value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(Some(1), Some(2)) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Some(1), None) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(None, Some(2)) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(None, None) == None\n" + " ```\n" +). +-spec 'or'(option(GK), option(GK)) -> option(GK). +'or'(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second + end. + +-file("src/gleam/option.gleam", 300). +?DOC( + " Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_or(Some(1), fn() { Some(2) }) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Some(1), fn() { None }) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(None, fn() { Some(2) }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(None, fn() { None }) == None\n" + " ```\n" +). +-spec lazy_or(option(GO), fun(() -> option(GO))) -> option(GO). +lazy_or(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second() + end. + +-file("src/gleam/option.gleam", 320). +-spec values_loop(list(option(GW)), list(GW)) -> list(GW). +values_loop(List, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [none | Rest] -> + values_loop(Rest, Acc); + + [{some, First} | Rest@1] -> + values_loop(Rest@1, [First | Acc]) + end. + +-file("src/gleam/option.gleam", 316). +?DOC( + " Given a list of `Option`s,\n" + " returns only the values inside `Some`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert values([Some(1), None, Some(3)]) == [1, 3]\n" + " ```\n" +). +-spec values(list(option(GS))) -> list(GS). +values(Options) -> + values_loop(Options, []). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@order.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@order.erl new file mode 100644 index 0000000..c4e87be --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@order.erl @@ -0,0 +1,188 @@ +-module(gleam@order). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/order.gleam"). +-export([negate/1, to_int/1, compare/2, reverse/1, break_tie/2, lazy_break_tie/2]). +-export_type([order/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type order() :: lt | eq | gt. + +-file("src/gleam/order.gleam", 32). +?DOC( + " Inverts an order, so less-than becomes greater-than and greater-than\n" + " becomes less-than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(Lt) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(Eq) == Eq\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(Gt) == Lt\n" + " ```\n" +). +-spec negate(order()) -> order(). +negate(Order) -> + case Order of + lt -> + gt; + + eq -> + eq; + + gt -> + lt + end. + +-file("src/gleam/order.gleam", 56). +?DOC( + " Produces a numeric representation of the order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_int(Lt) == -1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_int(Eq) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_int(Gt) == 1\n" + " ```\n" +). +-spec to_int(order()) -> integer(). +to_int(Order) -> + case Order of + lt -> + -1; + + eq -> + 0; + + gt -> + 1 + end. + +-file("src/gleam/order.gleam", 72). +?DOC( + " Compares two `Order` values to one another, producing a new `Order`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(Eq, with: Lt) == Gt\n" + " ```\n" +). +-spec compare(order(), order()) -> order(). +compare(A, B) -> + case {A, B} of + {X, Y} when X =:= Y -> + eq; + + {lt, _} -> + lt; + + {eq, gt} -> + lt; + + {_, _} -> + gt + end. + +-file("src/gleam/order.gleam", 92). +?DOC( + " Inverts an ordering function, so less-than becomes greater-than and greater-than\n" + " becomes less-than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + " import gleam/list\n" + "\n" + " assert list.sort([1, 5, 4], by: reverse(int.compare)) == [5, 4, 1]\n" + " ```\n" +). +-spec reverse(fun((I, I) -> order())) -> fun((I, I) -> order()). +reverse(Orderer) -> + fun(A, B) -> Orderer(B, A) end. + +-file("src/gleam/order.gleam", 112). +?DOC( + " Return a fallback `Order` in case the first argument is `Eq`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert break_tie(in: int.compare(1, 1), with: Lt) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert break_tie(in: int.compare(1, 0), with: Eq) == Gt\n" + " ```\n" +). +-spec break_tie(order(), order()) -> order(). +break_tie(Order, Other) -> + case Order of + lt -> + Order; + + gt -> + Order; + + eq -> + Other + end. + +-file("src/gleam/order.gleam", 139). +?DOC( + " Invokes a fallback function returning an `Order` in case the first argument\n" + " is `Eq`.\n" + "\n" + " This can be useful when the fallback comparison might be expensive and it\n" + " needs to be delayed until strictly necessary.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt }) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq }) == Gt\n" + " ```\n" +). +-spec lazy_break_tie(order(), fun(() -> order())) -> order(). +lazy_break_tie(Order, Comparison) -> + case Order of + lt -> + Order; + + gt -> + Order; + + eq -> + Comparison() + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@pair.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@pair.erl new file mode 100644 index 0000000..68ac227 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@pair.erl @@ -0,0 +1,104 @@ +-module(gleam@pair). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/pair.gleam"). +-export([first/1, second/1, swap/1, map_first/2, map_second/2, new/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/pair.gleam", 9). +?DOC( + " Returns the first element in a pair.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first(#(1, 2)) == 1\n" + " ```\n" +). +-spec first({CLY, any()}) -> CLY. +first(Pair) -> + {A, _} = Pair, + A. + +-file("src/gleam/pair.gleam", 22). +?DOC( + " Returns the second element in a pair.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert second(#(1, 2)) == 2\n" + " ```\n" +). +-spec second({any(), CMB}) -> CMB. +second(Pair) -> + {_, A} = Pair, + A. + +-file("src/gleam/pair.gleam", 35). +?DOC( + " Returns a new pair with the elements swapped.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert swap(#(1, 2)) == #(2, 1)\n" + " ```\n" +). +-spec swap({CMC, CMD}) -> {CMD, CMC}. +swap(Pair) -> + {A, B} = Pair, + {B, A}. + +-file("src/gleam/pair.gleam", 49). +?DOC( + " Returns a new pair with the first element having had `with` applied to\n" + " it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert #(1, 2) |> map_first(fn(n) { n * 2 }) == #(2, 2)\n" + " ```\n" +). +-spec map_first({CME, CMF}, fun((CME) -> CMG)) -> {CMG, CMF}. +map_first(Pair, Fun) -> + {A, B} = Pair, + {Fun(A), B}. + +-file("src/gleam/pair.gleam", 63). +?DOC( + " Returns a new pair with the second element having had `with` applied to\n" + " it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert #(1, 2) |> map_second(fn(n) { n * 2 }) == #(1, 4)\n" + " ```\n" +). +-spec map_second({CMH, CMI}, fun((CMI) -> CMJ)) -> {CMH, CMJ}. +map_second(Pair, Fun) -> + {A, B} = Pair, + {A, Fun(B)}. + +-file("src/gleam/pair.gleam", 77). +?DOC( + " Returns a new pair with the given elements. This can also be done using the dedicated\n" + " syntax instead: `new(1, 2) == #(1, 2)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new(1, 2) == #(1, 2)\n" + " ```\n" +). +-spec new(CMK, CML) -> {CMK, CML}. +new(First, Second) -> + {First, Second}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@result.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@result.erl new file mode 100644 index 0000000..7aed253 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@result.erl @@ -0,0 +1,500 @@ +-module(gleam@result). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/result.gleam"). +-export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, 'try'/2, unwrap/2, lazy_unwrap/2, unwrap_error/2, 'or'/2, lazy_or/2, all/1, partition/1, replace/2, replace_error/2, values/1, try_recover/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Result represents the result of something that may succeed or not.\n" + " `Ok` means it was successful, `Error` means it was not successful.\n" +). + +-file("src/gleam/result.gleam", 18). +?DOC( + " Checks whether the result is an `Ok` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_ok(Ok(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_ok(Error(Nil))\n" + " ```\n" +). +-spec is_ok({ok, any()} | {error, any()}) -> boolean(). +is_ok(Result) -> + case Result of + {error, _} -> + false; + + {ok, _} -> + true + end. + +-file("src/gleam/result.gleam", 37). +?DOC( + " Checks whether the result is an `Error` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !is_error(Ok(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_error(Error(Nil))\n" + " ```\n" +). +-spec is_error({ok, any()} | {error, any()}) -> boolean(). +is_error(Result) -> + case Result of + {ok, _} -> + false; + + {error, _} -> + true + end. + +-file("src/gleam/result.gleam", 60). +?DOC( + " Updates a value held within the `Ok` of a result by calling a given function\n" + " on it.\n" + "\n" + " If the result is an `Error` rather than `Ok` the function is not called and the\n" + " result stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map(over: Ok(1), with: fn(x) { x + 1 }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map(over: Error(1), with: fn(x) { x + 1 }) == Error(1)\n" + " ```\n" +). +-spec map({ok, CMV} | {error, CMW}, fun((CMV) -> CMZ)) -> {ok, CMZ} | + {error, CMW}. +map(Result, Fun) -> + case Result of + {ok, X} -> + {ok, Fun(X)}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/result.gleam", 83). +?DOC( + " Updates a value held within the `Error` of a result by calling a given function\n" + " on it.\n" + "\n" + " If the result is `Ok` rather than `Error` the function is not called and the\n" + " result stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map_error(over: Error(1), with: fn(x) { x + 1 }) == Error(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map_error(over: Ok(1), with: fn(x) { x + 1 }) == Ok(1)\n" + " ```\n" +). +-spec map_error({ok, CNC} | {error, CND}, fun((CND) -> CNG)) -> {ok, CNC} | + {error, CNG}. +map_error(Result, Fun) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, Error} -> + {error, Fun(Error)} + end. + +-file("src/gleam/result.gleam", 109). +?DOC( + " Merges a nested `Result` into a single layer.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten(Ok(Ok(1))) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Ok(Error(\"\"))) == Error(\"\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Error(Nil)) == Error(Nil)\n" + " ```\n" +). +-spec flatten({ok, {ok, CNJ} | {error, CNK}} | {error, CNK}) -> {ok, CNJ} | + {error, CNK}. +flatten(Result) -> + case Result of + {ok, X} -> + X; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 143). +?DOC( + " \"Updates\" an `Ok` result by passing its value to a function that yields a result,\n" + " and returning the yielded result. (This may \"replace\" the `Ok` with an `Error`.)\n" + "\n" + " If the input is an `Error` rather than an `Ok`, the function is not called and\n" + " the original `Error` is returned.\n" + "\n" + " This function is the equivalent of calling `map` followed by `flatten`, and\n" + " it is useful for chaining together multiple functions that may fail.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(x) { Ok(x + 1) }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(x) { Ok(#(\"a\", x)) }) == Ok(#(\"a\", 1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(_) { Error(\"Oh no\") }) == Error(\"Oh no\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Error(Nil), fn(x) { Ok(x + 1) }) == Error(Nil)\n" + " ```\n" +). +-spec 'try'({ok, CNR} | {error, CNS}, fun((CNR) -> {ok, CNV} | {error, CNS})) -> {ok, + CNV} | + {error, CNS}. +'try'(Result, Fun) -> + case Result of + {ok, X} -> + Fun(X); + + {error, E} -> + {error, E} + end. + +-file("src/gleam/result.gleam", 166). +?DOC( + " Extracts the `Ok` value from a result, returning a default value if the result\n" + " is an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap(Ok(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap(Error(\"\"), 0) == 0\n" + " ```\n" +). +-spec unwrap({ok, COA} | {error, any()}, COA) -> COA. +unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default + end. + +-file("src/gleam/result.gleam", 186). +?DOC( + " Extracts the `Ok` value from a result, evaluating the default function if the result\n" + " is an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Ok(1), fn() { 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Error(\"\"), fn() { 0 }) == 0\n" + " ```\n" +). +-spec lazy_unwrap({ok, COE} | {error, any()}, fun(() -> COE)) -> COE. +lazy_unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default() + end. + +-file("src/gleam/result.gleam", 206). +?DOC( + " Extracts the `Error` value from a result, returning a default value if the result\n" + " is an `Ok`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap_error(Error(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap_error(Ok(\"\"), 0) == 0\n" + " ```\n" +). +-spec unwrap_error({ok, any()} | {error, COJ}, COJ) -> COJ. +unwrap_error(Result, Default) -> + case Result of + {ok, _} -> + Default; + + {error, E} -> + E + end. + +-file("src/gleam/result.gleam", 233). +?DOC( + " Returns the first value if it is `Ok`, otherwise returns the second value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(Ok(1), Ok(2)) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Ok(1), Error(\"Error 2\")) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Error(\"Error 1\"), Ok(2)) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Error(\"Error 1\"), Error(\"Error 2\")) == Error(\"Error 2\")\n" + " ```\n" +). +-spec 'or'({ok, COM} | {error, CON}, {ok, COM} | {error, CON}) -> {ok, COM} | + {error, CON}. +'or'(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second + end. + +-file("src/gleam/result.gleam", 263). +?DOC( + " Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value.\n" + "\n" + " If you need access to the initial error value, use `result.try_recover`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_or(Ok(1), fn() { Ok(2) }) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Ok(1), fn() { Error(\"Error 2\") }) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Error(\"Error 1\"), fn() { Ok(2) }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Error(\"Error 1\"), fn() { Error(\"Error 2\") })\n" + " == Error(\"Error 2\")\n" + " ```\n" +). +-spec lazy_or({ok, COU} | {error, COV}, fun(() -> {ok, COU} | {error, COV})) -> {ok, + COU} | + {error, COV}. +lazy_or(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second() + end. + +-file("src/gleam/result.gleam", 287). +?DOC( + " Combines a list of results into a single result.\n" + " If all elements in the list are `Ok` then returns an `Ok` holding the list of values.\n" + " If any element is `Error` then returns the first error.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([Ok(1), Ok(2)]) == Ok([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([Ok(1), Error(\"e\")]) == Error(\"e\")\n" + " ```\n" +). +-spec all(list({ok, CPC} | {error, CPD})) -> {ok, list(CPC)} | {error, CPD}. +all(Results) -> + gleam@list:try_map(Results, fun(Result) -> Result end). + +-file("src/gleam/result.gleam", 307). +-spec partition_loop(list({ok, CPR} | {error, CPS}), list(CPR), list(CPS)) -> {list(CPR), + list(CPS)}. +partition_loop(Results, Oks, Errors) -> + case Results of + [] -> + {Oks, Errors}; + + [{ok, A} | Rest] -> + partition_loop(Rest, [A | Oks], Errors); + + [{error, E} | Rest@1] -> + partition_loop(Rest@1, Oks, [E | Errors]) + end. + +-file("src/gleam/result.gleam", 303). +?DOC( + " Given a list of results, returns a pair where the first element is a list\n" + " of all the values inside `Ok` and the second element is a list with all the\n" + " values inside `Error`. The values in both lists appear in reverse order with\n" + " respect to their position in the original list of results.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert partition([Ok(1), Error(\"a\"), Error(\"b\"), Ok(2)])\n" + " == #([2, 1], [\"b\", \"a\"])\n" + " ```\n" +). +-spec partition(list({ok, CPK} | {error, CPL})) -> {list(CPK), list(CPL)}. +partition(Results) -> + partition_loop(Results, [], []). + +-file("src/gleam/result.gleam", 327). +?DOC( + " Replace the value within a result\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace(Ok(1), Nil) == Ok(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace(Error(1), Nil) == Error(1)\n" + " ```\n" +). +-spec replace({ok, any()} | {error, CQA}, CQD) -> {ok, CQD} | {error, CQA}. +replace(Result, Value) -> + case Result of + {ok, _} -> + {ok, Value}; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 346). +?DOC( + " Replace the error within a result\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace_error(Error(1), Nil) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace_error(Ok(1), Nil) == Ok(1)\n" + " ```\n" +). +-spec replace_error({ok, CQG} | {error, any()}, CQK) -> {ok, CQG} | {error, CQK}. +replace_error(Result, Error) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, _} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 361). +?DOC( + " Given a list of results, returns only the values inside `Ok`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert values([Ok(1), Error(\"a\"), Ok(3)]) == [1, 3]\n" + " ```\n" +). +-spec values(list({ok, CQN} | {error, any()})) -> list(CQN). +values(Results) -> + gleam@list:filter_map(Results, fun(Result) -> Result end). + +-file("src/gleam/result.gleam", 397). +?DOC( + " Updates a value held within the `Error` of a result by calling a given function\n" + " on it, where the given function also returns a result. The two results are\n" + " then merged together into one result.\n" + "\n" + " If the result is an `Ok` rather than `Error` the function is not called and the\n" + " result stays the same.\n" + "\n" + " This function is useful for chaining together computations that may fail\n" + " and trying to recover from possible errors.\n" + "\n" + " If you do not need access to the initial error value, use `result.lazy_or`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert Ok(1)\n" + " |> try_recover(with: fn(_) { Error(\"failed to recover\") })\n" + " == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert Error(1)\n" + " |> try_recover(with: fn(error) { Ok(error + 1) })\n" + " == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert Error(1)\n" + " |> try_recover(with: fn(error) { Error(\"failed to recover\") })\n" + " == Error(\"failed to recover\")\n" + " ```\n" +). +-spec try_recover( + {ok, CQT} | {error, CQU}, + fun((CQU) -> {ok, CQT} | {error, CQX}) +) -> {ok, CQT} | {error, CQX}. +try_recover(Result, Fun) -> + case Result of + {ok, Value} -> + {ok, Value}; + + {error, Error} -> + Fun(Error) + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@set.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@set.erl new file mode 100644 index 0000000..194c622 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@set.erl @@ -0,0 +1,430 @@ +-module(gleam@set). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/set.gleam"). +-export([new/0, size/1, is_empty/1, contains/2, delete/2, to_list/1, fold/3, filter/2, drop/2, take/2, intersection/2, difference/2, is_subset/2, is_disjoint/2, each/2, insert/2, from_list/1, map/2, union/2, symmetric_difference/2]). +-export_type([set/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-opaque set(CVK) :: {set, gleam@dict:dict(CVK, list(nil))}. + +-file("src/gleam/set.gleam", 32). +?DOC(" Creates a new empty set.\n"). +-spec new() -> set(any()). +new() -> + {set, maps:new()}. + +-file("src/gleam/set.gleam", 50). +?DOC( + " Gets the number of members in a set.\n" + "\n" + " This function runs in constant time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(1)\n" + " |> insert(2)\n" + " |> size\n" + " == 2\n" + " ```\n" +). +-spec size(set(any())) -> integer(). +size(Set) -> + maps:size(erlang:element(2, Set)). + +-file("src/gleam/set.gleam", 66). +?DOC( + " Determines whether or not the set is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(1) |> is_empty }\n" + " ```\n" +). +-spec is_empty(set(any())) -> boolean(). +is_empty(Set) -> + Set =:= new(). + +-file("src/gleam/set.gleam", 108). +?DOC( + " Checks whether a set contains a given member.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(2)\n" + " |> contains(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{\n" + " new()\n" + " |> insert(2)\n" + " |> contains(1)\n" + " }\n" + " ```\n" +). +-spec contains(set(CVV), CVV) -> boolean(). +contains(Set, Member) -> + _pipe = erlang:element(2, Set), + _pipe@1 = gleam_stdlib:map_get(_pipe, Member), + gleam@result:is_ok(_pipe@1). + +-file("src/gleam/set.gleam", 130). +?DOC( + " Removes a member from a set. If the set does not contain the member then\n" + " the set is returned unchanged.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !{\n" + " new()\n" + " |> insert(2)\n" + " |> delete(2)\n" + " |> contains(2)\n" + " }\n" + " ```\n" +). +-spec delete(set(CVX), CVX) -> set(CVX). +delete(Set, Member) -> + {set, gleam@dict:delete(erlang:element(2, Set), Member)}. + +-file("src/gleam/set.gleam", 147). +?DOC( + " Converts the set into a list of the contained members.\n" + "\n" + " The list has no specific ordering, any unintentional ordering may change in\n" + " future versions of Gleam or Erlang.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(2) |> to_list == [2]\n" + " ```\n" +). +-spec to_list(set(CWA)) -> list(CWA). +to_list(Set) -> + maps:keys(erlang:element(2, Set)). + +-file("src/gleam/set.gleam", 191). +?DOC( + " Combines all entries into a single value by calling a given function on each\n" + " one.\n" + "\n" + " Sets are not ordered so the values are not returned in any specific order.\n" + " Do not write code that relies on the order entries are used by this\n" + " function as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 3, 9])\n" + " |> fold(0, fn(accumulator, member) { accumulator + member })\n" + " == 13\n" + " ```\n" +). +-spec fold(set(CWG), CWI, fun((CWI, CWG) -> CWI)) -> CWI. +fold(Set, Initial, Reducer) -> + gleam@dict:fold( + erlang:element(2, Set), + Initial, + fun(A, K, _) -> Reducer(A, K) end + ). + +-file("src/gleam/set.gleam", 215). +?DOC( + " Creates a new set from an existing set, minus any members that a given\n" + " function returns `False` for.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert from_list([1, 4, 6, 3, 675, 44, 67])\n" + " |> filter(keeping: int.is_even)\n" + " |> to_list\n" + " == [4, 6, 44]\n" + " ```\n" +). +-spec filter(set(CWJ), fun((CWJ) -> boolean())) -> set(CWJ). +filter(Set, Predicate) -> + {set, + gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}. + +-file("src/gleam/set.gleam", 252). +?DOC( + " Creates a new set from a given set with all the same entries except any\n" + " entry found on the given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3, 4])\n" + " |> drop([1, 3])\n" + " |> to_list\n" + " == [2, 4]\n" + " ```\n" +). +-spec drop(set(CWQ), list(CWQ)) -> set(CWQ). +drop(Set, Disallowed) -> + gleam@list:fold(Disallowed, Set, fun delete/2). + +-file("src/gleam/set.gleam", 270). +?DOC( + " Creates a new set from a given set, only including any members which are in\n" + " a given list.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3])\n" + " |> take([1, 3, 5])\n" + " |> to_list\n" + " == [1, 3]\n" + " ```\n" +). +-spec take(set(CWU), list(CWU)) -> set(CWU). +take(Set, Desired) -> + {set, gleam@dict:take(erlang:element(2, Set), Desired)}. + +-file("src/gleam/set.gleam", 290). +-spec order(set(CXC), set(CXC)) -> {set(CXC), set(CXC)}. +order(First, Second) -> + case maps:size(erlang:element(2, First)) > maps:size( + erlang:element(2, Second) + ) of + true -> + {First, Second}; + + false -> + {Second, First} + end. + +-file("src/gleam/set.gleam", 308). +?DOC( + " Creates a new set that contains members that are present in both given sets.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert intersection(from_list([1, 2]), from_list([2, 3])) |> to_list\n" + " == [2]\n" + " ```\n" +). +-spec intersection(set(CXH), set(CXH)) -> set(CXH). +intersection(First, Second) -> + {Larger, Smaller} = order(First, Second), + take(Larger, to_list(Smaller)). + +-file("src/gleam/set.gleam", 326). +?DOC( + " Creates a new set that contains members that are present in the first set\n" + " but not the second.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list\n" + " == [1]\n" + " ```\n" +). +-spec difference(set(CXL), set(CXL)) -> set(CXL). +difference(First, Second) -> + drop(First, to_list(Second)). + +-file("src/gleam/set.gleam", 345). +?DOC( + " Determines if a set is fully contained by another.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_subset(from_list([1]), from_list([1, 2]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_subset(from_list([1, 2, 3]), from_list([3, 4, 5]))\n" + " ```\n" +). +-spec is_subset(set(CXP), set(CXP)) -> boolean(). +is_subset(First, Second) -> + intersection(First, Second) =:= First. + +-file("src/gleam/set.gleam", 361). +?DOC( + " Determines if two sets contain no common members\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5]))\n" + " ```\n" +). +-spec is_disjoint(set(CXS), set(CXS)) -> boolean(). +is_disjoint(First, Second) -> + intersection(First, Second) =:= new(). + +-file("src/gleam/set.gleam", 405). +?DOC( + " Calls a function for each member in a set, discarding the return\n" + " value.\n" + "\n" + " Useful for producing a side effect for every item of a set.\n" + "\n" + " The order of elements in the iteration is an implementation detail that\n" + " should not be relied upon.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let set = from_list([\"apple\", \"banana\", \"cherry\"])\n" + "\n" + " assert each(set, io.println) == Nil\n" + " // apple\n" + " // banana\n" + " // cherry\n" + " ```\n" +). +-spec each(set(CXZ), fun((CXZ) -> any())) -> nil. +each(Set, Fun) -> + fold( + Set, + nil, + fun(Nil, Member) -> + Fun(Member), + Nil + end + ). + +-file("src/gleam/set.gleam", 84). +?DOC( + " Inserts a member into the set.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(1)\n" + " |> insert(2)\n" + " |> size\n" + " == 2\n" + " ```\n" +). +-spec insert(set(CVS), CVS) -> set(CVS). +insert(Set, Member) -> + {set, gleam@dict:insert(erlang:element(2, Set), Member, [])}. + +-file("src/gleam/set.gleam", 168). +?DOC( + " Creates a new set of the members in a given list.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + " import gleam/list\n" + "\n" + " assert [1, 1, 2, 4, 3, 2]\n" + " |> from_list\n" + " |> to_list\n" + " |> list.sort(by: int.compare)\n" + " == [1, 2, 3, 4]\n" + " ```\n" +). +-spec from_list(list(CWD)) -> set(CWD). +from_list(Members) -> + Dict = gleam@list:fold( + Members, + maps:new(), + fun(M, K) -> gleam@dict:insert(M, K, []) end + ), + {set, Dict}. + +-file("src/gleam/set.gleam", 234). +?DOC( + " Creates a new set from a given set with the result of applying the given\n" + " function to each member.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3, 4])\n" + " |> map(with: fn(x) { x * 2 })\n" + " |> to_list\n" + " == [2, 4, 6, 8]\n" + " ```\n" +). +-spec map(set(CWM), fun((CWM) -> CWO)) -> set(CWO). +map(Set, Fun) -> + fold(Set, new(), fun(Acc, Member) -> insert(Acc, Fun(Member)) end). + +-file("src/gleam/set.gleam", 285). +?DOC( + " Creates a new set that contains all members of both given sets.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert union(from_list([1, 2]), from_list([2, 3])) |> to_list\n" + " == [1, 2, 3]\n" + " ```\n" +). +-spec union(set(CWY), set(CWY)) -> set(CWY). +union(First, Second) -> + {Larger, Smaller} = order(First, Second), + fold(Smaller, Larger, fun insert/2). + +-file("src/gleam/set.gleam", 376). +?DOC( + " Creates a new set that contains members that are present in either set, but\n" + " not both.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert symmetric_difference(from_list([1, 2, 3]), from_list([3, 4]))\n" + " |> to_list\n" + " == [1, 2, 4]\n" + " ```\n" +). +-spec symmetric_difference(set(CXV), set(CXV)) -> set(CXV). +symmetric_difference(First, Second) -> + difference(union(First, Second), intersection(First, Second)). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string.erl new file mode 100644 index 0000000..410b9ba --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string.erl @@ -0,0 +1,1006 @@ +-module(gleam@string). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/string.gleam"). +-export([is_empty/1, length/1, reverse/1, replace/3, lowercase/1, uppercase/1, compare/2, slice/3, crop/2, drop_end/2, contains/2, starts_with/2, ends_with/2, split_once/2, append/2, concat/1, repeat/2, join/2, pad_start/3, pad_end/3, trim_start/1, trim_end/1, trim/1, pop_grapheme/1, to_graphemes/1, split/2, to_utf_codepoints/1, from_utf_codepoints/1, utf_codepoint/1, utf_codepoint_to_int/1, to_option/1, first/1, last/1, capitalise/1, inspect/1, byte_size/1, drop_start/2, remove_prefix/2, remove_suffix/2]). +-export_type([direction/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Strings in Gleam are UTF-8 binaries. They can be written in your code as\n" + " text surrounded by `\"double quotes\"`.\n" +). + +-type direction() :: leading | trailing. + +-file("src/gleam/string.gleam", 21). +?DOC( + " Determines if a `String` is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_empty(\"\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty(\"the world\")\n" + " ```\n" +). +-spec is_empty(binary()) -> boolean(). +is_empty(Str) -> + Str =:= <<""/utf8>>. + +-file("src/gleam/string.gleam", 46). +?DOC( + " Gets the number of grapheme clusters in a given `String`.\n" + "\n" + " This function has to iterate across the whole string to count the number of\n" + " graphemes, so it runs in linear time. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert length(\"Gleam\") == 5\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length(\"ß↑e̊\") == 3\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length(\"\") == 0\n" + " ```\n" +). +-spec length(binary()) -> integer(). +length(String) -> + string:length(String). + +-file("src/gleam/string.gleam", 59). +?DOC( + " Reverses a `String`.\n" + "\n" + " This function has to iterate across the whole `String` so it runs in linear\n" + " time. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert reverse(\"stressed\") == \"desserts\"\n" + " ```\n" +). +-spec reverse(binary()) -> binary(). +reverse(String) -> + _pipe = String, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = string:reverse(_pipe@1), + unicode:characters_to_binary(_pipe@2). + +-file("src/gleam/string.gleam", 78). +?DOC( + " Creates a new `String` by replacing all occurrences of a given substring.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace(\"www.example.com\", each: \".\", with: \"-\") == \"www-example-com\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace(\"a,b,c,d,e\", each: \",\", with: \"/\") == \"a/b/c/d/e\"\n" + " ```\n" +). +-spec replace(binary(), binary(), binary()) -> binary(). +replace(String, Pattern, Substitute) -> + _pipe = String, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = gleam_stdlib:string_replace(_pipe@1, Pattern, Substitute), + unicode:characters_to_binary(_pipe@2). + +-file("src/gleam/string.gleam", 102). +?DOC( + " Creates a new `String` with all the graphemes in the input `String` converted to\n" + " lowercase.\n" + "\n" + " Useful for case-insensitive comparisons.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lowercase(\"X-FILES\") == \"x-files\"\n" + " ```\n" +). +-spec lowercase(binary()) -> binary(). +lowercase(String) -> + string:lowercase(String). + +-file("src/gleam/string.gleam", 117). +?DOC( + " Creates a new `String` with all the graphemes in the input `String` converted to\n" + " uppercase.\n" + "\n" + " Useful for case-insensitive comparisons and VIRTUAL YELLING.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert uppercase(\"skinner\") == \"SKINNER\"\n" + " ```\n" +). +-spec uppercase(binary()) -> binary(). +uppercase(String) -> + string:uppercase(String). + +-file("src/gleam/string.gleam", 137). +?DOC( + " Compares two `String`s to see which is \"larger\" by comparing their graphemes.\n" + "\n" + " This does not compare the size or length of the given `String`s.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/order\n" + "\n" + " assert compare(\"Anthony\", \"Anthony\") == order.Eq\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/order\n" + "\n" + " assert compare(\"A\", \"B\") == order.Lt\n" + " ```\n" +). +-spec compare(binary(), binary()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + _ -> + case gleam_stdlib:less_than(A, B) of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/string.gleam", 181). +?DOC( + " Takes a substring given a start grapheme index and a length. Negative indexes\n" + " are taken starting from the *end* of the string.\n" + "\n" + " This function runs in linear time with the size of the index and the\n" + " length. Negative indexes are linear with the size of the input string in\n" + " addition to the other costs.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 1, length: 2) == \"le\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 1, length: 10) == \"leam\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 10, length: 3) == \"\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: -2, length: 2) == \"am\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: -12, length: 2) == \"\"\n" + " ```\n" +). +-spec slice(binary(), integer(), integer()) -> binary(). +slice(String, Idx, Len) -> + case Len =< 0 of + true -> + <<""/utf8>>; + + false -> + case Idx < 0 of + true -> + Translated_idx = string:length(String) + Idx, + case Translated_idx < 0 of + true -> + <<""/utf8>>; + + false -> + gleam_stdlib:slice(String, Translated_idx, Len) + end; + + false -> + gleam_stdlib:slice(String, Idx, Len) + end + end. + +-file("src/gleam/string.gleam", 218). +?DOC( + " Drops contents of the first `String` that occur before the second `String`.\n" + " If the `from` string does not contain the `before` string, `from` is\n" + " returned unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert crop(from: \"The Lone Gunmen\", before: \"Lone\") == \"Lone Gunmen\"\n" + " ```\n" +). +-spec crop(binary(), binary()) -> binary(). +crop(String, Substring) -> + gleam_stdlib:crop_string(String, Substring). + +-file("src/gleam/string.gleam", 253). +?DOC( + " Drops *n* graphemes from the end of a `String`.\n" + "\n" + " This function traverses the full string, so it runs in linear time with the\n" + " size of the string. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_end(from: \"Cigarette Smoking Man\", up_to: 2)\n" + " == \"Cigarette Smoking M\"\n" + " ```\n" +). +-spec drop_end(binary(), integer()) -> binary(). +drop_end(String, Num_graphemes) -> + case Num_graphemes =< 0 of + true -> + String; + + false -> + slice(String, 0, string:length(String) - Num_graphemes) + end. + +-file("src/gleam/string.gleam", 278). +?DOC( + " Checks if the first `String` contains the second.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert contains(does: \"theory\", contain: \"ory\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert contains(does: \"theory\", contain: \"the\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains(does: \"theory\", contain: \"THE\")\n" + " ```\n" +). +-spec contains(binary(), binary()) -> boolean(). +contains(Haystack, Needle) -> + gleam_stdlib:contains_string(Haystack, Needle). + +-file("src/gleam/string.gleam", 290). +?DOC( + " Checks whether the first `String` starts with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !starts_with(\"theory\", \"ory\")\n" + " ```\n" +). +-spec starts_with(binary(), binary()) -> boolean(). +starts_with(String, Prefix) -> + gleam_stdlib:string_starts_with(String, Prefix). + +-file("src/gleam/string.gleam", 302). +?DOC( + " Checks whether the first `String` ends with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert ends_with(\"theory\", \"ory\")\n" + " ```\n" +). +-spec ends_with(binary(), binary()) -> boolean(). +ends_with(String, Suffix) -> + gleam_stdlib:string_ends_with(String, Suffix). + +-file("src/gleam/string.gleam", 340). +?DOC( + " Splits a `String` a single time on the given substring.\n" + "\n" + " Returns an `Error` if substring not present.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split_once(\"home/gleam/desktop/\", on: \"/\")\n" + " == Ok(#(\"home\", \"gleam/desktop/\"))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split_once(\"home/gleam/desktop/\", on: \"?\") == Error(Nil)\n" + " ```\n" +). +-spec split_once(binary(), binary()) -> {ok, {binary(), binary()}} | + {error, nil}. +split_once(String, Substring) -> + case string:split(String, Substring) of + [First, Rest] -> + {ok, {First, Rest}}; + + _ -> + {error, nil} + end. + +-file("src/gleam/string.gleam", 370). +?DOC( + " Creates a new `String` by joining two `String`s together.\n" + "\n" + " This function typically copies both `String`s and runs in linear time, but\n" + " the exact behaviour will depend on how the runtime you are using optimises\n" + " your code. Benchmark and profile your code if you need to understand its\n" + " performance better.\n" + "\n" + " If you are joining together large string and want to avoid copying any data\n" + " you may want to investigate using the [`string_tree`](../gleam/string_tree.html)\n" + " module.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append(to: \"butter\", suffix: \"fly\") == \"butterfly\"\n" + " ```\n" +). +-spec append(binary(), binary()) -> binary(). +append(First, Second) -> + <>. + +-file("src/gleam/string.gleam", 389). +-spec concat_loop(list(binary()), binary()) -> binary(). +concat_loop(Strings, Accumulator) -> + case Strings of + [String | Strings@1] -> + concat_loop(Strings@1, <>); + + [] -> + Accumulator + end. + +-file("src/gleam/string.gleam", 385). +?DOC( + " Creates a new `String` by joining many `String`s together.\n" + "\n" + " This function copies all the `String`s and runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert concat([\"never\", \"the\", \"less\"]) == \"nevertheless\"\n" + " ```\n" +). +-spec concat(list(binary())) -> binary(). +concat(Strings) -> + erlang:list_to_binary(Strings). + +-file("src/gleam/string.gleam", 413). +-spec repeat_loop(integer(), binary(), binary()) -> binary(). +repeat_loop(Times, Doubling_acc, Acc) -> + Acc@1 = case Times rem 2 of + 0 -> + Acc; + + _ -> + <> + end, + Times@1 = Times div 2, + case Times@1 =< 0 of + true -> + Acc@1; + + false -> + repeat_loop( + Times@1, + <>, + Acc@1 + ) + end. + +-file("src/gleam/string.gleam", 406). +?DOC( + " Creates a new `String` by repeating a `String` a given number of times.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert repeat(\"ha\", times: 3) == \"hahaha\"\n" + " ```\n" +). +-spec repeat(binary(), integer()) -> binary(). +repeat(String, Times) -> + case Times =< 0 of + true -> + <<""/utf8>>; + + false -> + repeat_loop(Times, String, <<""/utf8>>) + end. + +-file("src/gleam/string.gleam", 442). +-spec join_loop(list(binary()), binary(), binary()) -> binary(). +join_loop(Strings, Separator, Accumulator) -> + case Strings of + [] -> + Accumulator; + + [String | Strings@1] -> + join_loop( + Strings@1, + Separator, + <<<>/binary, + String/binary>> + ) + end. + +-file("src/gleam/string.gleam", 435). +?DOC( + " Joins many `String`s together with a given separator.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert join([\"home\",\"evan\",\"Desktop\"], with: \"/\") == \"home/evan/Desktop\"\n" + " ```\n" +). +-spec join(list(binary()), binary()) -> binary(). +join(Strings, Separator) -> + case Strings of + [] -> + <<""/utf8>>; + + [First | Rest] -> + join_loop(Rest, Separator, First) + end. + +-file("src/gleam/string.gleam", 514). +-spec padding(integer(), binary()) -> binary(). +padding(Size, Pad_string) -> + Pad_string_length = string:length(Pad_string), + Num_pads = case Pad_string_length of + 0 -> 0; + Gleam@denominator -> Size div Gleam@denominator + end, + Extra = case Pad_string_length of + 0 -> 0; + Gleam@denominator@1 -> Size rem Gleam@denominator@1 + end, + <<(repeat(Pad_string, Num_pads))/binary, + (slice(Pad_string, 0, Extra))/binary>>. + +-file("src/gleam/string.gleam", 470). +?DOC( + " Pads the start of a `String` until it has a given length.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 5, with: \".\") == \"..121\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 3, with: \".\") == \"121\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 2, with: \".\") == \"121\"\n" + " ```\n" +). +-spec pad_start(binary(), integer(), binary()) -> binary(). +pad_start(String, Desired_length, Pad_string) -> + Current_length = string:length(String), + To_pad_length = Desired_length - Current_length, + case To_pad_length =< 0 of + true -> + String; + + false -> + <<(padding(To_pad_length, Pad_string))/binary, String/binary>> + end. + +-file("src/gleam/string.gleam", 500). +?DOC( + " Pads the end of a `String` until it has a given length.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 5, with: \".\") == \"123..\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 3, with: \".\") == \"123\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 2, with: \".\") == \"123\"\n" + " ```\n" +). +-spec pad_end(binary(), integer(), binary()) -> binary(). +pad_end(String, Desired_length, Pad_string) -> + Current_length = string:length(String), + To_pad_length = Desired_length - Current_length, + case To_pad_length =< 0 of + true -> + String; + + false -> + <> + end. + +-file("src/gleam/string.gleam", 556). +?DOC( + " Removes whitespace at the start of a `String`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim_start(\" hats \\n\") == \"hats \\n\"\n" + " ```\n" +). +-spec trim_start(binary()) -> binary(). +trim_start(String) -> + string:trim(String, leading). + +-file("src/gleam/string.gleam", 569). +?DOC( + " Removes whitespace at the end of a `String`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim_end(\" hats \\n\") == \" hats\"\n" + " ```\n" +). +-spec trim_end(binary()) -> binary(). +trim_end(String) -> + string:trim(String, trailing). + +-file("src/gleam/string.gleam", 535). +?DOC( + " Removes whitespace on both sides of a `String`.\n" + "\n" + " Whitespace in this function is the set of nonbreakable whitespace\n" + " codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1].\n" + "\n" + " [1]: https://unicode.org/reports/tr31/\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim(\" hats \\n\") == \"hats\"\n" + " ```\n" +). +-spec trim(binary()) -> binary(). +trim(String) -> + _pipe = String, + _pipe@1 = trim_start(_pipe), + trim_end(_pipe@1). + +-file("src/gleam/string.gleam", 594). +?DOC( + " Splits a non-empty `String` into its first element (head) and rest (tail).\n" + " This lets you pattern match on `String`s exactly as you would with lists.\n" + "\n" + " ## Performance\n" + "\n" + " There is a notable overhead to using this function, so you may not want to\n" + " use it in a tight loop. If you wish to efficiently parse a string you may\n" + " want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter).\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pop_grapheme(\"gleam\") == Ok(#(\"g\", \"leam\"))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pop_grapheme(\"\") == Error(Nil)\n" + " ```\n" +). +-spec pop_grapheme(binary()) -> {ok, {binary(), binary()}} | {error, nil}. +pop_grapheme(String) -> + gleam_stdlib:string_pop_grapheme(String). + +-file("src/gleam/string.gleam", 610). +-spec to_graphemes_loop(binary(), list(binary())) -> list(binary()). +to_graphemes_loop(String, Acc) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {Grapheme, Rest}} -> + to_graphemes_loop(Rest, [Grapheme | Acc]); + + {error, _} -> + Acc + end. + +-file("src/gleam/string.gleam", 604). +?DOC( + " Converts a `String` to a list of\n" + " [graphemes](https://en.wikipedia.org/wiki/Grapheme).\n" + "\n" + " ```gleam\n" + " assert to_graphemes(\"abc\") == [\"a\", \"b\", \"c\"]\n" + " ```\n" +). +-spec to_graphemes(binary()) -> list(binary()). +to_graphemes(String) -> + _pipe = String, + _pipe@1 = to_graphemes_loop(_pipe, []), + lists:reverse(_pipe@1). + +-file("src/gleam/string.gleam", 313). +?DOC( + " Creates a list of `String`s by splitting a given string on a given substring.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split(\"home/gleam/desktop/\", on: \"/\")\n" + " == [\"home\", \"gleam\", \"desktop\", \"\"]\n" + " ```\n" +). +-spec split(binary(), binary()) -> list(binary()). +split(X, Substring) -> + case Substring of + <<""/utf8>> -> + to_graphemes(X); + + _ -> + _pipe = X, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = gleam@string_tree:split(_pipe@1, Substring), + gleam@list:map(_pipe@2, fun unicode:characters_to_binary/1) + end. + +-file("src/gleam/string.gleam", 656). +-spec to_utf_codepoints_loop(bitstring(), list(integer())) -> list(integer()). +to_utf_codepoints_loop(Bit_array, Acc) -> + case Bit_array of + <> -> + to_utf_codepoints_loop(Rest, [First | Acc]); + + _ -> + lists:reverse(Acc) + end. + +-file("src/gleam/string.gleam", 651). +-spec do_to_utf_codepoints(binary()) -> list(integer()). +do_to_utf_codepoints(String) -> + to_utf_codepoints_loop(<>, []). + +-file("src/gleam/string.gleam", 646). +?DOC( + " Converts a `String` to a `List` of `UtfCodepoint`.\n" + "\n" + " See and\n" + " for an\n" + " explanation on code points.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert \"a\" |> to_utf_codepoints == [UtfCodepoint(97)]\n" + " ```\n" + "\n" + " ```gleam\n" + " // Semantically the same as:\n" + " // [\"🏳\", \"️\", \"‍\", \"🌈\"] or:\n" + " // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow]\n" + " assert \"🏳️‍🌈\" |> to_utf_codepoints\n" + " == [\n" + " UtfCodepoint(127987),\n" + " UtfCodepoint(65039),\n" + " UtfCodepoint(8205),\n" + " UtfCodepoint(127752),\n" + " ]\n" + " ```\n" +). +-spec to_utf_codepoints(binary()) -> list(integer()). +to_utf_codepoints(String) -> + do_to_utf_codepoints(String). + +-file("src/gleam/string.gleam", 695). +?DOC( + " Converts a `List` of `UtfCodepoint`s to a `String`.\n" + "\n" + " See and\n" + " for an\n" + " explanation on code points.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert Ok(a) = utf_codepoint(97)\n" + " let assert Ok(b) = utf_codepoint(98)\n" + " let assert Ok(c) = utf_codepoint(99)\n" + " assert from_utf_codepoints([a, b, c]) == \"abc\"\n" + " ```\n" +). +-spec from_utf_codepoints(list(integer())) -> binary(). +from_utf_codepoints(Utf_codepoints) -> + gleam_stdlib:utf_codepoint_list_to_string(Utf_codepoints). + +-file("src/gleam/string.gleam", 701). +?DOC( + " Converts an integer to a `UtfCodepoint`.\n" + "\n" + " Returns an `Error` if the integer does not represent a valid UTF codepoint.\n" +). +-spec utf_codepoint(integer()) -> {ok, integer()} | {error, nil}. +utf_codepoint(Value) -> + case Value of + I when I > 1114111 -> + {error, nil}; + + I@1 when (I@1 >= 55296) andalso (I@1 =< 57343) -> + {error, nil}; + + I@2 when I@2 < 0 -> + {error, nil}; + + I@3 -> + {ok, gleam_stdlib:identity(I@3)} + end. + +-file("src/gleam/string.gleam", 721). +?DOC( + " Converts a `UtfCodepoint` to its ordinal code point value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert [utf_codepoint, ..] = to_utf_codepoints(\"💜\")\n" + " assert utf_codepoint_to_int(utf_codepoint) == 128156\n" + " ```\n" +). +-spec utf_codepoint_to_int(integer()) -> integer(). +utf_codepoint_to_int(Cp) -> + gleam_stdlib:identity(Cp). + +-file("src/gleam/string.gleam", 736). +?DOC( + " Converts a `String` into `Option(String)` where an empty `String` becomes\n" + " `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_option(\"\") == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_option(\"hats\") == Some(\"hats\")\n" + " ```\n" +). +-spec to_option(binary()) -> gleam@option:option(binary()). +to_option(String) -> + case String of + <<""/utf8>> -> + none; + + _ -> + {some, String} + end. + +-file("src/gleam/string.gleam", 757). +?DOC( + " Returns the first grapheme cluster in a given `String` and wraps it in a\n" + " `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n" + " Otherwise, it returns `Ok(String)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first(\"\") == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first(\"icecream\") == Ok(\"i\")\n" + " ```\n" +). +-spec first(binary()) -> {ok, binary()} | {error, nil}. +first(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, _}} -> + {ok, First}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/string.gleam", 781). +?DOC( + " Returns the last grapheme cluster in a given `String` and wraps it in a\n" + " `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n" + " Otherwise, it returns `Ok(String)`.\n" + "\n" + " This function traverses the full string, so it runs in linear time with the\n" + " length of the string. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert last(\"\") == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert last(\"icecream\") == Ok(\"m\")\n" + " ```\n" +). +-spec last(binary()) -> {ok, binary()} | {error, nil}. +last(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, <<""/utf8>>}} -> + {ok, First}; + + {ok, {_, Rest}} -> + {ok, slice(Rest, -1, 1)}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/string.gleam", 798). +?DOC( + " Creates a new `String` with the first grapheme in the input `String`\n" + " converted to uppercase and the remaining graphemes to lowercase.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert capitalise(\"mamouna\") == \"Mamouna\"\n" + " ```\n" +). +-spec capitalise(binary()) -> binary(). +capitalise(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, Rest}} -> + append(string:uppercase(First), string:lowercase(Rest)); + + {error, _} -> + <<""/utf8>> + end. + +-file("src/gleam/string.gleam", 829). +?DOC( + " Returns a `String` representation of a term in Gleam syntax.\n" + "\n" + " This may be occasionally useful for quick-and-dirty printing of values in\n" + " scripts. For error reporting and other uses prefer constructing strings by\n" + " pattern matching on the values.\n" + "\n" + " ## Limitations\n" + "\n" + " The output format of this function is not stable and could change at any\n" + " time. The output is not suitable for parsing.\n" + "\n" + " This function works using runtime reflection, so the output may not be\n" + " perfectly accurate for data structures where the runtime structure doesn't\n" + " hold enough information to determine the original syntax. For example,\n" + " tuples with an Erlang atom in the first position will be mistaken for Gleam\n" + " records.\n" + "\n" + " ## Security and safety\n" + "\n" + " There is no limit to how large the strings that this function can produce.\n" + " Be careful not to call this function with large data structures or you\n" + " could use very large amounts of memory, potentially causing runtime\n" + " problems.\n" +). +-spec inspect(any()) -> binary(). +inspect(Term) -> + _pipe = Term, + _pipe@1 = gleam_stdlib:inspect(_pipe), + unicode:characters_to_binary(_pipe@1). + +-file("src/gleam/string.gleam", 852). +?DOC( + " Returns the number of bytes in a `String`.\n" + "\n" + " This function runs in constant time on Erlang and in linear time on\n" + " JavaScript.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert byte_size(\"🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻\") == 58\n" + " ```\n" +). +-spec byte_size(binary()) -> integer(). +byte_size(String) -> + erlang:byte_size(String). + +-file("src/gleam/string.gleam", 230). +?DOC( + " Drops *n* graphemes from the start of a `String`.\n" + "\n" + " This function runs in linear time with the number of graphemes to drop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_start(from: \"The Lone Gunmen\", up_to: 2) == \"e Lone Gunmen\"\n" + " ```\n" +). +-spec drop_start(binary(), integer()) -> binary(). +drop_start(String, Num_graphemes) -> + case Num_graphemes =< 0 of + true -> + String; + + false -> + Prefix = gleam_stdlib:slice(String, 0, Num_graphemes), + Prefix_size = erlang:byte_size(Prefix), + binary:part( + String, + Prefix_size, + erlang:byte_size(String) - Prefix_size + ) + end. + +-file("src/gleam/string.gleam", 871). +?DOC( + " Removes the given prefix from the start of a `String`, if present.\n" + "\n" + " If the `String` does not start with the given prefix the string is returned\n" + " unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remove_prefix(\"@lpil\", \"@\") == \"lpil\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remove_prefix(\"hello!\", \"@\") == \"hello!\"\n" + " ```\n" +). +-spec remove_prefix(binary(), binary()) -> binary(). +remove_prefix(String, Prefix) -> + gleam_stdlib:string_remove_prefix(String, Prefix). + +-file("src/gleam/string.gleam", 890). +?DOC( + " Removes the given suffix from the end of a `String`, if present.\n" + "\n" + " If the `String` does not end with the given suffix the string is returned\n" + " unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remove_suffix(\"Hello!\", \"!\") == \"Hello\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remove_suffix(\"Hello!?\", \"!\") == \"Hello!?\"\n" + " ```\n" +). +-spec remove_suffix(binary(), binary()) -> binary(). +remove_suffix(String, Suffix) -> + gleam_stdlib:string_remove_suffix(String, Suffix). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string_tree.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string_tree.erl new file mode 100644 index 0000000..0720f3b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@string_tree.erl @@ -0,0 +1,202 @@ +-module(gleam@string_tree). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/string_tree.gleam"). +-export([append_tree/2, prepend_tree/2, from_strings/1, new/0, concat/1, from_string/1, prepend/2, append/2, to_string/1, byte_size/1, join/2, lowercase/1, uppercase/1, reverse/1, split/2, replace/3, is_equal/2, is_empty/1]). +-export_type([string_tree/0, direction/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type string_tree() :: any(). + +-type direction() :: all. + +-file("src/gleam/string_tree.gleam", 61). +?DOC( + " Appends some `StringTree` onto the end of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec append_tree(string_tree(), string_tree()) -> string_tree(). +append_tree(Tree, Suffix) -> + gleam_stdlib:iodata_append(Tree, Suffix). + +-file("src/gleam/string_tree.gleam", 48). +?DOC( + " Prepends some `StringTree` onto the start of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend_tree(string_tree(), string_tree()) -> string_tree(). +prepend_tree(Tree, Prefix) -> + gleam_stdlib:iodata_append(Prefix, Tree). + +-file("src/gleam/string_tree.gleam", 69). +?DOC( + " Converts a list of strings into a `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_strings(list(binary())) -> string_tree(). +from_strings(Strings) -> + gleam_stdlib:identity(Strings). + +-file("src/gleam/string_tree.gleam", 24). +?DOC( + " Create an empty `StringTree`. Useful as the start of a pipe chaining many\n" + " trees together.\n" +). +-spec new() -> string_tree(). +new() -> + gleam_stdlib:identity([]). + +-file("src/gleam/string_tree.gleam", 77). +?DOC( + " Joins a list of trees into a single tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat(list(string_tree())) -> string_tree(). +concat(Trees) -> + gleam_stdlib:identity(Trees). + +-file("src/gleam/string_tree.gleam", 85). +?DOC( + " Converts a string into a `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_string(binary()) -> string_tree(). +from_string(String) -> + gleam_stdlib:identity(String). + +-file("src/gleam/string_tree.gleam", 32). +?DOC( + " Prepends a `String` onto the start of some `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend(string_tree(), binary()) -> string_tree(). +prepend(Tree, Prefix) -> + gleam_stdlib:iodata_append(gleam_stdlib:identity(Prefix), Tree). + +-file("src/gleam/string_tree.gleam", 40). +?DOC( + " Appends a `String` onto the end of some `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec append(string_tree(), binary()) -> string_tree(). +append(Tree, Second) -> + gleam_stdlib:iodata_append(Tree, gleam_stdlib:identity(Second)). + +-file("src/gleam/string_tree.gleam", 94). +?DOC( + " Turns a `StringTree` into a `String`.\n" + "\n" + " This function is implemented natively by the virtual machine and is highly\n" + " optimised.\n" +). +-spec to_string(string_tree()) -> binary(). +to_string(Tree) -> + unicode:characters_to_binary(Tree). + +-file("src/gleam/string_tree.gleam", 100). +?DOC(" Returns the size of the `StringTree` in bytes.\n"). +-spec byte_size(string_tree()) -> integer(). +byte_size(Tree) -> + erlang:iolist_size(Tree). + +-file("src/gleam/string_tree.gleam", 104). +?DOC(" Joins the given trees into a new tree separated with the given string.\n"). +-spec join(list(string_tree()), binary()) -> string_tree(). +join(Trees, Sep) -> + _pipe = Trees, + _pipe@1 = gleam@list:intersperse(_pipe, gleam_stdlib:identity(Sep)), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/string_tree.gleam", 115). +?DOC( + " Converts a `StringTree` to a new one where the contents have been\n" + " lowercased.\n" +). +-spec lowercase(string_tree()) -> string_tree(). +lowercase(Tree) -> + string:lowercase(Tree). + +-file("src/gleam/string_tree.gleam", 122). +?DOC( + " Converts a `StringTree` to a new one where the contents have been\n" + " uppercased.\n" +). +-spec uppercase(string_tree()) -> string_tree(). +uppercase(Tree) -> + string:uppercase(Tree). + +-file("src/gleam/string_tree.gleam", 127). +?DOC(" Converts a `StringTree` to a new one with the contents reversed.\n"). +-spec reverse(string_tree()) -> string_tree(). +reverse(Tree) -> + string:reverse(Tree). + +-file("src/gleam/string_tree.gleam", 145). +?DOC(" Splits a `StringTree` on a given pattern into a list of trees.\n"). +-spec split(string_tree(), binary()) -> list(string_tree()). +split(Tree, Pattern) -> + string:split(Tree, Pattern, all). + +-file("src/gleam/string_tree.gleam", 156). +?DOC(" Replaces all instances of a pattern with a given string substitute.\n"). +-spec replace(string_tree(), binary(), binary()) -> string_tree(). +replace(Tree, Pattern, Substitute) -> + gleam_stdlib:string_replace(Tree, Pattern, Substitute). + +-file("src/gleam/string_tree.gleam", 180). +?DOC( + " Compares two string trees to determine if they have the same textual\n" + " content.\n" + "\n" + " Comparing two string trees using the `==` operator may return `False` even\n" + " if they have the same content as they may have been build in different ways,\n" + " so using this function is often preferred.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_strings([\"a\", \"b\"]) != from_string(\"ab\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_equal(from_strings([\"a\", \"b\"]), from_string(\"ab\"))\n" + " ```\n" +). +-spec is_equal(string_tree(), string_tree()) -> boolean(). +is_equal(A, B) -> + string:equal(A, B). + +-file("src/gleam/string_tree.gleam", 201). +?DOC( + " Inspects a `StringTree` to determine if it is equivalent to an empty string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !{ from_string(\"ok\") |> is_empty }\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_string(\"\") |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_strings([]) |> is_empty\n" + " ```\n" +). +-spec is_empty(string_tree()) -> boolean(). +is_empty(Tree) -> + string:is_empty(Tree). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@uri.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@uri.erl new file mode 100644 index 0000000..7acb110 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam@uri.erl @@ -0,0 +1,1042 @@ +-module(gleam@uri). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/uri.gleam"). +-export([parse_query/1, percent_encode/1, query_to_string/1, percent_decode/1, path_segments/1, to_string/1, origin/1, merge/2, parse/1]). +-export_type([uri/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Utilities for working with URIs\n" + "\n" + " This module provides functions for working with URIs (for example, parsing\n" + " URIs or encoding query strings). The functions in this module are implemented\n" + " according to [RFC 3986](https://tools.ietf.org/html/rfc3986).\n" + "\n" + " Query encoding (Form encoding) is defined in the\n" + " [W3C specification](https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data).\n" +). + +-type uri() :: {uri, + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(integer()), + binary(), + gleam@option:option(binary()), + gleam@option:option(binary())}. + +-file("src/gleam/uri.gleam", 289). +-spec is_valid_host_within_brackets_char(integer()) -> boolean(). +is_valid_host_within_brackets_char(Char) -> + (((((48 >= Char) andalso (Char =< 57)) orelse ((65 >= Char) andalso (Char =< 90))) + orelse ((97 >= Char) andalso (Char =< 122))) + orelse (Char =:= 58)) + orelse (Char =:= 46). + +-file("src/gleam/uri.gleam", 506). +-spec parse_fragment(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_fragment(Rest, Pieces) -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + {some, Rest}}}. + +-file("src/gleam/uri.gleam", 478). +-spec parse_query_with_question_mark_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_query_with_question_mark_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"#"/utf8, Rest/binary>> when Size =:= 0 -> + parse_fragment(Rest, Pieces); + + <<"#"/utf8, Rest@1/binary>> -> + Query = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + {some, Query}, + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@1); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + {some, Original}, + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_query_with_question_mark_loop( + Original, + Rest@2, + Pieces, + Size + 1 + ) + end. + +-file("src/gleam/uri.gleam", 471). +-spec parse_query_with_question_mark(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_query_with_question_mark(Uri_string, Pieces) -> + parse_query_with_question_mark_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 437). +-spec parse_path_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_path_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"?"/utf8, Rest/binary>> -> + Path = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Path, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest, Pieces@1); + + <<"#"/utf8, Rest@1/binary>> -> + Path@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Path@1, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@2); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Original, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_path_loop(Original, Rest@2, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 433). +-spec parse_path(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_path(Uri_string, Pieces) -> + parse_path_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 388). +-spec parse_port_loop(binary(), uri(), integer()) -> {ok, uri()} | {error, nil}. +parse_port_loop(Uri_string, Pieces, Port) -> + case Uri_string of + <<"0"/utf8, Rest/binary>> -> + parse_port_loop(Rest, Pieces, Port * 10); + + <<"1"/utf8, Rest@1/binary>> -> + parse_port_loop(Rest@1, Pieces, (Port * 10) + 1); + + <<"2"/utf8, Rest@2/binary>> -> + parse_port_loop(Rest@2, Pieces, (Port * 10) + 2); + + <<"3"/utf8, Rest@3/binary>> -> + parse_port_loop(Rest@3, Pieces, (Port * 10) + 3); + + <<"4"/utf8, Rest@4/binary>> -> + parse_port_loop(Rest@4, Pieces, (Port * 10) + 4); + + <<"5"/utf8, Rest@5/binary>> -> + parse_port_loop(Rest@5, Pieces, (Port * 10) + 5); + + <<"6"/utf8, Rest@6/binary>> -> + parse_port_loop(Rest@6, Pieces, (Port * 10) + 6); + + <<"7"/utf8, Rest@7/binary>> -> + parse_port_loop(Rest@7, Pieces, (Port * 10) + 7); + + <<"8"/utf8, Rest@8/binary>> -> + parse_port_loop(Rest@8, Pieces, (Port * 10) + 8); + + <<"9"/utf8, Rest@9/binary>> -> + parse_port_loop(Rest@9, Pieces, (Port * 10) + 9); + + <<"?"/utf8, Rest@10/binary>> -> + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@10, Pieces@1); + + <<"#"/utf8, Rest@11/binary>> -> + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@11, Pieces@2); + + <<"/"/utf8, _/binary>> -> + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@3); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 353). +-spec parse_port(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_port(Uri_string, Pieces) -> + case Uri_string of + <<":0"/utf8, Rest/binary>> -> + parse_port_loop(Rest, Pieces, 0); + + <<":1"/utf8, Rest@1/binary>> -> + parse_port_loop(Rest@1, Pieces, 1); + + <<":2"/utf8, Rest@2/binary>> -> + parse_port_loop(Rest@2, Pieces, 2); + + <<":3"/utf8, Rest@3/binary>> -> + parse_port_loop(Rest@3, Pieces, 3); + + <<":4"/utf8, Rest@4/binary>> -> + parse_port_loop(Rest@4, Pieces, 4); + + <<":5"/utf8, Rest@5/binary>> -> + parse_port_loop(Rest@5, Pieces, 5); + + <<":6"/utf8, Rest@6/binary>> -> + parse_port_loop(Rest@6, Pieces, 6); + + <<":7"/utf8, Rest@7/binary>> -> + parse_port_loop(Rest@7, Pieces, 7); + + <<":8"/utf8, Rest@8/binary>> -> + parse_port_loop(Rest@8, Pieces, 8); + + <<":9"/utf8, Rest@9/binary>> -> + parse_port_loop(Rest@9, Pieces, 9); + + <<":"/utf8>> -> + {ok, Pieces}; + + <<""/utf8>> -> + {ok, Pieces}; + + <<"?"/utf8, Rest@10/binary>> -> + parse_query_with_question_mark(Rest@10, Pieces); + + <<":?"/utf8, Rest@10/binary>> -> + parse_query_with_question_mark(Rest@10, Pieces); + + <<"#"/utf8, Rest@11/binary>> -> + parse_fragment(Rest@11, Pieces); + + <<":#"/utf8, Rest@11/binary>> -> + parse_fragment(Rest@11, Pieces); + + <<"/"/utf8, _/binary>> -> + parse_path(Uri_string, Pieces); + + <<":"/utf8, Rest@12/binary>> -> + case Rest@12 of + <<"/"/utf8, _/binary>> -> + parse_path(Rest@12, Pieces); + + _ -> + {error, nil} + end; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 309). +-spec parse_host_outside_of_brackets_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_host_outside_of_brackets_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Original}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<":"/utf8, _/binary>> -> + Host = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Uri_string, Pieces@1); + + <<"/"/utf8, _/binary>> -> + Host@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@1}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@2); + + <<"?"/utf8, Rest/binary>> -> + Host@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@2}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest, Pieces@3); + + <<"#"/utf8, Rest@1/binary>> -> + Host@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@3}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@4); + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_host_outside_of_brackets_loop( + Original, + Rest@2, + Pieces, + Size + 1 + ) + end. + +-file("src/gleam/uri.gleam", 229). +-spec parse_host_within_brackets_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_host_within_brackets_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Uri_string}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<"]"/utf8, Rest/binary>> when Size =:= 0 -> + parse_port(Rest, Pieces); + + <<"]"/utf8, Rest@1/binary>> -> + Host = binary:part(Original, 0, Size + 1), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Rest@1, Pieces@1); + + <<"/"/utf8, _/binary>> when Size =:= 0 -> + parse_path(Uri_string, Pieces); + + <<"/"/utf8, _/binary>> -> + Host@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@1}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@2); + + <<"?"/utf8, Rest@2/binary>> when Size =:= 0 -> + parse_query_with_question_mark(Rest@2, Pieces); + + <<"?"/utf8, Rest@3/binary>> -> + Host@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@2}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@3, Pieces@3); + + <<"#"/utf8, Rest@4/binary>> when Size =:= 0 -> + parse_fragment(Rest@4, Pieces); + + <<"#"/utf8, Rest@5/binary>> -> + Host@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@3}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@5, Pieces@4); + + _ -> + {Char, Rest@6} = gleam_stdlib:string_pop_codeunit(Uri_string), + case is_valid_host_within_brackets_char(Char) of + true -> + parse_host_within_brackets_loop( + Original, + Rest@6, + Pieces, + Size + 1 + ); + + false -> + parse_host_outside_of_brackets_loop( + Original, + Original, + Pieces, + 0 + ) + end + end. + +-file("src/gleam/uri.gleam", 222). +-spec parse_host_within_brackets(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_host_within_brackets(Uri_string, Pieces) -> + parse_host_within_brackets_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 302). +-spec parse_host_outside_of_brackets(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_host_outside_of_brackets(Uri_string, Pieces) -> + parse_host_outside_of_brackets_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 199). +-spec parse_host(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_host(Uri_string, Pieces) -> + case Uri_string of + <<"["/utf8, _/binary>> -> + parse_host_within_brackets(Uri_string, Pieces); + + <<":"/utf8, _/binary>> -> + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Uri_string, Pieces@1); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + parse_host_outside_of_brackets(Uri_string, Pieces) + end. + +-file("src/gleam/uri.gleam", 167). +-spec parse_userinfo_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_userinfo_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"@"/utf8, Rest/binary>> when Size =:= 0 -> + parse_host(Rest, Pieces); + + <<"@"/utf8, Rest@1/binary>> -> + Userinfo = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + {some, Userinfo}, + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_host(Rest@1, Pieces@1); + + <<""/utf8>> -> + parse_host(Original, Pieces); + + <<"/"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + <<"?"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + <<"#"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_userinfo_loop(Original, Rest@2, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 163). +-spec parse_authority_pieces(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_authority_pieces(String, Pieces) -> + parse_userinfo_loop(String, String, Pieces, 0). + +-file("src/gleam/uri.gleam", 150). +-spec parse_authority_with_slashes(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_authority_with_slashes(Uri_string, Pieces) -> + case Uri_string of + <<"//"/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<"//"/utf8, Rest/binary>> -> + parse_authority_pieces(Rest, Pieces); + + _ -> + parse_path(Uri_string, Pieces) + end. + +-file("src/gleam/uri.gleam", 91). +-spec parse_scheme_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_scheme_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"/"/utf8, _/binary>> when Size =:= 0 -> + parse_authority_with_slashes(Uri_string, Pieces); + + <<"/"/utf8, _/binary>> -> + Scheme = binary:part(Original, 0, Size), + Pieces@1 = {uri, + {some, string:lowercase(Scheme)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_authority_with_slashes(Uri_string, Pieces@1); + + <<"?"/utf8, Rest/binary>> when Size =:= 0 -> + parse_query_with_question_mark(Rest, Pieces); + + <<"?"/utf8, Rest@1/binary>> -> + Scheme@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + {some, string:lowercase(Scheme@1)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@1, Pieces@2); + + <<"#"/utf8, Rest@2/binary>> when Size =:= 0 -> + parse_fragment(Rest@2, Pieces); + + <<"#"/utf8, Rest@3/binary>> -> + Scheme@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + {some, string:lowercase(Scheme@2)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@3, Pieces@3); + + <<":"/utf8, _/binary>> when Size =:= 0 -> + {error, nil}; + + <<":"/utf8, Rest@4/binary>> -> + Scheme@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + {some, string:lowercase(Scheme@3)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_authority_with_slashes(Rest@4, Pieces@4); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Original, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@5} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_scheme_loop(Original, Rest@5, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 536). +?DOC( + " Parses an URL-encoded query string into a list of key value pairs.\n" + " Returns an error for invalid encoding.\n" + "\n" + " The opposite operation is `uri.query_to_string`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse_query(\"a=1&b=2\") == Ok([#(\"a\", \"1\"), #(\"b\", \"2\")])\n" + " ```\n" +). +-spec parse_query(binary()) -> {ok, list({binary(), binary()})} | {error, nil}. +parse_query(Query) -> + gleam_stdlib:parse_query(Query). + +-file("src/gleam/uri.gleam", 576). +?DOC( + " Encodes a string into a percent encoded representation.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert percent_encode(\"100% great\") == \"100%25%20great\"\n" + " ```\n" +). +-spec percent_encode(binary()) -> binary(). +percent_encode(Value) -> + gleam_stdlib:percent_encode(Value). + +-file("src/gleam/uri.gleam", 561). +-spec percent_encode_query(binary()) -> binary(). +percent_encode_query(Part) -> + _pipe = gleam_stdlib:percent_encode(Part), + gleam@string:replace(_pipe, <<"+"/utf8>>, <<"%2B"/utf8>>). + +-file("src/gleam/uri.gleam", 556). +-spec query_pair({binary(), binary()}) -> gleam@string_tree:string_tree(). +query_pair(Pair) -> + _pipe = [percent_encode_query(erlang:element(1, Pair)), + <<"="/utf8>>, + percent_encode_query(erlang:element(2, Pair))], + gleam_stdlib:identity(_pipe). + +-file("src/gleam/uri.gleam", 548). +?DOC( + " Encodes a list of key value pairs as a URI query string.\n" + "\n" + " The opposite operation is `uri.parse_query`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert query_to_string([#(\"a\", \"1\"), #(\"b\", \"2\")]) == \"a=1&b=2\"\n" + " ```\n" +). +-spec query_to_string(list({binary(), binary()})) -> binary(). +query_to_string(Query) -> + _pipe = Query, + _pipe@1 = gleam@list:map(_pipe, fun query_pair/1), + _pipe@2 = gleam@list:intersperse( + _pipe@1, + gleam_stdlib:identity(<<"&"/utf8>>) + ), + _pipe@3 = gleam_stdlib:identity(_pipe@2), + unicode:characters_to_binary(_pipe@3). + +-file("src/gleam/uri.gleam", 588). +?DOC( + " Decodes a percent encoded string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert percent_decode(\"100%25%20great+fun\") == Ok(\"100% great+fun\")\n" + " ```\n" +). +-spec percent_decode(binary()) -> {ok, binary()} | {error, nil}. +percent_decode(Value) -> + gleam_stdlib:percent_decode(Value). + +-file("src/gleam/uri.gleam", 609). +-spec remove_dot_segments_loop(list(binary()), list(binary())) -> list(binary()). +remove_dot_segments_loop(Input, Accumulator) -> + case Input of + [] -> + lists:reverse(Accumulator); + + [Segment | Rest] -> + Accumulator@5 = case {Segment, Accumulator} of + {<<""/utf8>>, Accumulator@1} -> + Accumulator@1; + + {<<"."/utf8>>, Accumulator@2} -> + Accumulator@2; + + {<<".."/utf8>>, []} -> + []; + + {<<".."/utf8>>, [_ | Accumulator@3]} -> + Accumulator@3; + + {Segment@1, Accumulator@4} -> + [Segment@1 | Accumulator@4] + end, + remove_dot_segments_loop(Rest, Accumulator@5) + end. + +-file("src/gleam/uri.gleam", 605). +-spec remove_dot_segments(list(binary())) -> list(binary()). +remove_dot_segments(Input) -> + remove_dot_segments_loop(Input, []). + +-file("src/gleam/uri.gleam", 601). +?DOC( + " Splits the path section of a URI into its constituent segments.\n" + "\n" + " Removes empty segments and resolves dot-segments as specified in\n" + " [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert path_segments(\"/users/1\") == [\"users\" ,\"1\"]\n" + " ```\n" +). +-spec path_segments(binary()) -> list(binary()). +path_segments(Path) -> + remove_dot_segments(gleam@string:split(Path, <<"/"/utf8>>)). + +-file("src/gleam/uri.gleam", 639). +?DOC( + " Encodes a `Uri` value as a URI string.\n" + "\n" + " The opposite operation is `uri.parse`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let uri = Uri(..empty, scheme: Some(\"https\"), host: Some(\"example.com\"))\n" + " assert to_string(uri) == \"https://example.com\"\n" + " ```\n" +). +-spec to_string(uri()) -> binary(). +to_string(Uri) -> + Parts = case erlang:element(8, Uri) of + {some, Fragment} -> + [<<"#"/utf8>>, Fragment]; + + none -> + [] + end, + Parts@1 = case erlang:element(7, Uri) of + {some, Query} -> + [<<"?"/utf8>>, Query | Parts]; + + none -> + Parts + end, + Parts@2 = [erlang:element(6, Uri) | Parts@1], + Parts@3 = case {erlang:element(4, Uri), + gleam_stdlib:string_starts_with(erlang:element(6, Uri), <<"/"/utf8>>)} of + {{some, Host}, false} when Host =/= <<""/utf8>> -> + [<<"/"/utf8>> | Parts@2]; + + {_, _} -> + Parts@2 + end, + Parts@4 = case {erlang:element(4, Uri), erlang:element(5, Uri)} of + {{some, _}, {some, Port}} -> + [<<":"/utf8>>, erlang:integer_to_binary(Port) | Parts@3]; + + {_, _} -> + Parts@3 + end, + Parts@5 = case {erlang:element(2, Uri), + erlang:element(3, Uri), + erlang:element(4, Uri)} of + {{some, S}, {some, U}, {some, H}} -> + [S, <<"://"/utf8>>, U, <<"@"/utf8>>, H | Parts@4]; + + {{some, S@1}, none, {some, H@1}} -> + [S@1, <<"://"/utf8>>, H@1 | Parts@4]; + + {{some, S@2}, {some, _}, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {{some, S@2}, none, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {none, none, {some, H@2}} -> + [<<"//"/utf8>>, H@2 | Parts@4]; + + {_, _, _} -> + Parts@4 + end, + erlang:list_to_binary(Parts@5). + +-file("src/gleam/uri.gleam", 682). +?DOC( + " Fetches the origin of a URI.\n" + "\n" + " Returns the origin of a uri as defined in\n" + " [RFC 6454](https://tools.ietf.org/html/rfc6454)\n" + "\n" + " The supported URI schemes are `http` and `https`.\n" + " URLs without a scheme will return `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert Ok(uri) = parse(\"https://example.com/path?foo#bar\")\n" + " assert origin(uri) == Ok(\"https://example.com\")\n" + " ```\n" +). +-spec origin(uri()) -> {ok, binary()} | {error, nil}. +origin(Uri) -> + {uri, Scheme, _, Host, Port, _, _, _} = Uri, + case {Host, Scheme} of + {{some, H}, {some, <<"https"/utf8>>}} when Port =:= {some, 443} -> + {ok, erlang:list_to_binary([<<"https://"/utf8>>, H])}; + + {{some, H@1}, {some, <<"http"/utf8>>}} when Port =:= {some, 80} -> + {ok, erlang:list_to_binary([<<"http://"/utf8>>, H@1])}; + + {{some, H@2}, {some, S}} when (S =:= <<"http"/utf8>>) orelse (S =:= <<"https"/utf8>>) -> + case Port of + {some, P} -> + {ok, + erlang:list_to_binary( + [S, + <<"://"/utf8>>, + H@2, + <<":"/utf8>>, + erlang:integer_to_binary(P)] + )}; + + none -> + {ok, erlang:list_to_binary([S, <<"://"/utf8>>, H@2])} + end; + + {_, _} -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 763). +-spec drop_last(list(DDN)) -> list(DDN). +drop_last(Elements) -> + gleam@list:take(Elements, erlang:length(Elements) - 1). + +-file("src/gleam/uri.gleam", 767). +-spec join_segments(list(binary())) -> binary(). +join_segments(Segments) -> + gleam@string:join([<<""/utf8>> | Segments], <<"/"/utf8>>). + +-file("src/gleam/uri.gleam", 705). +?DOC( + " Resolves a URI with respect to the given base URI.\n" + "\n" + " The base URI must be an absolute URI or this function will return an error.\n" + " The algorithm for merging URIs is described in\n" + " [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2).\n" +). +-spec merge(uri(), uri()) -> {ok, uri()} | {error, nil}. +merge(Base, Relative) -> + case Base of + {uri, {some, _}, _, {some, _}, _, _, _, _} -> + case Relative of + {uri, _, _, {some, _}, _, _, _, _} -> + Path = begin + _pipe = erlang:element(6, Relative), + _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), + _pipe@2 = remove_dot_segments(_pipe@1), + join_segments(_pipe@2) + end, + Resolved = {uri, + gleam@option:'or'( + erlang:element(2, Relative), + erlang:element(2, Base) + ), + none, + erlang:element(4, Relative), + gleam@option:'or'( + erlang:element(5, Relative), + erlang:element(5, Base) + ), + Path, + erlang:element(7, Relative), + erlang:element(8, Relative)}, + {ok, Resolved}; + + _ -> + {New_path, New_query} = case erlang:element(6, Relative) of + <<""/utf8>> -> + {erlang:element(6, Base), + gleam@option:'or'( + erlang:element(7, Relative), + erlang:element(7, Base) + )}; + + _ -> + Path_segments = case gleam_stdlib:string_starts_with( + erlang:element(6, Relative), + <<"/"/utf8>> + ) of + true -> + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ); + + false -> + _pipe@3 = erlang:element(6, Base), + _pipe@4 = gleam@string:split( + _pipe@3, + <<"/"/utf8>> + ), + _pipe@5 = drop_last(_pipe@4), + lists:append( + _pipe@5, + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ) + ) + end, + Path@1 = begin + _pipe@6 = Path_segments, + _pipe@7 = remove_dot_segments(_pipe@6), + join_segments(_pipe@7) + end, + {Path@1, erlang:element(7, Relative)} + end, + Resolved@1 = {uri, + erlang:element(2, Base), + none, + erlang:element(4, Base), + erlang:element(5, Base), + New_path, + New_query, + erlang:element(8, Relative)}, + {ok, Resolved@1} + end; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 81). +?DOC( + " Parses a compliant URI string into the `Uri` type.\n" + " If the string is not a valid URI string then an error is returned.\n" + "\n" + " The opposite operation is `uri.to_string`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"https://example.com:1234/a/b?query=true#fragment\")\n" + " == Ok(\n" + " Uri(\n" + " scheme: Some(\"https\"),\n" + " userinfo: None,\n" + " host: Some(\"example.com\"),\n" + " port: Some(1234),\n" + " path: \"/a/b\",\n" + " query: Some(\"query=true\"),\n" + " fragment: Some(\"fragment\")\n" + " )\n" + " )\n" + " ```\n" +). +-spec parse(binary()) -> {ok, uri()} | {error, nil}. +parse(Uri_string) -> + gleam_stdlib:uri_parse(Uri_string). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.app.src b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.app.src new file mode 100644 index 0000000..e312da4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.app.src @@ -0,0 +1,31 @@ +{application, gleam_stdlib, [ + {vsn, "1.0.0"}, + {applications, []}, + {description, "A standard library for the Gleam programming language"}, + {modules, [gleam@bit_array, + gleam@bool, + gleam@bytes_tree, + gleam@dict, + gleam@dynamic, + gleam@dynamic@decode, + gleam@float, + gleam@function, + gleam@int, + gleam@io, + gleam@list, + gleam@option, + gleam@order, + gleam@pair, + gleam@result, + gleam@set, + gleam@string, + gleam@string_tree, + gleam@uri, + gleam_stdlib, + gleam_stdlib@@main, + gleam_stdlib_test_ffi, + gleeunit_ffi, + gleeunit_gleam_panic_ffi, + gleeunit_progress]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.erl new file mode 100644 index 0000000..92c91cd --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.erl @@ -0,0 +1,551 @@ +-module(gleam_stdlib). + +-export([ + map_get/2, iodata_append/2, identity/1, parse_int/1, parse_float/1, + less_than/2, string_pop_grapheme/1, string_pop_codeunit/1, + string_starts_with/2, wrap_list/1, string_ends_with/2, string_pad/4, + uri_parse/1, bit_array_slice/3, percent_encode/1, percent_decode/1, + base64_decode/1, parse_query/1, bit_array_concat/1, + base64_encode/2, tuple_get/2, classify_dynamic/1, print/1, + println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, + int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, + crop_string/2, base16_encode/1, base16_decode/1, string_replace/3, slice/3, + bit_array_to_int_and_size/1, bit_array_pad_to_bytes/1, index/2, list/5, + dict/1, int/1, float/1, bit_array/1, is_null/1, string_remove_prefix/2, + string_remove_suffix/2 +]). + +%% Taken from OTP's uri_string module +-define(DEC2HEX(X), + if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0; + ((X) >= 10) andalso ((X) =< 15) -> (X) + $A - 10 + end). + +%% Taken from OTP's uri_string module +-define(HEX2DEC(X), + if ((X) >= $0) andalso ((X) =< $9) -> (X) - $0; + ((X) >= $A) andalso ((X) =< $F) -> (X) - $A + 10; + ((X) >= $a) andalso ((X) =< $f) -> (X) - $a + 10 + end). + +-define(is_lowercase_char(X), + (X > 96 andalso X < 123)). +-define(is_underscore_char(X), + (X == 95)). +-define(is_digit_char(X), + (X > 47 andalso X < 58)). +-define(is_ascii_character(X), + (erlang:is_integer(X) andalso X >= 32 andalso X =< 126)). + +uppercase(X) -> X - 32. + +map_get(Map, Key) -> + case maps:find(Key, Map) of + error -> {error, nil}; + OkFound -> OkFound + end. + +iodata_append(Iodata, String) -> [Iodata, String]. + +identity(X) -> X. + +classify_dynamic(nil) -> <<"Nil">>; +classify_dynamic(null) -> <<"Nil">>; +classify_dynamic(undefined) -> <<"Nil">>; +classify_dynamic(X) when is_boolean(X) -> <<"Bool">>; +classify_dynamic(X) when is_atom(X) -> <<"Atom">>; +classify_dynamic(X) when is_binary(X) -> <<"String">>; +classify_dynamic(X) when is_bitstring(X) -> <<"BitArray">>; +classify_dynamic(X) when is_integer(X) -> <<"Int">>; +classify_dynamic(X) when is_float(X) -> <<"Float">>; +classify_dynamic(X) when is_list(X) -> <<"List">>; +classify_dynamic(X) when is_map(X) -> <<"Dict">>; +classify_dynamic(X) when is_tuple(X) -> <<"Array">>; +classify_dynamic(X) when is_reference(X) -> <<"Reference">>; +classify_dynamic(X) when is_pid(X) -> <<"Pid">>; +classify_dynamic(X) when is_port(X) -> <<"Port">>; +classify_dynamic(X) when is_function(X) -> <<"Function">>; +classify_dynamic(_) -> <<"Unknown">>. + +tuple_get(_tup, Index) when Index < 0 -> {error, nil}; +tuple_get(Data, Index) when Index >= tuple_size(Data) -> {error, nil}; +tuple_get(Data, Index) -> {ok, element(Index + 1, Data)}. + +int_from_base_string(String, Base) -> + case catch binary_to_integer(String, Base) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_int(String) -> + case catch binary_to_integer(String) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_float(String) -> + case catch binary_to_float(String) of + Float when is_float(Float) -> {ok, Float}; + _ -> {error, nil} + end. + +less_than(Lhs, Rhs) -> + Lhs < Rhs. + +string_starts_with(_, <<>>) -> true; +string_starts_with(String, Prefix) when byte_size(Prefix) > byte_size(String) -> false; +string_starts_with(String, Prefix) -> + PrefixSize = byte_size(Prefix), + Prefix == binary_part(String, 0, PrefixSize). + +string_ends_with(_, <<>>) -> true; +string_ends_with(String, Suffix) when byte_size(Suffix) > byte_size(String) -> false; +string_ends_with(String, Suffix) -> + SuffixSize = byte_size(Suffix), + Suffix == binary_part(String, byte_size(String) - SuffixSize, SuffixSize). + +string_pad(String, Length, Dir, PadString) -> + Chars = string:pad(String, Length, Dir, binary_to_list(PadString)), + case unicode:characters_to_binary(Chars) of + Bin when is_binary(Bin) -> Bin; + Error -> erlang:error({gleam_error, {string_invalid_utf8, Error}}) + end. + +string_pop_grapheme(String) -> + case string:next_grapheme(String) of + [ Next | Rest ] when is_binary(Rest) -> + {ok, {unicode:characters_to_binary([Next]), Rest}}; + + [ Next | Rest ] -> + {ok, {unicode:characters_to_binary([Next]), unicode:characters_to_binary(Rest)}}; + + _ -> {error, nil} + end. + +string_pop_codeunit(<>) -> {Cp, Rest}; +string_pop_codeunit(Binary) -> {0, Binary}. + +bit_array_pad_to_bytes(Bin) -> + case erlang:bit_size(Bin) rem 8 of + 0 -> Bin; + TrailingBits -> + PaddingBits = 8 - TrailingBits, + <> + end. + +bit_array_concat(BitArrays) -> + list_to_bitstring(BitArrays). + +-if(?OTP_RELEASE >= 26). +base64_encode(Bin, Padding) -> + PaddedBin = bit_array_pad_to_bytes(Bin), + base64:encode(PaddedBin, #{padding => Padding}). +-else. +base64_encode(_Bin, _Padding) -> + erlang:error(<<"Erlang OTP/26 or higher is required to use base64:encode">>). +-endif. + +bit_array_slice(Bin, Pos, Len) -> + try {ok, binary:part(Bin, Pos, Len)} + catch error:badarg -> {error, nil} + end. + +base64_decode(S) -> + try {ok, base64:decode(S)} + catch error:_ -> {error, nil} + end. + +wrap_list(X) when is_list(X) -> X; +wrap_list(X) -> [X]. + +parse_query(Query) -> + case uri_string:dissect_query(Query) of + {error, _, _} -> {error, nil}; + Pairs -> + Pairs1 = lists:map(fun + ({K, true}) -> {K, <<"">>}; + (Pair) -> Pair + end, Pairs), + {ok, Pairs1} + end. + +percent_encode(B) -> percent_encode(B, <<>>). +percent_encode(<<>>, Acc) -> + Acc; +percent_encode(<>, Acc) -> + case percent_ok(H) of + true -> + percent_encode(T, <>); + false -> + <> = <>, + percent_encode(T, <>) + end. + +percent_decode(Cs) -> percent_decode(Cs, <<>>). +percent_decode(<<$%, C0, C1, Cs/binary>>, Acc) -> + case is_hex_digit(C0) andalso is_hex_digit(C1) of + true -> + B = ?HEX2DEC(C0)*16+?HEX2DEC(C1), + percent_decode(Cs, <>); + false -> + {error, nil} + end; +percent_decode(<>, Acc) -> + percent_decode(Cs, <>); +percent_decode(<<>>, Acc) -> + check_utf8(Acc). + +percent_ok($!) -> true; +percent_ok($$) -> true; +percent_ok($') -> true; +percent_ok($() -> true; +percent_ok($)) -> true; +percent_ok($*) -> true; +percent_ok($+) -> true; +percent_ok($-) -> true; +percent_ok($.) -> true; +percent_ok($_) -> true; +percent_ok($~) -> true; +percent_ok(C) when $0 =< C, C =< $9 -> true; +percent_ok(C) when $A =< C, C =< $Z -> true; +percent_ok(C) when $a =< C, C =< $z -> true; +percent_ok(_) -> false. + +is_hex_digit(C) -> + ($0 =< C andalso C =< $9) orelse ($a =< C andalso C =< $f) orelse ($A =< C andalso C =< $F). + +check_utf8(Cs) -> + case unicode:characters_to_list(Cs) of + {incomplete, _, _} -> {error, nil}; + {error, _, _} -> {error, nil}; + _ -> {ok, Cs} + end. + +uri_parse(String) -> + case uri_string:parse(String) of + {error, _, _} -> {error, nil}; + Uri -> + Port = + try maps:get(port, Uri) of + undefined -> none; + Value -> {some, Value} + catch _:_ -> none + end, + {ok, {uri, + maps_get_optional_lowercase(Uri, scheme), + maps_get_optional(Uri, userinfo), + maps_get_optional(Uri, host), + Port, + maps_get_or(Uri, path, <<>>), + maps_get_optional(Uri, query), + maps_get_optional(Uri, fragment) + }} + end. + +maps_get_optional_lowercase(Map, Key) -> + try {some, string:lowercase(maps:get(Key, Map))} + catch _:_ -> none + end. + +maps_get_optional(Map, Key) -> + try {some, maps:get(Key, Map)} + catch _:_ -> none + end. + +maps_get_or(Map, Key, Default) -> + try maps:get(Key, Map) + catch _:_ -> Default + end. + +print(String) -> + io:put_chars(String), + nil. + +println(String) -> + io:put_chars([String, $\n]), + nil. + +print_error(String) -> + io:put_chars(standard_error, String), + nil. + +println_error(String) -> + io:put_chars(standard_error, [String, $\n]), + nil. + +inspect(true) -> + "True"; +inspect(false) -> + "False"; +inspect(nil) -> + "Nil"; +inspect(Data) when is_map(Data) -> + Fields = [ + [<<"#(">>, inspect(Key), <<", ">>, inspect(Value), <<")">>] + || {Key, Value} <- maps:to_list(Data) + ], + ["dict.from_list([", lists:join(", ", Fields), "])"]; +inspect(Atom) when is_atom(Atom) -> + erlang:element(2, inspect_atom(Atom)); +inspect(Any) when is_integer(Any) -> + erlang:integer_to_list(Any); +inspect(Any) when is_float(Any) -> + io_lib_format:fwrite_g(Any); +inspect(Binary) when is_binary(Binary) -> + case inspect_maybe_utf8_string(Binary, <<>>) of + {ok, InspectedUtf8String} -> InspectedUtf8String; + {error, not_a_utf8_string} -> + Segments = [erlang:integer_to_list(X) || <> <= Binary], + ["<<", lists:join(", ", Segments), ">>"] + end; +inspect(Bits) when is_bitstring(Bits) -> + inspect_bit_array(Bits); +inspect(List) when is_list(List) -> + case inspect_list(List, true) of + {charlist, _} -> ["charlist.from_string(\"", list_to_binary(List), "\")"]; + {proper, Elements} -> ["[", Elements, "]"]; + {improper, Elements} -> ["//erl([", Elements, "])"] + end; +inspect(Any) when is_tuple(Any) % Record constructors + andalso is_atom(element(1, Any)) + andalso element(1, Any) =/= false + andalso element(1, Any) =/= true + andalso element(1, Any) =/= nil +-> + [Atom | ArgsList] = erlang:tuple_to_list(Any), + InspectedArgs = lists:map(fun inspect/1, ArgsList), + case inspect_atom(Atom) of + {gleam_atom, GleamAtom} -> + Args = lists:join(<<", ">>, InspectedArgs), + [GleamAtom, "(", Args, ")"]; + {erlang_atom, ErlangAtom} -> + Args = lists:join(<<", ">>, [ErlangAtom | InspectedArgs]), + ["#(", Args, ")"] + end; +inspect(Tuple) when is_tuple(Tuple) -> + Elements = lists:map(fun inspect/1, erlang:tuple_to_list(Tuple)), + ["#(", lists:join(", ", Elements), ")"]; +inspect(Any) when is_function(Any) -> + {arity, Arity} = erlang:fun_info(Any, arity), + ArgsAsciiCodes = lists:seq($a, $a + Arity - 1), + Args = lists:join(<<", ">>, + lists:map(fun(Arg) -> <> end, ArgsAsciiCodes) + ), + ["//fn(", Args, ") { ... }"]; +inspect(Any) -> + ["//erl(", io_lib:format("~p", [Any]), ")"]. + +inspect_atom(Atom) -> + Binary = erlang:atom_to_binary(Atom), + case inspect_maybe_gleam_atom(Binary, none, <<>>) of + {ok, Inspected} -> {gleam_atom, Inspected}; + {error, _} -> {erlang_atom, ["atom.create(\"", Binary, "\")"]} + end. + +inspect_maybe_gleam_atom(<<>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, none, _) when ?is_digit_char(First) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_">>, _PrevChar, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, $_, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, _PrevChar, _Acc) + when not (?is_lowercase_char(First) orelse ?is_underscore_char(First) orelse ?is_digit_char(First)) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, none, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<<"_", Rest/binary>>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, $_, Acc); +inspect_maybe_gleam_atom(<>, $_, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<<>>, _PrevChar, Acc) -> + {ok, Acc}; +inspect_maybe_gleam_atom(A, B, C) -> + erlang:display({A, B, C}), + throw({gleam_error, A, B, C}). + +inspect_list([], _) -> + {proper, []}; +inspect_list([First], true) when ?is_ascii_character(First) -> + {charlist, nil}; +inspect_list([First], _) -> + {proper, [inspect(First)]}; +inspect_list([First | Rest], ValidCharlist) when is_list(Rest) -> + StillValidCharlist = ValidCharlist andalso ?is_ascii_character(First), + {Kind, Inspected} = inspect_list(Rest, StillValidCharlist), + {Kind, [inspect(First), <<", ">> | Inspected]}; +inspect_list([First | ImproperTail], _) -> + {improper, [inspect(First), <<" | ">>, inspect(ImproperTail)]}. + +inspect_bit_array(Bits) -> + Text = inspect_bit_array(Bits, <<"<<">>), + <>">>. + +inspect_bit_array(<<>>, Acc) -> + Acc; +inspect_bit_array(<>, Acc) -> + inspect_bit_array(Rest, append_segment(Acc, erlang:integer_to_binary(X))); +inspect_bit_array(Rest, Acc) -> + Size = bit_size(Rest), + <> = Rest, + X1 = erlang:integer_to_binary(X), + Size1 = erlang:integer_to_binary(Size), + Segment = <>, + inspect_bit_array(<<>>, append_segment(Acc, Segment)). + +bit_array_to_int_and_size(A) -> + Size = bit_size(A), + <> = A, + {A1, Size}. + +append_segment(<<"<<">>, Segment) -> + <<"<<", Segment/binary>>; +append_segment(Acc, Segment) -> + <>. + + +inspect_maybe_utf8_string(Binary, Acc) -> + case Binary of + <<>> -> {ok, <<$", Acc/binary, $">>}; + <> -> + Escaped = case First of + $" -> <<$\\, $">>; + $\\ -> <<$\\, $\\>>; + $\r -> <<$\\, $r>>; + $\n -> <<$\\, $n>>; + $\t -> <<$\\, $t>>; + $\f -> <<$\\, $f>>; + X when X > 126, X < 160 -> convert_to_u(X); + X when X < 32 -> convert_to_u(X); + Other -> <> + end, + inspect_maybe_utf8_string(Rest, <>); + _ -> {error, not_a_utf8_string} + end. + +convert_to_u(Code) -> + list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])). + +float_to_string(Float) when is_float(Float) -> + erlang:iolist_to_binary(io_lib_format:fwrite_g(Float)). + +utf_codepoint_list_to_string(List) -> + case unicode:characters_to_binary(List) of + {error, _} -> erlang:error({gleam_error, {string_invalid_utf8, List}}); + Binary -> Binary + end. + +crop_string(String, Prefix) -> + case string:find(String, Prefix) of + nomatch -> String; + New -> New + end. + +contains_string(String, Substring) -> + is_bitstring(string:find(String, Substring)). + +base16_encode(Bin) -> + PaddedBin = bit_array_pad_to_bytes(Bin), + binary:encode_hex(PaddedBin). + +base16_decode(String) -> + try + {ok, binary:decode_hex(String)} + catch + _:_ -> {error, nil} + end. + +string_replace(String, Pattern, Replacement) -> + string:replace(String, Pattern, Replacement, all). + +slice(String, Index, Length) -> + case string:slice(String, Index, Length) of + X when is_binary(X) -> X; + X when is_list(X) -> unicode:characters_to_binary(X) + end. + +index([X | _], 0) -> + {ok, {some, X}}; +index([_, X | _], 1) -> + {ok, {some, X}}; +index([_, _, X | _], 2) -> + {ok, {some, X}}; +index([_, _, _, X | _], 3) -> + {ok, {some, X}}; +index([_, _, _, _, X | _], 4) -> + {ok, {some, X}}; +index([_, _, _, _, _, X | _], 5) -> + {ok, {some, X}}; +index([_, _, _, _, _, _, X | _], 6) -> + {ok, {some, X}}; +index([_, _, _, _, _, _, _, X | _], 7) -> + {ok, {some, X}}; +index(Tuple, Index) when is_tuple(Tuple) andalso is_integer(Index) -> + {ok, try + {some, element(Index + 1, Tuple)} + catch _:_ -> + none + end}; +index(Map, Key) when is_map(Map) -> + {ok, try + {some, maps:get(Key, Map)} + catch _:_ -> + none + end}; +index(_, Index) when is_integer(Index) -> + {error, <<"Indexable">>}; +index(_, _) -> + {error, <<"Dict">>}. + +list(T, A, B, C, D) when is_tuple(T) -> + list(tuple_to_list(T), A, B, C, D); +list([], _, _, _, Acc) -> + {lists:reverse(Acc), []}; +list([X | Xs], Decode, PushPath, Index, Acc) -> + {Out, Errors} = Decode(X), + case Errors of + [] -> list(Xs, Decode, PushPath, Index + 1, [Out | Acc]); + _ -> PushPath({[], Errors}, integer_to_binary(Index)) + end; +list(Unexpected, _, _, _, []) -> + Found = gleam@dynamic:classify(Unexpected), + Error = {decode_error, <<"List"/utf8>>, Found, []}, + {[], [Error]}; +list(_, _, _, _, Acc) -> + {lists:reverse(Acc), []}. + +dict(#{} = Data) -> {ok, Data}; +dict(_) -> {error, nil}. + +int(I) when is_integer(I) -> {ok, I}; +int(_) -> {error, 0}. + +float(F) when is_float(F) -> {ok, F}; +float(_) -> {error, 0.0}. + +bit_array(B) when is_bitstring(B) -> {ok, B}; +bit_array(_) -> {error, <<>>}. + +is_null(X) -> + X =:= undefined orelse X =:= null orelse X =:= nil. + +string_remove_prefix(String, Prefix) -> + PrefixSize = byte_size(Prefix), + case String of + <> -> Suffix; + _ -> String + end. + +string_remove_suffix(String, Suffix) -> + StringSize = byte_size(String), + SuffixSize = byte_size(Suffix), + Offset = StringSize - SuffixSize, + case String of + <> -> Prefix; + _ -> String + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.mjs b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.mjs new file mode 100644 index 0000000..ebc5213 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleam_stdlib/src/gleam_stdlib.mjs @@ -0,0 +1,1150 @@ +import { + BitArray, + List$Empty, + List$NonEmpty, + List$isEmpty, + List$isNonEmpty, + Result$Ok, + Result$Error, + Result$isOk, + Result$isError, + UtfCodepoint, + stringBits, + toBitArray, + bitArraySlice, + CustomType, +} from "./gleam.mjs"; +import { Some, None } from "./gleam/option.mjs"; +import { + default as Dict, + fold as dict_fold, + get as dict_get, + from as dict_from_iterable, +} from "./dict.mjs"; +import { classify } from "./gleam/dynamic.mjs"; +import { DecodeError$DecodeError } from "./gleam/dynamic/decode.mjs"; + +const Nil = undefined; + +export function identity(x) { + return x; +} + +export function parse_int(value) { + if (/^[-+]?(\d+)$/.test(value)) { + return Result$Ok(parseInt(value)); + } else { + return Result$Error(Nil); + } +} + +export function parse_float(value) { + if (/^[-+]?(\d+)\.(\d+)([eE][-+]?\d+)?$/.test(value)) { + return Result$Ok(parseFloat(value)); + } else { + return Result$Error(Nil); + } +} + +export function to_string(term) { + return term.toString(); +} + +export function int_to_base_string(int, base) { + return int.toString(base).toUpperCase(); +} + +const int_base_patterns = { + 2: /[^0-1]/, + 3: /[^0-2]/, + 4: /[^0-3]/, + 5: /[^0-4]/, + 6: /[^0-5]/, + 7: /[^0-6]/, + 8: /[^0-7]/, + 9: /[^0-8]/, + 10: /[^0-9]/, + 11: /[^0-9a]/, + 12: /[^0-9a-b]/, + 13: /[^0-9a-c]/, + 14: /[^0-9a-d]/, + 15: /[^0-9a-e]/, + 16: /[^0-9a-f]/, + 17: /[^0-9a-g]/, + 18: /[^0-9a-h]/, + 19: /[^0-9a-i]/, + 20: /[^0-9a-j]/, + 21: /[^0-9a-k]/, + 22: /[^0-9a-l]/, + 23: /[^0-9a-m]/, + 24: /[^0-9a-n]/, + 25: /[^0-9a-o]/, + 26: /[^0-9a-p]/, + 27: /[^0-9a-q]/, + 28: /[^0-9a-r]/, + 29: /[^0-9a-s]/, + 30: /[^0-9a-t]/, + 31: /[^0-9a-u]/, + 32: /[^0-9a-v]/, + 33: /[^0-9a-w]/, + 34: /[^0-9a-x]/, + 35: /[^0-9a-y]/, + 36: /[^0-9a-z]/, +}; + +export function int_from_base_string(string, base) { + if (int_base_patterns[base].test(string.replace(/^-/, "").toLowerCase())) { + return Result$Error(Nil); + } + + const result = parseInt(string, base); + + if (isNaN(result)) { + return Result$Error(Nil); + } + + return Result$Ok(result); +} + +export function string_replace(string, target, substitute) { + return string.replaceAll(target, substitute); +} + +export function string_reverse(string) { + return [...string].reverse().join(""); +} + +export function string_length(string) { + if (string === "") { + return 0; + } + const iterator = graphemes_iterator(string); + if (iterator) { + let i = 0; + for (const _ of iterator) { + i++; + } + return i; + } else { + return string.match(/./gsu).length; + } +} + +export function graphemes(string) { + const iterator = graphemes_iterator(string); + if (iterator) { + return arrayToList(Array.from(iterator).map((item) => item.segment)); + } else { + return arrayToList(string.match(/./gsu)); + } +} + +let segmenter = undefined; + +function graphemes_iterator(string) { + if (globalThis.Intl && Intl.Segmenter) { + segmenter ||= new Intl.Segmenter(); + return segmenter.segment(string)[Symbol.iterator](); + } +} + +export function pop_grapheme(string) { + let first; + const iterator = graphemes_iterator(string); + if (iterator) { + first = iterator.next().value?.segment; + } else { + first = string.match(/./su)?.[0]; + } + if (first) { + return Result$Ok([first, string.slice(first.length)]); + } else { + return Result$Error(Nil); + } +} + +export function pop_codeunit(str) { + return [str.charCodeAt(0) | 0, str.slice(1)]; +} + +export function lowercase(string) { + return string.toLowerCase(); +} + +export function uppercase(string) { + return string.toUpperCase(); +} + +export function less_than(a, b) { + return a < b; +} + +export function add(a, b) { + return a + b; +} + +export function split(xs, pattern) { + return arrayToList(xs.split(pattern)); +} + +export function concat(xs) { + let result = ""; + for (const x of xs) { + result = result + x; + } + return result; +} + +export function length(data) { + return data.length; +} + +export function string_byte_slice(string, index, length) { + return string.slice(index, index + length); +} + +export function string_grapheme_slice(string, idx, len) { + if (len <= 0 || idx >= string.length) { + return ""; + } + + const iterator = graphemes_iterator(string); + if (iterator) { + while (idx-- > 0) { + iterator.next(); + } + + let result = ""; + + while (len-- > 0) { + const v = iterator.next().value; + if (v === undefined) { + break; + } + + result += v.segment; + } + + return result; + } else { + return string + .match(/./gsu) + .slice(idx, idx + len) + .join(""); + } +} + +export function string_codeunit_slice(str, from, length) { + return str.slice(from, from + length); +} +export function crop_string(string, substring) { + return string.substring(string.indexOf(substring)); +} + +export function contains_string(haystack, needle) { + return haystack.indexOf(needle) >= 0; +} + +export function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} + +export function ends_with(haystack, needle) { + return haystack.endsWith(needle); +} + +export function split_once(haystack, needle) { + const index = haystack.indexOf(needle); + if (index >= 0) { + const before = haystack.slice(0, index); + const after = haystack.slice(index + needle.length); + return Result$Ok([before, after]); + } else { + return Result$Error(Nil); + } +} + +const unicode_whitespaces = [ + "\u0020", // Space + "\u0009", // Horizontal tab + "\u000A", // Line feed + "\u000B", // Vertical tab + "\u000C", // Form feed + "\u000D", // Carriage return + "\u0085", // Next line + "\u2028", // Line separator + "\u2029", // Paragraph separator +].join(""); + +const trim_start_regex = /* @__PURE__ */ new RegExp( + `^[${unicode_whitespaces}]*`, +); +const trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); + +export function trim_start(string) { + return string.replace(trim_start_regex, ""); +} + +export function trim_end(string) { + return string.replace(trim_end_regex, ""); +} + +export function bit_array_from_string(string) { + return toBitArray([stringBits(string)]); +} + +export function bit_array_bit_size(bit_array) { + return bit_array.bitSize; +} + +export function bit_array_byte_size(bit_array) { + return bit_array.byteSize; +} + +export function bit_array_pad_to_bytes(bit_array) { + const trailingBitsCount = bit_array.bitSize % 8; + + // If the bit array is a whole number of bytes it can be returned unchanged + if (trailingBitsCount === 0) { + return bit_array; + } + + const finalByte = bit_array.byteAt(bit_array.byteSize - 1); + + // The required final byte has its unused trailing bits set to zero + const unusedBitsCount = 8 - trailingBitsCount; + const correctFinalByte = (finalByte >> unusedBitsCount) << unusedBitsCount; + + // If the unused bits in the final byte are already set to zero then the + // existing buffer can be re-used, avoiding a copy + if (finalByte === correctFinalByte) { + return new BitArray( + bit_array.rawBuffer, + bit_array.byteSize * 8, + bit_array.bitOffset, + ); + } + + // Copy the bit array into a new aligned buffer and set the correct final byte + const buffer = new Uint8Array(bit_array.byteSize); + for (let i = 0; i < buffer.length - 1; i++) { + buffer[i] = bit_array.byteAt(i); + } + buffer[buffer.length - 1] = correctFinalByte; + + return new BitArray(buffer); +} + +export function bit_array_concat(bit_arrays) { + return toBitArray(bit_arrays.toArray()); +} + +export function console_log(term) { + console.log(term); +} + +export function console_error(term) { + console.error(term); +} + +export function crash(message) { + throw new globalThis.Error(message); +} + +export function bit_array_to_string(bit_array) { + // If the bit array isn't a whole number of bytes then return an error + if (bit_array.bitSize % 8 !== 0) { + return Result$Error(Nil); + } + + try { + const decoder = new TextDecoder("utf-8", { fatal: true }); + + if (bit_array.bitOffset === 0) { + return Result$Ok(decoder.decode(bit_array.rawBuffer)); + } else { + // The input data isn't aligned, so copy it into a new aligned buffer so + // that TextDecoder can be used + const buffer = new Uint8Array(bit_array.byteSize); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = bit_array.byteAt(i); + } + return Result$Ok(decoder.decode(buffer)); + } + } catch { + return Result$Error(Nil); + } +} + +export function print(string) { + if (typeof process === "object" && process.stdout?.write) { + process.stdout.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stdout.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.log(string); // We're in a browser. Newlines are mandated + } +} + +export function print_error(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.error(string); // We're in a browser. Newlines are mandated + } +} + +export function print_debug(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string + "\n"); // If we're in Node.js, use `stderr` + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string + "\n")); // If we're in Deno, use `stderr` + } else { + console.log(string); // Otherwise, use `console.log` (so that it doesn't look like an error) + } +} + +export function ceiling(float) { + return Math.ceil(float); +} + +export function floor(float) { + return Math.floor(float); +} + +export function round(float) { + return Math.round(float); +} + +export function truncate(float) { + return Math.trunc(float); +} + +export function power(base, exponent) { + // It is checked in Gleam that: + // - The base is non-negative and that the exponent is not fractional. + // - The base is non-zero and the exponent is non-negative (otherwise + // the result will essentially be division by zero). + // It can thus be assumed that valid input is passed to the Math.pow + // function and a NaN or Infinity value will not be produced. + return Math.pow(base, exponent); +} + +export function random_uniform() { + const random_uniform_result = Math.random(); + // With round-to-nearest-even behavior, the ranges claimed for the functions below + // (excluding the one for Math.random() itself) aren't exact. + // If extremely large bounds are chosen (2^53 or higher), + // it's possible in extremely rare cases to calculate the usually-excluded upper bound. + // Note that as numbers in JavaScript are IEEE 754 floating point numbers + // See: + // Because of this, we just loop 'until' we get a valid result where 0.0 <= x < 1.0: + if (random_uniform_result === 1.0) { + return random_uniform(); + } + return random_uniform_result; +} + +export function bit_array_slice(bits, position, length) { + const start = Math.min(position, position + length); + const end = Math.max(position, position + length); + + if (start < 0 || end * 8 > bits.bitSize) { + return Result$Error(Nil); + } + + return Result$Ok(bitArraySlice(bits, start * 8, end * 8)); +} + +export function codepoint(int) { + return new UtfCodepoint(int); +} + +export function string_to_codepoint_integer_list(string) { + return arrayToList(Array.from(string).map((item) => item.codePointAt(0))); +} + +export function utf_codepoint_list_to_string(utf_codepoint_integer_list) { + return utf_codepoint_integer_list + .toArray() + .map((x) => String.fromCodePoint(x.value)) + .join(""); +} + +export function utf_codepoint_to_int(utf_codepoint) { + return utf_codepoint.value; +} + +function unsafe_percent_decode(string) { + return decodeURIComponent(string || ""); +} + +function unsafe_percent_decode_query(string) { + return decodeURIComponent((string || "").replace("+", " ")); +} + +export function percent_decode(string) { + try { + return Result$Ok(unsafe_percent_decode(string)); + } catch { + return Result$Error(Nil); + } +} + +export function percent_encode(string) { + return encodeURIComponent(string).replace("%2B", "+"); +} + +export function parse_query(query) { + try { + const pairs = []; + for (const section of query.split("&")) { + const [key, value] = section.split("="); + if (!key) continue; + + const decodedKey = unsafe_percent_decode_query(key); + const decodedValue = unsafe_percent_decode_query(value); + pairs.push([decodedKey, decodedValue]); + } + return Result$Ok(arrayToList(pairs)); + } catch { + return Result$Error(Nil); + } +} + +const b64EncodeLookup = [ + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47, +]; + +let b64TextDecoder; + +// Implementation based on https://github.com/mitschabaude/fast-base64/blob/main/js.js +export function base64_encode(bit_array, padding) { + b64TextDecoder ??= new TextDecoder(); + + bit_array = bit_array_pad_to_bytes(bit_array); + + const m = bit_array.byteSize; + const k = m % 3; + const n = Math.floor(m / 3) * 4 + (k && k + 1); + const N = Math.ceil(m / 3) * 4; + const encoded = new Uint8Array(N); + + for (let i = 0, j = 0; j < m; i += 4, j += 3) { + const y = + (bit_array.byteAt(j) << 16) + + (bit_array.byteAt(j + 1) << 8) + + (bit_array.byteAt(j + 2) | 0); + + encoded[i] = b64EncodeLookup[y >> 18]; + encoded[i + 1] = b64EncodeLookup[(y >> 12) & 0x3f]; + encoded[i + 2] = b64EncodeLookup[(y >> 6) & 0x3f]; + encoded[i + 3] = b64EncodeLookup[y & 0x3f]; + } + + let base64 = b64TextDecoder.decode(new Uint8Array(encoded.buffer, 0, n)); + + if (padding) { + if (k === 1) { + base64 += "=="; + } else if (k === 2) { + base64 += "="; + } + } + + return base64; +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64 +export function base64_decode(sBase64) { + try { + const binString = atob(sBase64); + const length = binString.length; + const array = new Uint8Array(length); + for (let i = 0; i < length; i++) { + array[i] = binString.charCodeAt(i); + } + return Result$Ok(new BitArray(array)); + } catch { + return Result$Error(Nil); + } +} + +export function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (typeof data === "boolean") { + return "Bool"; + } else if (isResult(data)) { + return "Result"; + } else if (isList(data)) { + return "List"; + } else if (data instanceof BitArray) { + return "BitArray"; + } else if (data instanceof Dict) { + return "Dict"; + } else if (Number.isInteger(data)) { + return "Int"; + } else if (Array.isArray(data)) { + return `Array`; + } else if (typeof data === "number") { + return "Float"; + } else if (data === null) { + return "Nil"; + } else if (data === undefined) { + return "Nil"; + } else { + const type = typeof data; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} + +export function byte_size(string) { + return new TextEncoder().encode(string).length; +} + +// In JavaScript, bitwise operations convert numbers to a sequence of 32 bits, +// while Erlang uses arbitrary precision integers. +// +// To get around this, every function here follows this pattern: +// +// 1. If both values fit in the 32-bit signed integer range, use the standard +// JavaScript bitwise operators directly. +// +// Note: For bitwise_shift_left, the result also needs to fit in 32 bits, +// so we use floating-point multiplication instead. +// +// 2. If either value falls outside the safe integer range (-2^53, 2^53), +// fall back to BigInt arithmetic, then downcast the result back to a Number. +// +// 3. Otherwise (safe integers outside the 32-bit range), we split the operation +// across the high 21 bits and low 32 bits individually: +// +// x1 $ x2 = ((hi(x1) $ hi(x2)) << 32) | (lo(x1) $ lo(x2)) +// +// where `$` is a bitwise operator. +// +// We split both values into a `hi` and a `lo` part: +// +// hi(x) = Math.floor(x / 2^32) the upper 21 bits +// lo(x) = x >>> 0 the lower 32 bits (as unsigned) +// +// For `hi`, we use that shifts are equal to multiplication/division with +// powers of two to get around the 32-bit range limitation. Math.floor is +// used instead of truncation since arithmetic right shift fills with the +// sign bit. For negative numbers, the discarded bits were non-zero +// (representing a positive fractional part), so discarding them makes the +// result strictly more negative, i.e. rounding away from 0. +// +// This works because bitwise operators are distributive across bit ranges: +// +// x1 $ x2 = (hi(x1) $ hi(x2)) << 32 | (lo(x1) $ lo(x2)) +// = (hi(x1) $ hi(x2)) * 2^32 + (lo(x1) $ lo(x2)) +// +// JavaScript bitwise operators truncate inputs to signed 32-bit integers, +// so `x1 $ x2` already computes `lo(x1) $ lo(x2)` — we just need to +// reinterpret the signed result as unsigned using `>>> 0`: +// +// lo(x1) $ lo(x2) = (x1 $ x2) >>> 0 = lo(x1 $ x2) +// => x1 $ x2 = (hi(x1) $ hi(x2)) * 2^32 + lo(x1 $ x2). +// +const MIN_I32 = -(2 ** 31); // -2147483648 +const MAX_I32 = 2 ** 31 - 1; // 2147483647 +const U32 = 2 ** 32; +const MAX_SAFE = Number.MAX_SAFE_INTEGER; +const MIN_SAFE = Number.MIN_SAFE_INTEGER; + +export function bitwise_and(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x & y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) & BigInt(y)); + + return (Math.floor(x / U32) & Math.floor(y / U32)) * U32 + ((x & y) >>> 0); +} + +export function bitwise_or(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x | y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) | BigInt(y)); + + return (Math.floor(x / U32) | Math.floor(y / U32)) * U32 + ((x | y) >>> 0); +} + +export function bitwise_exclusive_or(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x ^ y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) ^ BigInt(y)); + + return (Math.floor(x / U32) ^ Math.floor(y / U32)) * U32 + ((x ^ y) >>> 0); +} + +export function bitwise_not(x) { + if (x >= MIN_I32 && x <= MAX_I32) return ~x; + if (x < MIN_SAFE || x > MAX_SAFE) return Number(~BigInt(x)); + + return ~Math.floor(x / U32) * U32 + (~x >>> 0); +} + +export function bitwise_shift_right(x, y) { + if (y === 0) return x; + if (y < 0) return bitwise_shift_left(x, -y); + if (y < 32 && x >= MIN_I32 && x <= MAX_I32) return x >> y; + if (x < MIN_SAFE || x > MAX_SAFE) return Number(BigInt(x) >> BigInt(y)); + + const ahi = Math.floor(x / U32); + + // Shifting right by y < 32 moves bits across the hi/lo boundary: + // + // before: [ hi (21 bits) | lo (32 bits) ] + // after: [ hi >> y | (hi's low y bits) ++ (lo >> y) ] + // + // The new low word has two sources: + // - lo's bits shifted down: x >>> y (>>> treats x as unsigned 32-bit) + // - hi's bottom y bits shifted up: ahi << (32 - y). + if (y < 32) return (ahi >> y) * U32 + (((x >>> y) | (ahi << (32 - y))) >>> 0); + + // Shifting by >= 32 wipes out the entire low word. The result is just the + // high word shifted right by the remaining amount. + return ahi >> (y - 32); +} + +export function bitwise_shift_left(x, y) { + if (y === 0) return x; + if (y < 0) return bitwise_shift_right(x, -y); + if (y < 31) return x * (1 << y); + return x * 2 ** y; +} + +export function inspect(v) { + return new Inspector().inspect(v); +} + +export function float_to_string(float) { + const string = float.toString().replace("+", ""); + if (string.indexOf(".") >= 0) { + return string; + } else { + const index = string.indexOf("e"); + if (index >= 0) { + return string.slice(0, index) + ".0" + string.slice(index); + } else { + return string + ".0"; + } + } +} + +class Inspector { + #references = new Set(); + + inspect(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === undefined) return "Nil"; + if (t === "string") return this.#string(v); + if (t === "bigint" || Number.isInteger(v)) return v.toString(); + if (t === "number") return float_to_string(v); + if (v instanceof UtfCodepoint) return this.#utfCodepoint(v); + if (v instanceof BitArray) return this.#bit_array(v); + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof globalThis.Error) return `//js(${v.toString()})`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + + if (this.#references.size === this.#references.add(v).size) { + return "//js(circular reference)"; + } + + let printed; + if (Array.isArray(v)) { + printed = `#(${v.map((v) => this.inspect(v)).join(", ")})`; + } else if (isList(v)) { + printed = this.#list(v); + } else if (v instanceof CustomType) { + printed = this.#customType(v); + } else if (v instanceof Dict) { + printed = this.#dict(v); + } else if (v instanceof Set) { + return `//js(Set(${[...v].map((v) => this.inspect(v)).join(", ")}))`; + } else { + printed = this.#object(v); + } + this.#references.delete(v); + return printed; + } + + #object(v) { + const name = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${this.inspect(k)}: ${this.inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name === "Object" ? "" : name + " "; + return `//js(${head}{${body}})`; + } + + #dict(map) { + let body = "dict.from_list(["; + let first = true; + + body = dict_fold(map, body, (body, key, value) => { + if (!first) body = body + ", "; + first = false; + return body + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")"; + }); + + return body + "])"; + } + + #customType(record) { + const props = Object.keys(record) + .map((label) => { + const value = this.inspect(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value}` : value; + }) + .join(", "); + return props + ? `${record.constructor.name}(${props})` + : record.constructor.name; + } + + #list(list) { + if (List$isEmpty(list)) { + return "[]"; + } + + let char_out = 'charlist.from_string("'; + let list_out = "["; + + let current = list; + while (List$isNonEmpty(current)) { + let element = current.head; + current = current.tail; + + if (list_out !== "[") { + list_out += ", "; + } + list_out += this.inspect(element); + + if (char_out) { + if (Number.isInteger(element) && element >= 32 && element <= 126) { + char_out += String.fromCharCode(element); + } else { + char_out = null; + } + } + } + + if (char_out) { + return char_out + '")'; + } else { + return list_out + "]"; + } + } + + #string(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + switch (char) { + case "\n": + new_str += "\\n"; + break; + case "\r": + new_str += "\\r"; + break; + case "\t": + new_str += "\\t"; + break; + case "\f": + new_str += "\\f"; + break; + case "\\": + new_str += "\\\\"; + break; + case '"': + new_str += '\\"'; + break; + default: + if (char < " " || (char > "~" && char < "\u{00A0}")) { + new_str += + "\\u{" + + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + + "}"; + } else { + new_str += char; + } + } + } + new_str += '"'; + return new_str; + } + + #utfCodepoint(codepoint) { + return `//utfcodepoint(${String.fromCodePoint(codepoint.value)})`; + } + + #bit_array(bits) { + if (bits.bitSize === 0) { + return "<<>>"; + } + + let acc = "<<"; + + for (let i = 0; i < bits.byteSize - 1; i++) { + acc += bits.byteAt(i).toString(); + acc += ", "; + } + + if (bits.byteSize * 8 === bits.bitSize) { + acc += bits.byteAt(bits.byteSize - 1).toString(); + } else { + const trailingBitsCount = bits.bitSize % 8; + acc += bits.byteAt(bits.byteSize - 1) >> (8 - trailingBitsCount); + acc += `:size(${trailingBitsCount})`; + } + + acc += ">>"; + return acc; + } +} + +export function base16_encode(bit_array) { + const trailingBitsCount = bit_array.bitSize % 8; + + let result = ""; + + for (let i = 0; i < bit_array.byteSize; i++) { + let byte = bit_array.byteAt(i); + + if (i === bit_array.byteSize - 1 && trailingBitsCount !== 0) { + const unusedBitsCount = 8 - trailingBitsCount; + byte = (byte >> unusedBitsCount) << unusedBitsCount; + } + + result += byte.toString(16).padStart(2, "0").toUpperCase(); + } + + return result; +} + +export function base16_decode(string) { + const bytes = new Uint8Array(string.length / 2); + for (let i = 0; i < string.length; i += 2) { + const a = parseInt(string[i], 16); + const b = parseInt(string[i + 1], 16); + if (isNaN(a) || isNaN(b)) return Result$Error(Nil); + bytes[i / 2] = a * 16 + b; + } + return Result$Ok(new BitArray(bytes)); +} + +export function bit_array_to_int_and_size(bits) { + const trailingBitsCount = bits.bitSize % 8; + const unusedBitsCount = trailingBitsCount === 0 ? 0 : 8 - trailingBitsCount; + + return [bits.byteAt(0) >> unusedBitsCount, bits.bitSize]; +} + +export function bit_array_starts_with(bits, prefix) { + if (prefix.bitSize > bits.bitSize) { + return false; + } + + // Check any whole bytes + const byteCount = Math.trunc(prefix.bitSize / 8); + for (let i = 0; i < byteCount; i++) { + if (bits.byteAt(i) !== prefix.byteAt(i)) { + return false; + } + } + + // Check any trailing bits at the end of the prefix + if (prefix.bitSize % 8 !== 0) { + const unusedBitsCount = 8 - (prefix.bitSize % 8); + if ( + bits.byteAt(byteCount) >> unusedBitsCount !== + prefix.byteAt(byteCount) >> unusedBitsCount + ) { + return false; + } + } + + return true; +} + +export function log(x) { + // It is checked in Gleam that: + // - The input is strictly positive (x > 0) + // - This ensures that Math.log will never return NaN or -Infinity + // The function can thus safely pass the input to Math.log + // and a valid finite float will always be produced. + return Math.log(x); +} + +export function exp(x) { + return Math.exp(x); +} + +export function list_to_array(list) { + let current = list; + let array = []; + while (List$isNonEmpty(current)) { + array.push(current.head); + current = current.tail; + } + return array; +} + +export function index(data, key) { + // Dictionaries and dictionary-like objects can be indexed + if (data instanceof Dict) { + const result = dict_get(data, key); + return Result$Ok(result.isOk() ? new Some(result[0]) : new None()); + } + + if (data instanceof WeakMap || data instanceof Map) { + const token = {}; + const entry = data.get(key, token); + if (entry === token) return Result$Ok(new None()); + return Result$Ok(new Some(entry)); + } + + const key_is_int = Number.isInteger(key); + + // Only elements 0-7 of lists can be indexed, negative indices are not allowed + if (key_is_int && key >= 0 && key < 8 && isList(data)) { + let i = 0; + for (const value of data) { + if (i === key) return Result$Ok(new Some(value)); + i++; + } + return Result$Error("Indexable"); + } + + // Arrays and objects can be indexed + if ( + (key_is_int && Array.isArray(data)) || + (data && typeof data === "object") || + (data && Object.getPrototypeOf(data) === Object.prototype) + ) { + if (key in data) return Result$Ok(new Some(data[key])); + return Result$Ok(new None()); + } + + return Result$Error(key_is_int ? "Indexable" : "Dict"); +} + +export function list(data, decode, pushPath, index, emptyList) { + if (!(isList(data) || Array.isArray(data))) { + const error = DecodeError$DecodeError("List", classify(data), emptyList); + return [emptyList, arrayToList([error])]; + } + + const decoded = []; + + for (const element of data) { + const layer = decode(element); + const [out, errors] = layer; + + if (List$isNonEmpty(errors)) { + const [_, errors] = pushPath(layer, index.toString()); + return [emptyList, errors]; + } + decoded.push(out); + index++; + } + + return [arrayToList(decoded), emptyList]; +} + +export function dict(data) { + if (data instanceof Dict) { + return Result$Ok(data); + } + if (data instanceof Map || data instanceof WeakMap) { + return Result$Ok(dict_from_iterable(data)); + } + if (data == null) { + return Result$Error("Dict"); + } + if (typeof data !== "object") { + return Result$Error("Dict"); + } + const proto = Object.getPrototypeOf(data); + if (proto === Object.prototype || proto === null) { + return Result$Ok(dict_from_iterable(Object.entries(data))); + } + return Result$Error("Dict"); +} + +export function bit_array(data) { + if (data instanceof BitArray) return Result$Ok(data); + if (data instanceof Uint8Array) return Result$Ok(new BitArray(data)); + return Result$Error(new BitArray(new Uint8Array())); +} + +export function float(data) { + if (typeof data === "number") return Result$Ok(data); + return Result$Error(0.0); +} + +export function int(data) { + if (Number.isInteger(data)) return Result$Ok(data); + return Result$Error(0); +} + +export function string(data) { + if (typeof data === "string") return Result$Ok(data); + return Result$Error(""); +} + +export function is_null(data) { + return data === null || data === undefined; +} + +function arrayToList(array) { + let list = List$Empty(); + let i = array.length; + while (i--) { + list = List$NonEmpty(array[i], list); + } + return list; +} + +function isList(data) { + return List$isEmpty(data) || List$isNonEmpty(data); +} + +function isResult(data) { + return Result$isOk(data) || Result$isError(data); +} + +export function string_remove_prefix(string, prefix) { + if (string.startsWith(prefix)) { + return string.slice(prefix.length); + } + return string; +} + +export function string_remove_suffix(string, suffix) { + if (string.endsWith(suffix)) { + return string.slice(0, string.length - suffix.length); + } + return string; +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/LICENCE b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/LICENCE new file mode 100644 index 0000000..c7967c3 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/README.md b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/README.md new file mode 100644 index 0000000..4d81364 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/README.md @@ -0,0 +1,44 @@ +# gleeunit + +A simple test runner for Gleam, using EUnit on Erlang and a custom runner on JS. + +[![Package Version](https://img.shields.io/hexpm/v/gleeunit)](https://hex.pm/packages/gleeunit) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleeunit/) + + +```sh +gleam add gleeunit@1 --dev +``` +```gleam +// In test/yourapp_test.gleam +import gleeunit + +pub fn main() { + gleeunit.main() +} +``` + +Now any public function with a name ending in `_test` in the `test` directory +will be found and run as a test. + +```gleam +pub fn some_function_test() { + assert some_function() == "Hello!" +} +``` + +Run the tests by entering `gleam test` in the command line. + +### Deno + +If using the Deno JavaScript runtime, you will need to add the following to your +`gleam.toml`. + +```toml +[javascript.deno] +allow_read = [ + "gleam.toml", + "test", + "build", +] +``` diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/gleam.toml b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/gleam.toml new file mode 100644 index 0000000..fea4efe --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/gleam.toml @@ -0,0 +1,16 @@ +name = "gleeunit" +version = "1.10.0" +licences = ["Apache-2.0"] +description = "A simple test runner for Gleam, using EUnit on Erlang" +repository = { type = "github", user = "lpil", repo = "gleeunit" } +links = [{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }] +gleam = ">= 1.13.0" + +[javascript.deno] +allow_read = ["gleam.toml", "test", "build"] + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +testhelper = { "path" = "./testhelper" } diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl new file mode 100644 index 0000000..9360941 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl @@ -0,0 +1,6 @@ +-record(assert, { + start :: integer(), + 'end' :: integer(), + expression_start :: integer(), + kind :: gleeunit@internal@gleam_panic:assert_kind() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl new file mode 100644 index 0000000..812663c --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl @@ -0,0 +1,5 @@ +-record(asserted_expression, { + start :: integer(), + 'end' :: integer(), + kind :: gleeunit@internal@gleam_panic:expression_kind() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl new file mode 100644 index 0000000..eee44c9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl @@ -0,0 +1,5 @@ +-record(binary_operator, { + operator :: binary(), + left :: gleeunit@internal@gleam_panic:asserted_expression(), + right :: gleeunit@internal@gleam_panic:asserted_expression() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl new file mode 100644 index 0000000..e7ffaa0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl @@ -0,0 +1 @@ +-record(expression, {value :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl new file mode 100644 index 0000000..9d55488 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl @@ -0,0 +1,3 @@ +-record(function_call, { + arguments :: list(gleeunit@internal@gleam_panic:asserted_expression()) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl new file mode 100644 index 0000000..cf36764 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl @@ -0,0 +1,8 @@ +-record(gleam_panic, { + message :: binary(), + file :: binary(), + module :: binary(), + function :: binary(), + line :: integer(), + kind :: gleeunit@internal@gleam_panic:panic_kind() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl new file mode 100644 index 0000000..11f865e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl @@ -0,0 +1,7 @@ +-record(let_assert, { + start :: integer(), + 'end' :: integer(), + pattern_start :: integer(), + pattern_end :: integer(), + value :: gleam@dynamic:dynamic_() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl new file mode 100644 index 0000000..2396489 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl @@ -0,0 +1 @@ +-record(literal, {value :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl new file mode 100644 index 0000000..0424990 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl @@ -0,0 +1,3 @@ +-record(other_expression, { + expression :: gleeunit@internal@gleam_panic:asserted_expression() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl new file mode 100644 index 0000000..575ccce --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl @@ -0,0 +1 @@ +-record(state, {passed :: integer(), failed :: integer(), skipped :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.app.src b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.app.src new file mode 100644 index 0000000..519beb8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.app.src @@ -0,0 +1,16 @@ +{application, gleeunit, [ + {vsn, "1.10.0"}, + {applications, [gleam_stdlib]}, + {description, "A simple test runner for Gleam, using EUnit on Erlang"}, + {modules, [erlang_test_module, + gleeunit, + gleeunit@@main, + gleeunit@internal@gleam_panic, + gleeunit@internal@reporting, + gleeunit@should, + gleeunit_ffi, + gleeunit_gleam_panic_ffi, + gleeunit_progress, + gleeunit_test_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.erl new file mode 100644 index 0000000..bcb58b6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.erl @@ -0,0 +1,89 @@ +-module(gleeunit). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit.gleam"). +-export([main/0]). +-export_type([atom_/0, encoding/0, report_module_name/0, gleeunit_progress_option/0, eunit_option/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type atom_() :: any(). + +-type encoding() :: utf8. + +-type report_module_name() :: gleeunit_progress. + +-type gleeunit_progress_option() :: {colored, boolean()}. + +-type eunit_option() :: verbose | + no_tty | + {report, {report_module_name(), list(gleeunit_progress_option())}} | + {scale_timeouts, integer()}. + +-file("src/gleeunit.gleam", 42). +-spec gleam_to_erlang_module_name(binary()) -> binary(). +gleam_to_erlang_module_name(Path) -> + case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of + true -> + _pipe = Path, + _pipe@1 = gleam@string:replace( + _pipe, + <<".gleam"/utf8>>, + <<""/utf8>> + ), + gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"@"/utf8>>); + + false -> + _pipe@2 = Path, + _pipe@3 = gleam@string:split(_pipe@2, <<"/"/utf8>>), + _pipe@4 = gleam@list:last(_pipe@3), + _pipe@5 = gleam@result:unwrap(_pipe@4, Path), + gleam@string:replace(_pipe@5, <<".erl"/utf8>>, <<""/utf8>>) + end. + +-file("src/gleeunit.gleam", 18). +-spec do_main() -> nil. +do_main() -> + Options = [verbose, + no_tty, + {report, {gleeunit_progress, [{colored, true}]}}, + {scale_timeouts, 10}], + Result = begin + _pipe = gleeunit_ffi:find_files( + <<"**/*.{erl,gleam}"/utf8>>, + <<"test"/utf8>> + ), + _pipe@1 = gleam@list:map(_pipe, fun gleam_to_erlang_module_name/1), + _pipe@2 = gleam@list:map( + _pipe@1, + fun(_capture) -> erlang:binary_to_atom(_capture, utf8) end + ), + gleeunit_ffi:run_eunit(_pipe@2, Options) + end, + Code = case Result of + {ok, _} -> + 0; + + {error, _} -> + 1 + end, + erlang:halt(Code). + +-file("src/gleeunit.gleam", 13). +?DOC( + " Find and run all test functions for the current project using Erlang's EUnit\n" + " test framework, or a custom JavaScript test runner.\n" + "\n" + " Any Erlang or Gleam function in the `test` directory with a name ending in\n" + " `_test` is considered a test function and will be run.\n" + "\n" + " A test that panics is considered a failure.\n" +). +-spec main() -> nil. +main() -> + do_main(). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.gleam new file mode 100644 index 0000000..7d3155e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit.gleam @@ -0,0 +1,86 @@ +import gleam/list +import gleam/result +import gleam/string + +/// Find and run all test functions for the current project using Erlang's EUnit +/// test framework, or a custom JavaScript test runner. +/// +/// Any Erlang or Gleam function in the `test` directory with a name ending in +/// `_test` is considered a test function and will be run. +/// +/// A test that panics is considered a failure. +/// +pub fn main() -> Nil { + do_main() +} + +@external(javascript, "./gleeunit_ffi.mjs", "main") +fn do_main() -> Nil { + let options = [ + Verbose, + NoTty, + Report(#(GleeunitProgress, [Colored(True)])), + ScaleTimeouts(10), + ] + + let result = + find_files(matching: "**/*.{erl,gleam}", in: "test") + |> list.map(gleam_to_erlang_module_name) + |> list.map(dangerously_convert_string_to_atom(_, Utf8)) + |> run_eunit(options) + + let code = case result { + Ok(_) -> 0 + Error(_) -> 1 + } + halt(code) +} + +@external(erlang, "erlang", "halt") +fn halt(a: Int) -> Nil + +fn gleam_to_erlang_module_name(path: String) -> String { + case string.ends_with(path, ".gleam") { + True -> + path + |> string.replace(".gleam", "") + |> string.replace("/", "@") + + False -> + path + |> string.split("/") + |> list.last + |> result.unwrap(path) + |> string.replace(".erl", "") + } +} + +@external(erlang, "gleeunit_ffi", "find_files") +fn find_files(matching matching: String, in in: String) -> List(String) + +type Atom + +type Encoding { + Utf8 +} + +@external(erlang, "erlang", "binary_to_atom") +fn dangerously_convert_string_to_atom(a: String, b: Encoding) -> Atom + +type ReportModuleName { + GleeunitProgress +} + +type GleeunitProgressOption { + Colored(Bool) +} + +type EunitOption { + Verbose + NoTty + Report(#(ReportModuleName, List(GleeunitProgressOption))) + ScaleTimeouts(Int) +} + +@external(erlang, "gleeunit_ffi", "run_eunit") +fn run_eunit(a: List(Atom), b: List(EunitOption)) -> Result(Nil, a) diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam new file mode 100644 index 0000000..6a5d309 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam @@ -0,0 +1,49 @@ +import gleam/dynamic + +pub type GleamPanic { + GleamPanic( + message: String, + file: String, + module: String, + function: String, + line: Int, + kind: PanicKind, + ) +} + +pub type PanicKind { + Todo + Panic + LetAssert( + start: Int, + end: Int, + pattern_start: Int, + pattern_end: Int, + value: dynamic.Dynamic, + ) + Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind) +} + +pub type AssertKind { + BinaryOperator( + operator: String, + left: AssertedExpression, + right: AssertedExpression, + ) + FunctionCall(arguments: List(AssertedExpression)) + OtherExpression(expression: AssertedExpression) +} + +pub type AssertedExpression { + AssertedExpression(start: Int, end: Int, kind: ExpressionKind) +} + +pub type ExpressionKind { + Literal(value: dynamic.Dynamic) + Expression(value: dynamic.Dynamic) + Unevaluated +} + +@external(erlang, "gleeunit_gleam_panic_ffi", "from_dynamic") +@external(javascript, "./gleeunit_gleam_panic_ffi.mjs", "from_dynamic") +pub fn from_dynamic(data: dynamic.Dynamic) -> Result(GleamPanic, Nil) diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl new file mode 100644 index 0000000..d78f5e5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl @@ -0,0 +1,49 @@ +-module(gleeunit_gleam_panic_ffi). +-export([from_dynamic/1]). + +from_dynamic(#{ + gleam_error := assert, + start := Start, + 'end' := End, + expression_start := EStart +} = E) -> + wrap(E, {assert, Start, End, EStart, assert_kind(E)}); +from_dynamic(#{ + gleam_error := let_assert, + start := Start, + 'end' := End, + pattern_start := PStart, + pattern_end := PEnd, + value := Value +} = E) -> + wrap(E, {let_assert, Start, End, PStart, PEnd, Value}); +from_dynamic(#{gleam_error := panic} = E) -> + wrap(E, panic); +from_dynamic(#{gleam_error := todo} = E) -> + wrap(E, todo); +from_dynamic(_) -> + {error, nil}. + +assert_kind(#{kind := binary_operator, left := L, right := R, operator := O}) -> + {binary_operator, atom_to_binary(O), expression(L), expression(R)}; +assert_kind(#{kind := function_call, arguments := Arguments}) -> + {function_call, lists:map(fun expression/1, Arguments)}; +assert_kind(#{kind := expression, expression := Expression}) -> + {other_expression, expression(Expression)}. + +expression(#{start := S, 'end' := E, kind := literal, value := Value}) -> + {asserted_expression, S, E, {literal, Value}}; +expression(#{start := S, 'end' := E, kind := expression, value := Value}) -> + {asserted_expression, S, E, {expression, Value}}; +expression(#{start := S, 'end' := E, kind := unevaluated}) -> + {asserted_expression, S, E, unevaluated}. + +wrap(#{ + gleam_error := _, + file := File, + message := Message, + module := Module, + function := Function, + line := Line +}, Kind) -> + {ok, {gleam_panic, Message, File, Module, Function, Line, Kind}}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs new file mode 100644 index 0000000..03f6025 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs @@ -0,0 +1,91 @@ +import { Result$Ok, Result$Error, List$Empty, List$NonEmpty } from "../../gleam.mjs"; +import { + GleamPanic$GleamPanic, + PanicKind$Todo, + PanicKind$Panic, + PanicKind$LetAssert, + PanicKind$Assert, + AssertKind$BinaryOperator, + AssertKind$FunctionCall, + AssertKind$OtherExpression, + AssertedExpression$AssertedExpression, + ExpressionKind$Literal, + ExpressionKind$Expression, + ExpressionKind$Unevaluated, +} from "./gleam_panic.mjs"; + +export function from_dynamic(error) { + if (!(error instanceof globalThis.Error) || !error.gleam_error) { + return Result$Error(undefined); + } + + if (error.gleam_error === "todo") { + return wrap(error, PanicKind$Todo()); + } + + if (error.gleam_error === "panic") { + return wrap(error, PanicKind$Panic()); + } + + if (error.gleam_error === "let_assert") { + let kind = PanicKind$LetAssert( + error.start, + error.end, + error.pattern_start, + error.pattern_end, + error.value, + ); + return wrap(error, kind); + } + + if (error.gleam_error === "assert") { + let kind = PanicKind$Assert( + error.start, + error.end, + error.expression_start, + assert_kind(error), + ); + return wrap(error, kind); + } + + return Result$Error(undefined); +} + +function assert_kind(error) { + if (error.kind == "binary_operator") { + return AssertKind$BinaryOperator( + error.operator, + expression(error.left), + expression(error.right), + ); + } + + if (error.kind == "function_call") { + let list = List$Empty(); + let i = error.arguments.length; + while (i--) { + list = List$NonEmpty(expression(error.arguments[i]), list); + } + return AssertKind$FunctionCall(list); + } + + return AssertKind$OtherExpression(expression(error.expression)); +} + +function expression(data) { + const expression = AssertedExpression$AssertedExpression(data.start, data.end, undefined); + if (data.kind == "literal") { + expression.kind = ExpressionKind$Literal(data.value); + } else if (data.kind == "expression") { + expression.kind = ExpressionKind$Expression(data.value); + } else { + expression.kind = ExpressionKind$Unevaluated(); + } + return expression; +} + +function wrap(e, kind) { + return Result$Ok( + GleamPanic$GleamPanic(e.message, e.file, e.module, e.function, e.line, kind), + ); +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam new file mode 100644 index 0000000..72f766f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam @@ -0,0 +1,240 @@ +import gleam/bit_array +import gleam/dynamic +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string +import gleeunit/internal/gleam_panic.{type GleamPanic} + +pub type State { + State(passed: Int, failed: Int, skipped: Int) +} + +pub fn new_state() -> State { + State(passed: 0, failed: 0, skipped: 0) +} + +pub fn finished(state: State) -> Int { + case state { + State(passed: 0, failed: 0, skipped: 0) -> { + io.println("\nNo tests found!") + 1 + } + State(failed: 0, skipped: 0, ..) -> { + let message = + "\n" <> int.to_string(state.passed) <> " passed, no failures" + io.println(green(message)) + 0 + } + State(skipped: 0, ..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, " + <> int.to_string(state.failed) + <> " failures" + io.println(red(message)) + 1 + } + State(failed: 0, ..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, 0 failures, " + <> int.to_string(state.skipped) + <> " skipped" + io.println(yellow(message)) + 1 + } + State(..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, " + <> int.to_string(state.failed) + <> " failures, " + <> int.to_string(state.skipped) + <> " skipped" + io.println(red(message)) + 1 + } + } +} + +pub fn test_passed(state: State) -> State { + io.print(green(".")) + State(..state, passed: state.passed + 1) +} + +pub fn test_failed( + state: State, + module: String, + function: String, + error: dynamic.Dynamic, +) -> State { + let message = case gleam_panic.from_dynamic(error) { + Ok(error) -> { + let src = option.from_result(read_file(error.file)) + format_gleam_error(error, module, function, src) + } + Error(_) -> format_unknown(module, function, error) + } + + io.print("\n" <> message) + State(..state, failed: state.failed + 1) +} + +pub fn eunit_missing() -> Result(never, Nil) { + let message = bold(red("Error")) <> ": EUnit libraries not found. + +Your Erlang installation seems to be incomplete. If you installed Erlang using +a package manager ensure that you have installed the full Erlang +distribution instead of a stripped-down version. +" + io.print_error(message) + Error(Nil) +} + +fn format_unknown( + module: String, + function: String, + error: dynamic.Dynamic, +) -> String { + string.concat([ + grey(module <> "." <> function) <> "\n", + "An unexpected error occurred:\n", + "\n", + " " <> string.inspect(error) <> "\n", + ]) +} + +fn format_gleam_error( + error: GleamPanic, + module: String, + function: String, + src: Option(BitArray), +) -> String { + let location = grey(error.file <> ":" <> int.to_string(error.line)) + + case error.kind { + gleam_panic.Panic -> { + string.concat([ + bold(red("panic")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.Todo -> { + string.concat([ + bold(yellow("todo")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.Assert(start:, end:, kind:, ..) -> { + string.concat([ + bold(red("assert")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + code_snippet(src, start, end), + assert_info(kind), + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.LetAssert(start:, end:, value:, ..) -> { + string.concat([ + bold(red("let assert")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + code_snippet(src, start, end), + cyan("value") <> ": " <> string.inspect(value) <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + } +} + +fn assert_info(kind: gleam_panic.AssertKind) -> String { + case kind { + gleam_panic.BinaryOperator(left:, right:, ..) -> { + string.concat([assert_value(" left", left), assert_value("right", right)]) + } + + gleam_panic.FunctionCall(arguments:) -> { + arguments + |> list.index_map(fn(e, i) { + let number = string.pad_start(int.to_string(i), 5, " ") + assert_value(number, e) + }) + |> string.concat + } + + gleam_panic.OtherExpression(..) -> "" + } +} + +fn assert_value(name: String, value: gleam_panic.AssertedExpression) -> String { + cyan(name) <> ": " <> inspect_value(value) <> "\n" +} + +fn inspect_value(value: gleam_panic.AssertedExpression) -> String { + case value.kind { + gleam_panic.Unevaluated -> grey("unevaluated") + gleam_panic.Literal(..) -> grey("literal") + gleam_panic.Expression(value:) -> string.inspect(value) + } +} + +fn code_snippet(src: Option(BitArray), start: Int, end: Int) -> String { + { + use src <- result.try(option.to_result(src, Nil)) + use snippet <- result.try(bit_array.slice(src, start, end - start)) + use snippet <- result.try(bit_array.to_string(snippet)) + let snippet = cyan(" code") <> ": " <> snippet <> "\n" + Ok(snippet) + } + |> result.unwrap("") +} + +pub fn test_skipped(state: State, module: String, function: String) -> State { + io.print("\n" <> module <> "." <> function <> yellow(" skipped")) + State(..state, skipped: state.skipped + 1) +} + +fn bold(text: String) -> String { + "\u{001b}[1m" <> text <> "\u{001b}[22m" +} + +fn cyan(text: String) -> String { + "\u{001b}[36m" <> text <> "\u{001b}[39m" +} + +fn yellow(text: String) -> String { + "\u{001b}[33m" <> text <> "\u{001b}[39m" +} + +fn green(text: String) -> String { + "\u{001b}[32m" <> text <> "\u{001b}[39m" +} + +fn red(text: String) -> String { + "\u{001b}[31m" <> text <> "\u{001b}[39m" +} + +fn grey(text: String) -> String { + "\u{001b}[90m" <> text <> "\u{001b}[39m" +} + +@external(erlang, "file", "read_file") +fn read_file(path: String) -> Result(BitArray, dynamic.Dynamic) { + case read_file_text(path) { + Ok(text) -> Ok(bit_array.from_string(text)) + Error(e) -> Error(e) + } +} + +@external(javascript, "../../gleeunit_ffi.mjs", "read_file") +fn read_file_text(path: String) -> Result(String, dynamic.Dynamic) diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/should.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/should.gleam new file mode 100644 index 0000000..99cd16c --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit/should.gleam @@ -0,0 +1,72 @@ +//// Use the `assert` keyword instead of this module. + +import gleam/option.{type Option, None, Some} +import gleam/string + +pub fn equal(a: t, b: t) -> Nil { + case a == b { + True -> Nil + _ -> + panic as string.concat([ + "\n", + string.inspect(a), + "\nshould equal\n", + string.inspect(b), + ]) + } +} + +pub fn not_equal(a: t, b: t) -> Nil { + case a != b { + True -> Nil + _ -> + panic as string.concat([ + "\n", + string.inspect(a), + "\nshould not equal\n", + string.inspect(b), + ]) + } +} + +pub fn be_ok(a: Result(a, e)) -> a { + case a { + Ok(value) -> value + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be ok"]) + } +} + +pub fn be_error(a: Result(a, e)) -> e { + case a { + Error(error) -> error + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be error"]) + } +} + +pub fn be_some(a: Option(a)) -> a { + case a { + Some(value) -> value + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be some"]) + } +} + +pub fn be_none(a: Option(a)) -> Nil { + case a { + None -> Nil + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be none"]) + } +} + +pub fn be_true(actual: Bool) -> Nil { + actual + |> equal(True) +} + +pub fn be_false(actual: Bool) -> Nil { + actual + |> equal(False) +} + +pub fn fail() -> Nil { + be_true(False) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl new file mode 100644 index 0000000..398ea7d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl @@ -0,0 +1,56 @@ +-module(gleeunit@internal@gleam_panic). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/internal/gleam_panic.gleam"). +-export([from_dynamic/1]). +-export_type([gleam_panic/0, panic_kind/0, assert_kind/0, asserted_expression/0, expression_kind/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type gleam_panic() :: {gleam_panic, + binary(), + binary(), + binary(), + binary(), + integer(), + panic_kind()}. + +-type panic_kind() :: todo | + panic | + {let_assert, + integer(), + integer(), + integer(), + integer(), + gleam@dynamic:dynamic_()} | + {assert, integer(), integer(), integer(), assert_kind()}. + +-type assert_kind() :: {binary_operator, + binary(), + asserted_expression(), + asserted_expression()} | + {function_call, list(asserted_expression())} | + {other_expression, asserted_expression()}. + +-type asserted_expression() :: {asserted_expression, + integer(), + integer(), + expression_kind()}. + +-type expression_kind() :: {literal, gleam@dynamic:dynamic_()} | + {expression, gleam@dynamic:dynamic_()} | + unevaluated. + +-file("src/gleeunit/internal/gleam_panic.gleam", 49). +?DOC(false). +-spec from_dynamic(gleam@dynamic:dynamic_()) -> {ok, gleam_panic()} | + {error, nil}. +from_dynamic(Data) -> + gleeunit_gleam_panic_ffi:from_dynamic(Data). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@reporting.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@reporting.erl new file mode 100644 index 0000000..8c37c79 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@internal@reporting.erl @@ -0,0 +1,343 @@ +-module(gleeunit@internal@reporting). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/internal/reporting.gleam"). +-export([new_state/0, test_skipped/3, test_passed/1, finished/1, eunit_missing/0, test_failed/4]). +-export_type([state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type state() :: {state, integer(), integer(), integer()}. + +-file("src/gleeunit/internal/reporting.gleam", 15). +?DOC(false). +-spec new_state() -> state(). +new_state() -> + {state, 0, 0, 0}. + +-file("src/gleeunit/internal/reporting.gleam", 207). +?DOC(false). +-spec bold(binary()) -> binary(). +bold(Text) -> + <<<<"\x{001b}[1m"/utf8, Text/binary>>/binary, "\x{001b}[22m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 211). +?DOC(false). +-spec cyan(binary()) -> binary(). +cyan(Text) -> + <<<<"\x{001b}[36m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 191). +?DOC(false). +-spec code_snippet(gleam@option:option(bitstring()), integer(), integer()) -> binary(). +code_snippet(Src, Start, End) -> + _pipe = begin + gleam@result:'try'( + gleam@option:to_result(Src, nil), + fun(Src@1) -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Src@1, Start, End - Start), + fun(Snippet) -> + gleam@result:'try'( + gleam@bit_array:to_string(Snippet), + fun(Snippet@1) -> + Snippet@2 = <<<<<<(cyan(<<" code"/utf8>>))/binary, + ": "/utf8>>/binary, + Snippet@1/binary>>/binary, + "\n"/utf8>>, + {ok, Snippet@2} + end + ) + end + ) + end + ) + end, + gleam@result:unwrap(_pipe, <<""/utf8>>). + +-file("src/gleeunit/internal/reporting.gleam", 215). +?DOC(false). +-spec yellow(binary()) -> binary(). +yellow(Text) -> + <<<<"\x{001b}[33m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 202). +?DOC(false). +-spec test_skipped(state(), binary(), binary()) -> state(). +test_skipped(State, Module, Function) -> + gleam_stdlib:print( + <<<<<<<<"\n"/utf8, Module/binary>>/binary, "."/utf8>>/binary, + Function/binary>>/binary, + (yellow(<<" skipped"/utf8>>))/binary>> + ), + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State) + 1}. + +-file("src/gleeunit/internal/reporting.gleam", 219). +?DOC(false). +-spec green(binary()) -> binary(). +green(Text) -> + <<<<"\x{001b}[32m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 66). +?DOC(false). +-spec test_passed(state()) -> state(). +test_passed(State) -> + gleam_stdlib:print(green(<<"."/utf8>>)), + {state, + erlang:element(2, State) + 1, + erlang:element(3, State), + erlang:element(4, State)}. + +-file("src/gleeunit/internal/reporting.gleam", 223). +?DOC(false). +-spec red(binary()) -> binary(). +red(Text) -> + <<<<"\x{001b}[31m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 19). +?DOC(false). +-spec finished(state()) -> integer(). +finished(State) -> + case State of + {state, 0, 0, 0} -> + gleam_stdlib:println(<<"\nNo tests found!"/utf8>>), + 1; + + {state, _, 0, 0} -> + Message = <<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, no failures"/utf8>>, + gleam_stdlib:println(green(Message)), + 0; + + {state, _, _, 0} -> + Message@1 = <<<<<<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary, + " failures"/utf8>>, + gleam_stdlib:println(red(Message@1)), + 1; + + {state, _, 0, _} -> + Message@2 = <<<<<<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, 0 failures, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary, + " skipped"/utf8>>, + gleam_stdlib:println(yellow(Message@2)), + 1; + + {state, _, _, _} -> + Message@3 = <<<<<<<<<<<<"\n"/utf8, + (erlang:integer_to_binary( + erlang:element(2, State) + ))/binary>>/binary, + " passed, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary, + " failures, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary, + " skipped"/utf8>>, + gleam_stdlib:println(red(Message@3)), + 1 + end. + +-file("src/gleeunit/internal/reporting.gleam", 89). +?DOC(false). +-spec eunit_missing() -> {ok, any()} | {error, nil}. +eunit_missing() -> + Message = <<(bold(red(<<"Error"/utf8>>)))/binary, + ": EUnit libraries not found. + +Your Erlang installation seems to be incomplete. If you installed Erlang using +a package manager ensure that you have installed the full Erlang +distribution instead of a stripped-down version. +"/utf8>>, + gleam_stdlib:print_error(Message), + {error, nil}. + +-file("src/gleeunit/internal/reporting.gleam", 227). +?DOC(false). +-spec grey(binary()) -> binary(). +grey(Text) -> + <<<<"\x{001b}[90m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 100). +?DOC(false). +-spec format_unknown(binary(), binary(), gleam@dynamic:dynamic_()) -> binary(). +format_unknown(Module, Function, Error) -> + erlang:list_to_binary( + [<<(grey(<<<>/binary, Function/binary>>))/binary, + "\n"/utf8>>, + <<"An unexpected error occurred:\n"/utf8>>, + <<"\n"/utf8>>, + <<<<" "/utf8, (gleam@string:inspect(Error))/binary>>/binary, + "\n"/utf8>>] + ). + +-file("src/gleeunit/internal/reporting.gleam", 183). +?DOC(false). +-spec inspect_value(gleeunit@internal@gleam_panic:asserted_expression()) -> binary(). +inspect_value(Value) -> + case erlang:element(4, Value) of + unevaluated -> + grey(<<"unevaluated"/utf8>>); + + {literal, _} -> + grey(<<"literal"/utf8>>); + + {expression, Value@1} -> + gleam@string:inspect(Value@1) + end. + +-file("src/gleeunit/internal/reporting.gleam", 179). +?DOC(false). +-spec assert_value( + binary(), + gleeunit@internal@gleam_panic:asserted_expression() +) -> binary(). +assert_value(Name, Value) -> + <<<<<<(cyan(Name))/binary, ": "/utf8>>/binary, + (inspect_value(Value))/binary>>/binary, + "\n"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 160). +?DOC(false). +-spec assert_info(gleeunit@internal@gleam_panic:assert_kind()) -> binary(). +assert_info(Kind) -> + case Kind of + {binary_operator, _, Left, Right} -> + erlang:list_to_binary( + [assert_value(<<" left"/utf8>>, Left), + assert_value(<<"right"/utf8>>, Right)] + ); + + {function_call, Arguments} -> + _pipe = Arguments, + _pipe@1 = gleam@list:index_map( + _pipe, + fun(E, I) -> + Number = gleam@string:pad_start( + erlang:integer_to_binary(I), + 5, + <<" "/utf8>> + ), + assert_value(Number, E) + end + ), + erlang:list_to_binary(_pipe@1); + + {other_expression, _} -> + <<""/utf8>> + end. + +-file("src/gleeunit/internal/reporting.gleam", 113). +?DOC(false). +-spec format_gleam_error( + gleeunit@internal@gleam_panic:gleam_panic(), + binary(), + binary(), + gleam@option:option(bitstring()) +) -> binary(). +format_gleam_error(Error, Module, Function, Src) -> + Location = grey( + <<<<(erlang:element(3, Error))/binary, ":"/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(6, Error)))/binary>> + ), + case erlang:element(7, Error) of + panic -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"panic"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + todo -> + erlang:list_to_binary( + [<<<<<<(bold(yellow(<<"todo"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + {assert, Start, End, _, Kind} -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"assert"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + code_snippet(Src, Start, End), + assert_info(Kind), + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + {let_assert, Start@1, End@1, _, _, Value} -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"let assert"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + code_snippet(Src, Start@1, End@1), + <<<<<<(cyan(<<"value"/utf8>>))/binary, ": "/utf8>>/binary, + (gleam@string:inspect(Value))/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ) + end. + +-file("src/gleeunit/internal/reporting.gleam", 71). +?DOC(false). +-spec test_failed(state(), binary(), binary(), gleam@dynamic:dynamic_()) -> state(). +test_failed(State, Module, Function, Error) -> + Message = case gleeunit_gleam_panic_ffi:from_dynamic(Error) of + {ok, Error@1} -> + Src = gleam@option:from_result( + file:read_file(erlang:element(3, Error@1)) + ), + format_gleam_error(Error@1, Module, Function, Src); + + {error, _} -> + format_unknown(Module, Function, Error) + end, + gleam_stdlib:print(<<"\n"/utf8, Message/binary>>), + {state, + erlang:element(2, State), + erlang:element(3, State) + 1, + erlang:element(4, State)}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@should.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@should.erl new file mode 100644 index 0000000..dd0f07a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit@should.erl @@ -0,0 +1,153 @@ +-module(gleeunit@should). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/should.gleam"). +-export([equal/2, not_equal/2, be_ok/1, be_error/1, be_some/1, be_none/1, be_true/1, be_false/1, fail/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(" Use the `assert` keyword instead of this module.\n"). + +-file("src/gleeunit/should.gleam", 6). +-spec equal(DOJ, DOJ) -> nil. +equal(A, B) -> + case A =:= B of + true -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould equal\n"/utf8>>, + gleam@string:inspect(B)] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"equal"/utf8>>, + line => 10}) + end. + +-file("src/gleeunit/should.gleam", 19). +-spec not_equal(DOK, DOK) -> nil. +not_equal(A, B) -> + case A /= B of + true -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould not equal\n"/utf8>>, + gleam@string:inspect(B)] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"not_equal"/utf8>>, + line => 23}) + end. + +-file("src/gleeunit/should.gleam", 32). +-spec be_ok({ok, DOL} | {error, any()}) -> DOL. +be_ok(A) -> + case A of + {ok, Value} -> + Value; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be ok"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_ok"/utf8>>, + line => 35}) + end. + +-file("src/gleeunit/should.gleam", 39). +-spec be_error({ok, any()} | {error, DOQ}) -> DOQ. +be_error(A) -> + case A of + {error, Error} -> + Error; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be error"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_error"/utf8>>, + line => 42}) + end. + +-file("src/gleeunit/should.gleam", 46). +-spec be_some(gleam@option:option(DOT)) -> DOT. +be_some(A) -> + case A of + {some, Value} -> + Value; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be some"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_some"/utf8>>, + line => 49}) + end. + +-file("src/gleeunit/should.gleam", 53). +-spec be_none(gleam@option:option(any())) -> nil. +be_none(A) -> + case A of + none -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be none"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_none"/utf8>>, + line => 56}) + end. + +-file("src/gleeunit/should.gleam", 60). +-spec be_true(boolean()) -> nil. +be_true(Actual) -> + _pipe = Actual, + equal(_pipe, true). + +-file("src/gleeunit/should.gleam", 65). +-spec be_false(boolean()) -> nil. +be_false(Actual) -> + _pipe = Actual, + equal(_pipe, false). + +-file("src/gleeunit/should.gleam", 70). +-spec fail() -> nil. +fail() -> + be_true(false). diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.erl new file mode 100644 index 0000000..05c7490 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.erl @@ -0,0 +1,21 @@ +-module(gleeunit_ffi). + +-export([find_files/2, run_eunit/2]). + +find_files(Pattern, In) -> + Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), + lists:map(fun list_to_binary/1, Results). + +run_eunit(Tests, Options) -> + case code:which(eunit) of + non_existing -> + gleeunit@internal@reporting:eunit_missing(); + + _ -> + case eunit:test(Tests, Options) of + ok -> {ok, nil}; + error -> {error, nil}; + {error, Term} -> {error, Term} + end + end. + diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.mjs b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.mjs new file mode 100644 index 0000000..7bdc071 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_ffi.mjs @@ -0,0 +1,100 @@ +import { readFileSync } from "node:fs"; +import { Result$Ok, Result$Error } from "./gleam.mjs"; +import * as reporting from "./gleeunit/internal/reporting.mjs"; + +export function read_file(path) { + try { + return Result$Ok(readFileSync(path)); + } catch { + return Result$Error(undefined); + } +} + +async function* gleamFiles(directory) { + for (let entry of await read_dir(directory)) { + let path = join_path(directory, entry); + if (path.endsWith(".gleam")) { + yield path; + } else { + try { + yield* gleamFiles(path); + } catch (error) { + // Could not read directory, assume it's a file + } + } + } +} + +async function readRootPackageName() { + let toml = await async_read_file("gleam.toml", "utf-8"); + for (let line of toml.split("\n")) { + let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() + if (matches) return matches[1]; + } + throw new Error("Could not determine package name from gleam.toml"); +} + +export async function main() { + let state = reporting.new_state(); + + let packageName = await readRootPackageName(); + let dist = `../${packageName}/`; + + for await (let path of await gleamFiles("test")) { + let js_path = path.slice("test/".length).replace(".gleam", ".mjs"); + let module = await import(join_path(dist, js_path)); + for (let fnName of Object.keys(module)) { + if (!fnName.endsWith("_test")) continue; + try { + await module[fnName](); + state = reporting.test_passed(state); + } catch (error) { + let moduleName = js_path.slice(0, -4); + state = reporting.test_failed(state, moduleName, fnName, error); + } + } + } + + const status = reporting.finished(state); + exit(status); +} + +export function crash(message) { + throw new Error(message); +} + +function exit(code) { + if (globalThis.Deno) { + Deno.exit(code); + } else { + process.exit(code); + } +} + +async function read_dir(path) { + if (globalThis.Deno) { + let items = []; + for await (let item of Deno.readDir(path, { withFileTypes: true })) { + items.push(item.name); + } + return items; + } else { + let { readdir } = await import("node:fs/promises"); + return readdir(path); + } +} + +function join_path(a, b) { + if (a.endsWith("/")) return a + b; + return a + "/" + b; +} + +async function async_read_file(path) { + if (globalThis.Deno) { + return Deno.readTextFile(path); + } else { + let { readFile } = await import("node:fs/promises"); + let contents = await readFile(path); + return contents.toString(); + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_progress.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_progress.erl new file mode 100644 index 0000000..e6576a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/gleeunit/src/gleeunit_progress.erl @@ -0,0 +1,72 @@ +%% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters + +-module(gleeunit_progress). +-define(NOTEST, true). + +%% eunit_listener callbacks +-export([ + init/1, handle_begin/3, handle_end/3, handle_cancel/3, terminate/2, + start/0, start/1 +]). + +-define(reporting, gleeunit@internal@reporting). + +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +init(_Options) -> + ?reporting:new_state(). + +handle_begin(_test_or_group, _data, State) -> + State. + +handle_end(group, _data, State) -> + State; +handle_end(test, Data, State) -> + {AtomModule, AtomFunction, _Arity} = proplists:get_value(source, Data), + Module = erlang:atom_to_binary(AtomModule), + Function = erlang:atom_to_binary(AtomFunction), + + % EUnit swallows stdout, so print it to make debugging easier. + case proplists:get_value(output, Data) of + undefined -> ok; + <<>> -> ok; + Out -> gleam@io:print(Out) + end, + + case proplists:get_value(status, Data) of + ok -> + ?reporting:test_passed(State); + {skipped, _Reason} -> + ?reporting:test_skipped(State, Module, Function); + {error, {_, Exception, _Stack}} -> + ?reporting:test_failed(State, Module, Function, Exception) + end. + + +handle_cancel(_test_or_group, Data, State) -> + ?reporting:test_failed(State, <<"gleeunit">>, <<"main">>, Data). + +terminate({ok, _Data}, State) -> + ?reporting:finished(State), + ok; +terminate({error, Reason}, State) -> + ?reporting:finished(State), + io:fwrite(" +Eunit failed: + +~80p + +This is probably a bug in gleeunit. Please report it. +", [Reason]), + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/README.md b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/README.md new file mode 100644 index 0000000..0623ff3 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/README.md @@ -0,0 +1,31 @@ +# logging + +Configuration for the Erlang logger. + +[![Package Version](https://img.shields.io/hexpm/v/logging)](https://hex.pm/packages/logging) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/logging/) + +```sh +gleam add logging@1 +``` + +```gleam +import logging.{Info} + +pub fn main() { + // Run this once at the start of your program + logging.configure() + + // And get logging! + logging.log(Info, "Hello, Joe!") +} +``` + +## Disabling the colored output + +When using some logger services, colored output can be superfluous, because +they're not processed at all, and appears as real characters. You can set +the `NO_COLOUR` or `NO_COLOR` environment variable to any string that _is not_ +`"false"` or the empty string to disable the colored output from the logger. + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/gleam.toml b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/gleam.toml new file mode 100644 index 0000000..5b3b7e4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/gleam.toml @@ -0,0 +1,15 @@ +name = "logging" +version = "1.5.0" +description = "Configuration for the Erlang logger" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "logging" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = "~> 0.34 or ~> 1.0" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.app.src b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.app.src new file mode 100644 index 0000000..02e2b5f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.app.src @@ -0,0 +1,9 @@ +{application, logging, [ + {vsn, "1.5.0"}, + {applications, [gleam_stdlib]}, + {description, "Configuration for the Erlang logger"}, + {modules, [logging, + logging@@main, + logging_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.erl new file mode 100644 index 0000000..4de3967 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.erl @@ -0,0 +1,56 @@ +-module(logging). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/logging.gleam"). +-export([configure/0, log/2, set_level/1]). +-export_type([log_level/0, do_not_leak/0, key/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type log_level() :: emergency | + alert | + critical | + error | + warning | + notice | + info | + debug. + +-type do_not_leak() :: any(). + +-type key() :: level. + +-file("src/logging.gleam", 25). +?DOC( + " Configure the default Erlang logger handler with a pretty Gleam output\n" + " format, and sets the logging level to `Info`.\n" + "\n" + " ## Interaction with Elixir\n" + "\n" + " Elixir's built-in `logger` application removes Erlang's default logger\n" + " handler and replaces it with its own code, so if you have an Elixir package\n" + " in your project then this code will not be able to configure the logger as\n" + " it could normally.\n" +). +-spec configure() -> nil. +configure() -> + logging_ffi:configure(). + +-file("src/logging.gleam", 29). +?DOC(" Log a message to the Erlang logger at the given log level.\n"). +-spec log(log_level(), binary()) -> nil. +log(Level, Message) -> + logger:log(Level, Message), + nil. + +-file("src/logging.gleam", 42). +?DOC(" Change the log visibility level to be output.\n"). +-spec set_level(log_level()) -> nil. +set_level(Level) -> + logger:set_primary_config(level, Level), + nil. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.gleam b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.gleam new file mode 100644 index 0000000..b9e6a58 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging.gleam @@ -0,0 +1,49 @@ +pub type LogLevel { + Emergency + Alert + Critical + Error + Warning + Notice + Info + Debug +} + +type DoNotLeak + +/// Configure the default Erlang logger handler with a pretty Gleam output +/// format, and sets the logging level to `Info`. +/// +/// ## Interaction with Elixir +/// +/// Elixir's built-in `logger` application removes Erlang's default logger +/// handler and replaces it with its own code, so if you have an Elixir package +/// in your project then this code will not be able to configure the logger as +/// it could normally. +/// +@external(erlang, "logging_ffi", "configure") +pub fn configure() -> Nil + +/// Log a message to the Erlang logger at the given log level. +/// +pub fn log(level: LogLevel, message: String) -> Nil { + erlang_log(level, message) + Nil +} + +@external(erlang, "logger", "log") +fn erlang_log(level: LogLevel, message: String) -> DoNotLeak + +@external(erlang, "logger", "set_primary_config") +fn set_primary_config_level(key: Key, level: LogLevel) -> DoNotLeak + +/// Change the log visibility level to be output. +/// +pub fn set_level(level: LogLevel) -> Nil { + set_primary_config_level(Level, level) + Nil +} + +type Key { + Level +} diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging_ffi.erl b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging_ffi.erl new file mode 100644 index 0000000..eff2251 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/logging/src/logging_ffi.erl @@ -0,0 +1,87 @@ +-module(logging_ffi). +-export([configure/0, format/2]). + +configure() -> + logger:update_primary_config(#{ + level => info, + filter_default => log, + filters => [ + {domain,{fun logger_filters:domain/2, {stop, sub, [sasl]}}}, + {domain,{fun logger_filters:domain/2, {stop, sub, [supervisor_report]}}}, + {progress, {fun logger_filters:progress/2, stop}} + ], + metadata => #{} + }), + logger:update_handler_config(default, #{ + formatter => {logging_ffi, #{colored => is_colored_output()}} + }), + nil. + +is_colored_output() -> + not ( + envvar_enabled("NO_COLOUR", false) + orelse envvar_enabled("NO_COLOR", false) + ). + +envvar_enabled(Name, Default) -> + case os:getenv(Name) of + % Not set, use default + false -> Default; + % Set to a false value + "" -> false; + "false" -> false; + % Otherwise it is enabled + _ -> true + end. + +format(#{level := Level, msg := Msg, meta := _Meta}, Config) -> + [format_level(Level, Config), format_msg(Msg), $\n]. + +format_level(Level, #{colored := Colored}) -> + case Level of + emergency when Colored -> "\x1b[1;41mEMRG\x1b[0m"; + alert when Colored -> "\x1b[1;41mALRT\x1b[0m"; + critical when Colored -> "\x1b[1;41mCRIT\x1b[0m"; + error when Colored -> "\x1b[1;31mEROR\x1b[0m"; + warning when Colored -> "\x1b[1;33mWARN\x1b[0m"; + notice when Colored -> "\x1b[1;32mNTCE\x1b[0m"; + info when Colored -> "\x1b[1;34mINFO\x1b[0m"; + debug when Colored -> "\x1b[1;36mDEBG\x1b[0m"; + emergency -> "EMRG"; + alert -> "ALRT"; + critical -> "CRIT"; + error -> "EROR"; + warning -> "WARN"; + notice -> "NTCE"; + info -> "INFO"; + debug -> "DEBG" + end. + +format_msg(Report0) -> + case Report0 of + {string, Msg} -> + [$\s, Msg]; + + {report, Report1} when is_map(Report1) -> + format_kv(maps:to_list(Report1)); + + {report, Report1} when is_list(Report1) -> + format_kv(Report1); + + _ -> + [$\s, gleam@string:inspect(Report0)] + end. + +format_kv(Pairs) -> + case Pairs of + [] -> []; + [{K, V} | Rest] when is_atom(K) -> [ + $\s, erlang:atom_to_binary(K), $=, gleam@string:inspect(V) + | format_kv(Rest) + ]; + [{K, V} | Rest] -> [ + $\s, gleam@string:inspect(K), $=, gleam@string:inspect(V) + | format_kv(Rest) + ]; + Other -> gleam@string:inspect(Other) + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/build/packages/packages.toml b/examples/http_interactions/build/packages/gleam_otp/build/packages/packages.toml new file mode 100644 index 0000000..cd35ea1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/build/packages/packages.toml @@ -0,0 +1,5 @@ +[packages] +gleeunit = "1.10.0" +gleam_erlang = "1.3.0" +gleam_stdlib = "1.0.0" +logging = "1.5.0" diff --git a/examples/http_interactions/build/packages/gleam_otp/gleam.toml b/examples/http_interactions/build/packages/gleam_otp/gleam.toml new file mode 100644 index 0000000..1b59a8e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/gleam.toml @@ -0,0 +1,24 @@ +name = "gleam_otp" +version = "1.2.0" +licences = ["Apache-2.0"] +description = "Fault tolerant multi-core programs with OTP, the BEAM actor framework." + +gleam = ">= 1.11.0" + +internal_modules = [ + "gleam/otp/internal/*" +] + +repository = { type = "github", user = "gleam-lang", repo = "otp" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.60.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +logging = ">= 1.3.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Builder.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Builder.hrl new file mode 100644 index 0000000..fcd4411 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Builder.hrl @@ -0,0 +1,8 @@ +-record(builder, { + initialise :: fun((gleam@erlang@process:subject(any())) -> {ok, + gleam@otp@actor:initialised(any(), any(), any())} | + {error, binary()}), + initialisation_timeout :: integer(), + on_message :: fun((any(), any()) -> gleam@otp@actor:next(any(), any())), + name :: gleam@option:option(gleam@erlang@process:name(any())) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl new file mode 100644 index 0000000..85677d1 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl @@ -0,0 +1,4 @@ +-record(continue, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Initialised.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Initialised.hrl new file mode 100644 index 0000000..a2b79d4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Initialised.hrl @@ -0,0 +1,5 @@ +-record(initialised, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())), + return :: any() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Started.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Started.hrl new file mode 100644 index 0000000..2ed001b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@actor_Started.hrl @@ -0,0 +1 @@ +-record(started, {pid :: gleam@erlang@process:pid_(), data :: any()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Builder.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Builder.hrl new file mode 100644 index 0000000..02f5f44 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Builder.hrl @@ -0,0 +1,9 @@ +-record(builder, { + child_type :: gleam@otp@supervision:child_type(), + template :: fun((any()) -> {ok, gleam@otp@actor:started(any())} | + {error, gleam@otp@actor:start_error()}), + restart_strategy :: gleam@otp@supervision:restart(), + intensity :: integer(), + period :: integer(), + name :: gleam@option:option(gleam@erlang@process:name(gleam@otp@factory_supervisor:message(any(), any()))) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_NamedSupervisor.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_NamedSupervisor.hrl new file mode 100644 index 0000000..5faf2fa --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_NamedSupervisor.hrl @@ -0,0 +1,3 @@ +-record(named_supervisor, { + name :: gleam@erlang@process:name(gleam@otp@factory_supervisor:message(any(), any())) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Supervisor.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Supervisor.hrl new file mode 100644 index 0000000..e015b9a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@factory_supervisor_Supervisor.hrl @@ -0,0 +1 @@ +-record(supervisor, {pid :: gleam@erlang@process:pid_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Builder.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Builder.hrl new file mode 100644 index 0000000..5164974 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Builder.hrl @@ -0,0 +1,7 @@ +-record(builder, { + strategy :: gleam@otp@static_supervisor:strategy(), + intensity :: integer(), + period :: integer(), + auto_shutdown :: gleam@otp@static_supervisor:auto_shutdown(), + children :: list(gleam@otp@supervision:child_specification(nil)) +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Supervisor.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Supervisor.hrl new file mode 100644 index 0000000..e015b9a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@static_supervisor_Supervisor.hrl @@ -0,0 +1 @@ +-record(supervisor, {pid :: gleam@erlang@process:pid_()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_ChildSpecification.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_ChildSpecification.hrl new file mode 100644 index 0000000..d955c8d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_ChildSpecification.hrl @@ -0,0 +1,7 @@ +-record(child_specification, { + start :: fun(() -> {ok, gleam@otp@actor:started(any())} | + {error, gleam@otp@actor:start_error()}), + restart :: gleam@otp@supervision:restart(), + significant :: boolean(), + child_type :: gleam@otp@supervision:child_type() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_Worker.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_Worker.hrl new file mode 100644 index 0000000..4b32c7a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@supervision_Worker.hrl @@ -0,0 +1 @@ +-record(worker, {shutdown_ms :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl new file mode 100644 index 0000000..99ab4cb --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl @@ -0,0 +1,7 @@ +-record(status_info, { + module :: gleam@erlang@atom:atom_(), + parent :: gleam@erlang@process:pid_(), + mode :: gleam@otp@system:mode(), + debug_state :: gleam@otp@system:debug_state(), + state :: gleam@dynamic:dynamic_() +}). diff --git a/examples/http_interactions/build/packages/gleam_otp/manifest.toml b/examples/http_interactions/build/packages/gleam_otp/manifest.toml new file mode 100644 index 0000000..c28f494 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/manifest.toml @@ -0,0 +1,15 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_stdlib", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "960090C2FB391784BB34267B099DC9315CC1B1F6013E7415BC763CEF1905D7D3" }, + { name = "gleeunit", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "254B697FE72EEAD7BF82E941723918E421317813AC49923EE76A18C788C61E72" }, + { name = "logging", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "BC5F18CE5DD9686100229FE5409BDC3DD5C46D5A7DF2F804AD2D8F0DD6C5060E" }, +] + +[requirements] +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +logging = { version = ">= 1.3.0 and < 2.0.0" } diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/actor.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/actor.gleam new file mode 100644 index 0000000..a2dce58 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/actor.gleam @@ -0,0 +1,660 @@ +//// This module provides the _Actor_ abstraction, one of the most common +//// building blocks of Gleam OTP programs. +//// +//// An Actor is a process like any other BEAM process and can be used to hold +//// state, execute code, and communicate with other processes by sending and +//// receiving messages. The advantage of using the actor abstraction over a bare +//// process is that it provides a single interface for commonly needed +//// functionality, including support for the [tracing and debugging +//// features in OTP][erlang-sys]. +//// +//// Gleam's Actor is similar to Erlang's `gen_server` and Elixir's `GenServer` +//// but differs in that it offers a fully typed interface. This different API is +//// why Gleam uses the name "Actor" rather than some variation of +//// "generic-server". +//// +//// [erlang-sys]: https://www.erlang.org/doc/man/sys.html +//// +//// ## Example +//// +//// An Actor can be used to create a client-server interaction between an Actor +//// (the server) and other processes (the clients). In this example we have an +//// Actor that works as a stack, allowing clients to push and pop elements. +//// +//// ```gleam +//// pub fn main() { +//// // Start the actor with initial state of an empty list, and the +//// // `handle_message` callback function (defined below). +//// // We assert that it starts successfully. +//// // +//// // In real-world Gleam OTP programs we would likely write a wrapper functions +//// // called `start`, `push` `pop`, `shutdown` to start and interact with the +//// // Actor. We are not doing that here for the sake of showing how the Actor +//// // API works. +//// let assert Ok(actor) = +//// actor.new([]) |> actor.on_message(handle_message) |> actor.start +//// let subject = actor.data +//// +//// // We can send a message to the actor to push elements onto the stack. +//// process.send(subject, Push("Joe")) +//// process.send(subject, Push("Mike")) +//// process.send(subject, Push("Robert")) +//// +//// // The `Push` message expects no response, these messages are sent purely for +//// // the side effect of mutating the state held by the actor. +//// // +//// // We can also send the `Pop` message to take a value off of the actor's +//// // stack. This message expects a response, so we use `process.call` to send a +//// // message and wait until a reply is received. +//// // +//// // In this instance we are giving the actor 10 milliseconds to reply, if the +//// // `call` function doesn't get a reply within this time it will panic and +//// // crash the client process. +//// let assert Ok("Robert") = process.call(subject, 10, Pop) +//// let assert Ok("Mike") = process.call(subject, 10, Pop) +//// let assert Ok("Joe") = process.call(subject, 10, Pop) +//// +//// // The stack is now empty, so if we pop again the actor replies with an error. +//// let assert Error(Nil) = process.call(subject, 10, Pop) +//// +//// // Lastly, we can send a message to the actor asking it to shut down. +//// process.send(subject, Shutdown) +//// } +//// ``` +//// +//// Here is the code that is used to implement this actor: +//// +//// ```gleam +//// // First step of implementing the stack Actor is to define the message type that +//// // it can receive. +//// // +//// // The type of the elements in the stack is not fixed so a type parameter +//// // is used for it instead of a concrete type such as `String` or `Int`. +//// pub type Message(element) { +//// // The `Shutdown` message is used to tell the actor to stop. +//// // It is the simplest message type, it contains no data. +//// // +//// // Most the time we don't define an API to shut down an actor, but in this +//// // example we do to show how it can be done. +//// Shutdown +//// +//// // The `Push` message is used to add a new element to the stack. +//// // It contains the item to add, the type of which is the `element` +//// // parameterised type. +//// Push(push: element) +//// +//// // The `Pop` message is used to remove an element from the stack. +//// // It contains a `Subject`, which is used to send the response back to the +//// // message sender. In this case the reply is of type `Result(element, Nil)`. +//// Pop(reply_with: Subject(Result(element, Nil))) +//// } +//// +//// // The last part is to implement the `handle_message` callback function. +//// // +//// // This function is called by the Actor for each message it receives. +//// // Actors are single threaded only doing one thing at a time, so they handle +//// // messages sequentially one at a time, in the order they are received. +//// // +//// // The function takes the current state and a message, and returns a data +//// // structure that indicates what to do next, along with the new state. +//// fn handle_message( +//// stack: List(e), +//// message: Message(e), +//// ) -> actor.Next(List(e), Message(e)) { +//// case message { +//// // For the `Shutdown` message we return the `actor.stop` value, which causes +//// // the actor to discard any remaining messages and stop. +//// // We may chose to do some clean-up work here, but this actor doesn't need +//// // to do this. +//// Shutdown -> actor.stop() +//// +//// // For the `Push` message we add the new element to the stack and return +//// // `actor.continue` with this new stack, causing the actor to process any +//// // queued messages or wait for more. +//// Push(value) -> { +//// let new_state = [value, ..stack] +//// actor.continue(new_state) +//// } +//// +//// // For the `Pop` message we attempt to remove an element from the stack, +//// // sending it or an error back to the caller, before continuing. +//// Pop(client) -> { +//// case stack { +//// [] -> { +//// // When the stack is empty we can't pop an element, so we send an +//// // error back. +//// process.send(client, Error(Nil)) +//// actor.continue([]) +//// } +//// +//// [first, ..rest] -> { +//// // Otherwise we send the first element back and use the remaining +//// // elements as the new state. +//// process.send(client, Ok(first)) +//// actor.continue(rest) +//// } +//// } +//// } +//// } +//// } +//// ``` + +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom +import gleam/erlang/charlist.{type Charlist} +import gleam/erlang/process.{ + type ExitReason, type Pid, type Selector, type Subject, Abnormal, Killed, +} +import gleam/option.{type Option, None, Some} +import gleam/otp/system.{ + type DebugState, type Mode, type StatusInfo, type SystemMessage, GetState, + GetStatus, Resume, Running, StatusInfo, Suspend, Suspended, +} +import gleam/result +import gleam/string + +type Message(message) { + /// A regular message excepted by the process + Message(message) + + /// An OTP system message, for debugging or maintenance + System(SystemMessage) + + /// An unexpected message + Unexpected(Dynamic) +} + +/// The type used to indicate what to do after handling a message. +/// +pub opaque type Next(state, message) { + /// Continue handling messages. + /// + /// An optional selector can be provided to changes the messages that the + /// actor is handling. This replaces any selector that was previously given + /// in the actor's `init` callback, or in any previous `Next` value. + /// + Continue(state: state, selector: Option(Selector(message))) + + /// Stop handling messages and shut down. + /// + Stop(ExitReason) +} + +/// Indicate the actor should continue, processing any waiting or future messages. +/// +pub fn continue(state: state) -> Next(state, message) { + Continue(state:, selector: None) +} + +/// Indicate the actor should stop and shut-down, handling no futher messages. +/// +/// The reason for exiting is `Normal`. +/// +pub fn stop() -> Next(state, message) { + Stop(process.Normal) +} + +/// Indicate the actor is in a bad state and should shut down. It will not +/// handle any new messages, and any linked processes will also exit abnormally. +/// +/// The provided reason will be given and propagated. +/// +pub fn stop_abnormal(reason: String) -> Next(state, message) { + Stop(process.Abnormal(dynamic.string(reason))) +} + +/// Provide a selector to change the messages that the actor is handling +/// going forward. This replaces any selector that was previously given +/// in the actor's `init` callback, or in any previous `Next` value. +/// +pub fn with_selector( + value: Next(state, message), + selector: Selector(message), +) -> Next(state, message) { + case value { + Continue(state, _) -> Continue(state, Some(selector)) + Stop(_) -> value + } +} + +type Self(state, msg) { + Self( + /// The mode the actor is currently in, either active or suspended. + mode: Mode, + /// The pid of the process that started this actor. + parent: Pid, + /// The state of this actor, provided by the programmer. + state: state, + /// The selector that actor is currently using to reveive messages. This + /// can be changed by the `Next` value returned by the actor's `loop` callback. + selector: Selector(Message(msg)), + /// An opaque value used by the OTP system debug APIs. + debug_state: DebugState, + /// The message handling code provided by the programmer. + message_handler: fn(state, msg) -> Next(state, msg), + ) +} + +/// A value returned to the parent when their child actor successfully starts. +pub type Started(data) { + Started( + /// The process identifier of the started actor. This can be used to + /// monitor the actor, make it exit, or anything else you can do with a + /// pid. + pid: Pid, + /// Data returned by the actor after it initialised. Commonly this will be + /// a subject that it will receive messages from. + data: data, + ) +} + +/// A convenience for the type returned when an actor process is started. +pub type StartResult(data) = + Result(Started(data), StartError) + +/// A type returned from an actor's initialiser, containing the actor state, a +/// selector to receive messages using, and data to return to the parent. +/// +/// Use the `initialised`, `selecting`, and `returning` functions to construct +/// this type. +/// +pub opaque type Initialised(state, message, data) { + Initialised(state: state, selector: Option(Selector(message)), return: data) +} + +/// Takes the post-initialisation state of the actor. This state will be passed +/// to the `on_message` callback each time a message is received. +/// +pub fn initialised(state: state) -> Initialised(state, message, Nil) { + Initialised(state, None, Nil) +} + +/// Add a selector for the actor to receive messages with. +/// +/// If a message is received by the actor but not selected for with the +/// selector then the actor will discard it and log a warning. +/// +pub fn selecting( + initialised: Initialised(state, old_message, return), + selector: Selector(message), +) -> Initialised(state, message, return) { + Initialised(..initialised, selector: Some(selector)) +} + +/// Add the data to return to the parent process. This might be a subject that +/// the actor will receive messages over. +/// +pub fn returning( + initialised: Initialised(state, message, old_return), + return: return, +) -> Initialised(state, message, return) { + Initialised(..initialised, return:) +} + +pub opaque type Builder(state, message, return) { + Builder( + /// The initialisation functionality for the actor. This function is called + /// just after the actor starts but before the channel sender is returned + /// to the parent. + /// + /// This function is used to ensure that any required data or state is + /// correct. If this function returns an error it means that the actor has + /// failed to start and an error is returned to the parent. + /// + initialise: fn(Subject(message)) -> + Result(Initialised(state, message, return), String), + /// How many milliseconds the `init` function has to return before it is + /// considered to have taken too long and failed. + /// + initialisation_timeout: Int, + /// This function is called to handle each message that the actor receives. + /// + on_message: fn(state, message) -> Next(state, message), + /// The actor can be named for you at start. + /// + name: Option(process.Name(message)), + ) +} + +/// Create a builder for an actor without a custom initialiser. The actor +/// returns a subject to the parent that can be used to send messages to the +/// actor. +/// +/// If the actor has been given a name with the `named` function then the +/// subject is a named subject. +/// +/// If you wish to create an actor with some other initialisation logic that +/// runs before it starts handling messages, see `new_with_initialiser`. +/// +pub fn new(state: state) -> Builder(state, message, Subject(message)) { + let initialise = fn(subject) { + initialised(state) |> returning(subject) |> Ok + } + Builder( + initialise:, + initialisation_timeout: 1000, + on_message: fn(state, _) { continue(state) }, + name: option.None, + ) +} + +/// Create a builder for an actor with a custom initialiser that runs before +/// the start function returns to the parent, and before the actor starts +/// handling messages. +/// +/// The first argument is a number of milliseconds that the initialiser +/// function is expected to return within. If it takes longer the initialiser +/// is considered to have failed and the actor will be killed, and an error +/// will be returned to the parent. +/// +/// The actor's default subject is passed to the initialiser function. You can +/// chose to return it to the parent with `returning`, use it in some other +/// way, or ignore it completely. +/// +/// If a custom selector is given using the `selecting` function then this +/// overwrites the default selector, which selects for the default subject, so +/// you will need to add the subject to the custom selector yourself. +/// +pub fn new_with_initialiser( + timeout: Int, + initialise: fn(Subject(message)) -> + Result(Initialised(state, message, return), String), +) -> Builder(state, message, return) { + Builder( + initialise:, + initialisation_timeout: timeout, + on_message: fn(state, _) { continue(state) }, + name: option.None, + ) +} + +/// Set the message handler for the actor. This callback function will be +/// called each time the actor receives a message. +/// +/// Actors handle messages sequentially, later messages being handled after the +/// previous one has been handled. +pub fn on_message( + builder: Builder(state, message, return), + handler: fn(state, message) -> Next(state, message), +) -> Builder(state, message, return) { + Builder(..builder, on_message: handler) +} + +/// Provide a name for the actor to be registered with when started, enabling +/// it to receive messages via a named subject. This is useful for making +/// processes that can take over from an older one that has exited due to a +/// failure, or to avoid passing subjects from receiver processes to sender +/// processes. +/// +/// If the name is already registered to another process then the actor will +/// fail to start. +/// +/// When this function is used the actor's default subject will be a named +/// subject using this name. +/// +pub fn named( + builder: Builder(state, message, return), + name: process.Name(message), +) -> Builder(state, message, return) { + Builder(..builder, name: option.Some(name)) +} + +fn exit_process(reason: ExitReason) -> ExitReason { + case reason { + Abnormal(reason) -> process.send_abnormal_exit(process.self(), reason) + Killed -> process.kill(process.self()) + _ -> Nil + } + + reason +} + +fn receive_message(self: Self(state, msg)) -> Message(msg) { + let selector = case self.mode { + // When suspended we only respond to system messages + Suspended -> + process.new_selector() + |> select_system_messages + + // When running we respond to all messages + Running -> + // The actor needs to handle various different messages: + // + // - OTP system messages. These are handled by the actor for the + // programmer, they don't need to do anything. + // - Messages sent to the subject the actor creates during initialisation + // and returns to the parent. + // - Any arbitrary messages the programmer expects the actor to receive. + // For example, messages sent by a pubsub system where it does not + // support using the actor's subject. + // - Any unexpected messages. + // + // We add the handler for unexpected messages first so that the user + // supplied selector can override it if desired. + process.new_selector() + |> process.select_other(Unexpected) + |> process.merge_selector(self.selector) + |> select_system_messages + } + + process.selector_receive_forever(selector) +} + +fn select_system_messages( + selector: Selector(Message(msg)), +) -> Selector(Message(msg)) { + selector + |> process.select_record(atom.create("system"), 2, convert_system_message) +} + +@external(erlang, "gleam_otp_external", "convert_system_message") +fn convert_system_message(b: Dynamic) -> Message(msg) + +fn process_status_info(self: Self(state, msg)) -> StatusInfo { + StatusInfo( + module: atom.create("gleam@otp@actor"), + parent: self.parent, + mode: self.mode, + debug_state: self.debug_state, + state: erase(self.state), + ) +} + +fn loop(self: Self(state, msg)) -> ExitReason { + case receive_message(self) { + // An OTP system message. This is handled by the actor for the programmer, + // behind the scenes. + System(system) -> + case system { + GetState(callback) -> { + callback(erase(self.state)) + loop(self) + } + Resume(callback) -> { + callback() + loop(Self(..self, mode: Running)) + } + Suspend(callback) -> { + callback() + loop(Self(..self, mode: Suspended)) + } + GetStatus(callback) -> { + callback(process_status_info(self)) + loop(self) + } + } + + // An unexpected message. It this is reached then the programmer has not + // handled this, so log a warning. + Unexpected(message) -> { + log_warning( + charlist.from_string("Actor discarding unexpected message: ~s"), + [charlist.from_string(string.inspect(message))], + ) + loop(self) + } + + // A regular message that the programmer is expecting, either over the + // subject or some other messsage that the programmer's selector expects. + Message(msg) -> + case self.message_handler(self.state, msg) { + Stop(reason) -> exit_process(reason) + + Continue(state: state, selector: new_selector) -> { + let selector = case new_selector { + None -> self.selector + Some(s) -> process.map_selector(s, Message) + } + loop(Self(..self, state: state, selector: selector)) + } + } + } +} + +// TODO: replace this when we have Gleam bindings to the logger +@external(erlang, "logger", "warning") +fn log_warning(a: Charlist, b: List(Charlist)) -> Nil + +@external(erlang, "gleam_otp_external", "identity") +fn erase(a: anything) -> Dynamic + +// Run automatically when the actor is first started. +fn initialise_actor( + builder: Builder(state, msg, return), + parent: Pid, + ack: Subject(Result(return, String)), +) -> ExitReason { + // Run the actor initialiser. + let result = { + use subject <- result.try(case builder.name { + None -> Ok(process.new_subject()) + Some(name) -> { + use _ <- result.try(try_register_self(name)) + Ok(process.named_subject(name)) + } + }) + use result <- result.try(builder.initialise(subject)) + Ok(#(subject, result)) + } + + case result { + // Init was OK, send the subject to the parent and start handling messages. + Ok(#(subject, Initialised(state:, selector:, return:))) -> { + // Add the default subject to the selector provided by the initialiser. + // The initialiser may have added additional handlers to the selector. + let selector = case selector { + Some(selector) -> selector + None -> process.new_selector() |> process.select(subject) + } + let selector = process.map_selector(selector, Message) + // Signal to parent that the process has initialised successfully + process.send(ack, Ok(return)) + // Start message receive loop + let self = + Self( + state: state, + parent:, + selector: selector, + message_handler: builder.on_message, + debug_state: system.debug_state([]), + mode: Running, + ) + loop(self) + } + + // The init failed. Send the reason back to the parent, but exit normally. + Error(reason) -> { + process.send(ack, Error(reason)) + exit_process(process.Normal) + } + } +} + +fn try_register_self(name: process.Name(msg)) -> Result(Nil, String) { + case process.register(process.self(), name) { + Ok(Nil) -> Ok(Nil) + Error(_) -> Error("name already registered") + } +} + +pub type StartError { + InitTimeout + InitFailed(String) + InitExited(ExitReason) +} + +type StartInitMessage(data) { + Ack(Result(data, String)) + Mon(process.Down) +} + +/// Starts an actor from a given `Builder`. On failure, `start` returns a `StartError` +pub fn start( + builder: Builder(state, msg, return), +) -> Result(Started(return), StartError) { + let timeout = builder.initialisation_timeout + let ack_subject = process.new_subject() + let self = process.self() + + let child = + process.spawn(fn() { initialise_actor(builder, self, ack_subject) }) + + let monitor = process.monitor(child) + let selector = + process.new_selector() + |> process.select_map(ack_subject, Ack) + |> process.select_specific_monitor(monitor, Mon) + + let result = case process.selector_receive(selector, timeout) { + // Child started OK + Ok(Ack(Ok(subject))) -> Ok(subject) + + // Child initialiser returned an error + Ok(Ack(Error(reason))) -> Error(InitFailed(reason)) + + // Child went down while initialising + Ok(Mon(down)) -> Error(InitExited(down.reason)) + + // Child did not finish initialising in time + Error(Nil) -> { + // Unlink the child before killing it, so that we only return the error, + // but don't also send an exit message to the linked parent process. + process.unlink(child) + process.kill(child) + Error(InitTimeout) + } + } + + // Remove the monitor used for the starting of the actor as to avoid an extra + // message arriving at the parent if the child dies later. + process.demonitor_process(monitor) + + case result { + Ok(data) -> Ok(Started(pid: child, data:)) + Error(error) -> Error(error) + } +} + +/// Send a message over a given channel. +/// +/// This is a re-export of `process.send`, for the sake of convenience. +/// +pub fn send(subject: Subject(msg), msg: msg) -> Nil { + process.send(subject, msg) +} + +/// Send a synchronous message and wait for a response from the receiving +/// process. +/// +/// If a reply is not received within the given timeout then the sender process +/// crashes rather than leaving the processes in an invalid state. +/// +/// This is a re-export of `process.call`, for the sake of convenience. +/// +pub fn call( + subject: Subject(message), + waiting timeout: Int, + sending make_message: fn(Subject(reply)) -> message, +) -> reply { + process.call(subject, timeout, make_message) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/factory_supervisor.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/factory_supervisor.gleam new file mode 100644 index 0000000..eecd895 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/factory_supervisor.gleam @@ -0,0 +1,439 @@ +//// A supervisor where child processes are started dynamically from a +//// pre-specified template, so new processes can be created as needed +//// while the program is running. +//// +//// When the supervisor is shut down it shuts down all its children +//// concurrently and in no specified order. +//// +//// For further detail see the Erlang documentation, particularly the parts +//// about the `simple_one_for_one` restart strategy, which is the Erlang +//// equivilent of the factory supervisor. +//// . +//// +//// ## Usage +//// +//// Add the factory supervisor to your supervision tree using the `supervised` +//// function and a name created at the start of the program. The `new` +//// function takes a "template function", which is a function that takes one +//// argument and starts a linked child process. +//// +//// You most likely want to give the factory supervisor a name, and to pass +//// that name to any other processes that will want to cause new child +//// processes to be started under the factory supervisor. In this example a +//// web server is used. +//// +//// ```gleam +//// import gleam/erlang/process.{type Name} +//// import gleam/otp/actor.{type StartResult} +//// import gleam/otp/factory_supervisor as factory +//// import gleam/otp/static_supervisor as supervisor +//// import my_app +//// +//// /// This function starts the application's supervision tree. +//// /// +//// /// It takes a record as an argument that +//// /// +//// pub fn start_supervision_tree(reporters_name: Name(_)) -> StartResult(_) { +//// // Define a named factory supervisor that can create new child processes +//// // using the `my_app.start_reporter_actor` function, which is defined +//// // elsewhere in the program. +//// let reporter_factory_supervisor = +//// factory.worker_child(my_app.start_reporter_actor) +//// |> factory.named(reporters_name) +//// |> factory.supervised +//// +//// // This web server process takes the name, so it can contact the factory +//// // supervisor to command it to start new processes as needed. +//// let web_server = my_app.supervised_web_server(reporters_name) +//// +//// // Create the top-level static supervisor with the supervisor and web +//// // server as its children +//// supervisor.new(supervisor.RestForOne) +//// |> supervisor.add(reporter_factory_supervisor) +//// |> supervisor.add(web_server) +//// |> supervisor.start +//// } +//// ``` +//// +//// Any process with the name of the factory supervisor can use the +//// `get_by_name` function to get a reference to the supervisor, and then use +//// the `start_child` function to have it start new child processes. +//// +//// Remember! Each process name created with `process.new_name` is unique. +//// Two names created by calling the function twice are different names, even +//// if the same string is given as an argument. You must create the name value +//// at the start of your program and then pass it down into application code +//// and library code that uses names. +//// +//// ```gleam +//// import gleam/http/request.{type Request} +//// import gleam/http/response.{type Response} +//// import gleam/otp/factory_supervisor +//// import my_app +//// +//// /// In our example this function is called each time a HTTP request is +//// /// received by the web server. +//// pub fn handle_request(req: Request(_), reporters: Name(_)) -> Response(_) { +//// // Get a reference to the supervisor using the name +//// let supervisor = factory_supervisor.get_by_name(reporters) +//// +//// // Start a new child process under the supervisor, passing the request path +//// // to use as the argument for the child-starting template function. +//// let start_result = factory_supervisor.start_child(supervisor, request.path) +//// +//// // A response is sent to the HTTP client. +//// // The child starting template function returns a result, with the error case +//// // being used when children fail to start. Because of this the `start_child` +//// // function also returns a result, so it must be handled too. +//// case start_result { +//// Ok(_) -> response.new(200) +//// Error(_) -> response.new(500) +//// } +//// } +//// ``` + +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} +import gleam/option +import gleam/otp/actor +import gleam/otp/internal/result2.{type Result2} +import gleam/otp/supervision.{type ChildSpecification} + +const default_intensity = 2 + +const default_period = 5 + +const default_restart_strategy = supervision.Transient + +/// A reference to the running supervisor +/// +/// This supervisor wrap Erlang/OTP's `supervisor` module, and as such it does +/// not use subjects for message sending. If it was implemented in Gleam a +/// subject might be used instead of this type. +/// +pub opaque type Supervisor(child_argument, child_data) { + Supervisor(pid: Pid) + NamedSupervisor(name: process.Name(Message(child_argument, child_data))) +} + +/// The message type of a factory supervisor. This message type is not used +/// directly, but if you are using a name with a factory supervisor then this +/// will be the message type of the name. +pub type Message(child_argument, child_data) + +/// Get a reference to a supervisor using its registered name. +/// +/// If no supervisor has been started using this name then functions +/// using this reference will fail. +/// +/// # Panics +/// +/// Functions using the `Supervisor` reference returned by this function +/// will panic if there is no factory supervisor registered with the name +/// when they are called. Always make sure your supervisors are themselves +/// supervised. +/// +pub fn get_by_name( + name: process.Name(Message(child_argument, child_data)), +) -> Supervisor(child_argument, child_data) { + NamedSupervisor(name) +} + +/// A builder for configuring and starting a supervisor. See each of the +/// functions that take this type for details of the configuration possible. +/// +pub opaque type Builder(child_argument, child_data) { + Builder( + child_type: supervision.ChildType, + template: fn(child_argument) -> actor.StartResult(child_data), + restart_strategy: supervision.Restart, + intensity: Int, + period: Int, + name: option.Option(process.Name(Message(child_argument, child_data))), + ) +} + +/// Configure a supervisor with a child-starting template function. +/// +/// You should use this unless the child processes are also supervisors. +/// +/// The default shutdown timeout is 5000ms. This can be changed with the +/// `timeout` function. +/// +pub fn worker_child( + template: fn(child_argument) -> actor.StartResult(child_data), +) -> Builder(child_argument, child_data) { + Builder( + template:, + child_type: supervision.Worker(5000), + restart_strategy: default_restart_strategy, + intensity: default_intensity, + period: default_period, + name: option.None, + ) +} + +/// Configure a supervisor with a template that will start children that are +/// also supervisors. +/// +/// You should only use this if the child processes are also supervisors. +/// +/// Supervisor children have an unlimited amount of time to shutdown, there is +/// no timeout. +/// +pub fn supervisor_child( + template: fn(child_argument) -> actor.StartResult(child_data), +) -> Builder(child_argument, child_data) { + Builder( + template:, + child_type: supervision.Supervisor, + restart_strategy: default_restart_strategy, + intensity: default_intensity, + period: default_period, + name: option.None, + ) +} + +/// Provide a name for the supervisor to be registered with when started, +/// enabling it be more easily contacted by other processes. This is useful for +/// enabling processes that can take over from an older one that has exited due +/// to a failure. +/// +/// If the name is already registered to another process then the factory +/// supervisor will fail to start. +/// +pub fn named( + builder: Builder(child_argument, child_data), + name: process.Name(Message(child_argument, child_data)), +) -> Builder(child_argument, child_data) { + Builder(..builder, name: option.Some(name)) +} + +/// To prevent a supervisor from getting into an infinite loop of child +/// process terminations and restarts, a maximum restart tolerance is +/// defined using two integer values specified with keys intensity and +/// period in the above map. Assuming the values MaxR for intensity and MaxT +/// for period, then, if more than MaxR restarts occur within MaxT seconds, +/// the supervisor terminates all child processes and then itself. The +/// termination reason for the supervisor itself in that case will be +/// shutdown. +/// +/// Intensity defaults to 2 and period defaults to 5. +/// +pub fn restart_tolerance( + builder: Builder(child_argument, child_data), + intensity intensity: Int, + period period: Int, +) -> Builder(child_argument, child_data) { + Builder(..builder, intensity: intensity, period: period) +} + +/// Configure the amount of milliseconds a child has to shut down before +/// being brutal killed by the supervisor. +/// +/// If not set the default for a child is 5000ms. +/// +/// This will be ignored if the child is a supervisor itself. +/// +pub fn timeout( + builder: Builder(argument, data), + ms ms: Int, +) -> Builder(argument, data) { + case builder.child_type { + supervision.Worker(_) -> + Builder(..builder, child_type: supervision.Worker(ms)) + _ -> builder + } +} + +/// Configure the strategy for restarting children when they exit. See the +/// documentation for the `supervision.Restart` for details. +/// +/// If not set the default strategy is `supervision.Transient`, so children +/// will be restarted if they terminate abnormally. +/// +pub fn restart_strategy( + builder: Builder(argument, data), + restart_strategy: supervision.Restart, +) -> Builder(argument, data) { + case builder.child_type { + supervision.Worker(_) -> Builder(..builder, restart_strategy:) + _ -> builder + } +} + +/// Start a new supervisor process with the configuration and child template +/// specified within the builder. +/// +/// Typically you would use the `supervised` function to add your supervisor to +/// a supervision tree instead of using this function directly. +/// +/// The supervisor will be linked to the parent process that calls this +/// function. +/// +pub fn start( + builder: Builder(child_argument, child_data), +) -> actor.StartResult(Supervisor(child_argument, child_data)) { + let flags = + make_erlang_start_flags([ + Strategy(SimpleOneForOne), + Intensity(builder.intensity), + Period(builder.period), + ]) + + let module_atom = atom.create("gleam@otp@factory_supervisor") + let function_atom = atom.create("start_child_callback") + let mfa = #(module_atom, function_atom, [builder.template]) + + let #(type_, shutdown) = case builder.child_type { + supervision.Supervisor -> #(atom.create("supervisor"), make_timeout(-1)) + supervision.Worker(ms) -> #(atom.create("worker"), make_timeout(ms)) + } + + let child = + make_erlang_child_spec([ + Id(0), + Start(mfa), + Restart(builder.restart_strategy), + Type(type_), + Shutdown(shutdown), + ]) + + let configuration = #(flags, [child]) + let start_result = case builder.name { + option.None -> unnamed_start(module_atom, configuration) + option.Some(name) -> named_start(Local(name), module_atom, configuration) + } + + case start_result { + Ok(pid) -> Ok(actor.Started(pid:, data: Supervisor(pid))) + Error(error) -> Error(convert_erlang_start_error(error)) + } +} + +@external(erlang, "maps", "from_list") +fn make_erlang_start_flags( + flags: List(ErlangStartFlag(data)), +) -> ErlangStartFlags + +type ErlangStartFlags + +@external(erlang, "gleam_otp_external", "convert_erlang_start_error") +fn convert_erlang_start_error(dynamic: Dynamic) -> actor.StartError + +@external(erlang, "supervisor", "start_link") +fn unnamed_start( + module: Atom, + args: #(ErlangStartFlags, List(ErlangChildSpec)), +) -> Result(Pid, Dynamic) + +@external(erlang, "supervisor", "start_link") +fn named_start( + name: ErlangSupervisorName(child_argument, child_data), + module: Atom, + args: #(ErlangStartFlags, List(ErlangChildSpec)), +) -> Result(Pid, Dynamic) + +type ErlangSupervisorName(child_argument, child_data) { + Local(process.Name(Message(child_argument, child_data))) +} + +type Strategy { + SimpleOneForOne +} + +type ErlangStartFlag(data) { + Strategy(Strategy) + Intensity(Int) + Period(Int) +} + +type ErlangChildSpec + +@external(erlang, "maps", "from_list") +fn make_erlang_child_spec( + properties: List(ErlangChildSpecProperty(argument, data)), +) -> ErlangChildSpec + +type ErlangChildSpecProperty(argument, data) { + Id(Int) + Start( + #( + Atom, + Atom, + List(fn(argument) -> Result(actor.Started(data), actor.StartError)), + ), + ) + Restart(supervision.Restart) + Type(Atom) + Shutdown(Timeout) +} + +type Timeout + +/// Negative numbers mean an infinite timeout +@external(erlang, "gleam_otp_external", "make_timeout") +fn make_timeout(amount: Int) -> Timeout + +/// Create a `ChildSpecification` that adds this supervisor as the child of +/// another, making it fault tolerant and part of the application's supervision +/// tree. You should prefer to starting unsupervised supervisors with the +/// `start` function. +/// +/// If any child fails to start the supevisor first terminates all already +/// started child processes with reason shutdown and then terminate itself and +/// returns an error. +/// +pub fn supervised( + builder: Builder(child_argument, child_data), +) -> ChildSpecification(Supervisor(child_argument, child_data)) { + supervision.supervisor(fn() { start(builder) }) +} + +/// Start a new child using the supervisor's child template and the given +/// argument. The start result of the child is returned. +/// +pub fn start_child( + supervisor: Supervisor(child_argument, child_data), + argument: child_argument, +) -> actor.StartResult(child_data) { + let start = case supervisor { + NamedSupervisor(name:) -> erlang_start_child_name(name, _) + Supervisor(pid:) -> erlang_start_child_pid(pid, _) + } + case start([argument]) { + result2.Ok(pid, data) -> Ok(actor.Started(pid, data)) + result2.Error(reason) -> Error(reason) + } +} + +@external(erlang, "supervisor", "start_child") +fn erlang_start_child_name( + supervisor: process.Name(Message(child_argument, child_data)), + argument: List(child_argument), +) -> Result2(Pid, data, actor.StartError) + +@external(erlang, "supervisor", "start_child") +fn erlang_start_child_pid( + supervisor: Pid, + argument: List(child_argument), +) -> Result2(Pid, data, actor.StartError) + +// Callback used by the Erlang supervisor module. +@internal +pub fn init(start_data: Dynamic) -> Result(Dynamic, never) { + Ok(start_data) +} + +// Callback used by the Erlang supervisor module. +@internal +pub fn start_child_callback( + start: fn(argument) -> Result(actor.Started(data), actor.StartError), + argument: argument, +) -> Result2(Pid, data, actor.StartError) { + case start(argument) { + Ok(started) -> result2.Ok(started.pid, started.data) + Error(error) -> result2.Error(error) + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/internal/result2.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/internal/result2.gleam new file mode 100644 index 0000000..8684e8d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/internal/result2.gleam @@ -0,0 +1,5 @@ +/// An alernative ok type used within Erlang/OTP +pub type Result2(data1, data2, error) { + Ok(data1, data2) + Error(error) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/port.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/port.gleam new file mode 100644 index 0000000..73d7b00 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/port.gleam @@ -0,0 +1,12 @@ +/// Deprecated: Use gleam_erlang Port instead. +/// +/// Ports are how code running on the Erlang virtual machine interacts with +/// the outside world. Bytes of data can be sent to and read from ports, +/// providing a form of message passing to an external program or resource. +/// +/// For more information on ports see the [Erlang ports documentation][1]. +/// +/// [1]: https://erlang.org/doc/reference_manual/ports.html +/// +@deprecated("Use gleam_erlang Port instead") +pub type Port diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/static_supervisor.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/static_supervisor.gleam new file mode 100644 index 0000000..a3f6b2f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/static_supervisor.gleam @@ -0,0 +1,284 @@ +//// A supervisor where the number and types of the children are specified +//// once, and the supervisor manages them using a configured restart strategy. +//// +//// For further detail see the Erlang documentation: +//// . +//// +//// # Example +//// +//// ```gleam +//// import gleam/otp/actor +//// import gleam/otp/static_supervisor.{type Supervisor} as supervisor +//// import app/database_pool +//// import app/http_server +//// +//// pub fn start_supervisor() -> actor.StartResult(Supervisor) { +//// supervisor.new(supervisor.OneForOne) +//// |> supervisor.add(database_pool.supervised()) +//// |> supervisor.add(http_server.supervised()) +//// |> supervisor.start +//// } +//// ``` + +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} +import gleam/list +import gleam/otp/actor +import gleam/otp/supervision.{type ChildSpecification} + +/// A reference to the running supervisor. In future this could be used to send +/// commands to the supervisor to perform certain actions, but today no such +/// APIs have been exposed. +/// +/// This supervisor wrap Erlang/OTP's `supervisor` module, and as such it does +/// not use subjects for message sending. If it was implemented in Gleam a +/// subject might be used instead of this type. +/// +pub opaque type Supervisor { + Supervisor(pid: Pid) +} + +/// How the supervisor should react when one of its children terminates. +pub type Strategy { + /// If one child process terminates and is to be restarted, only that child + /// process is affected. This is the default restart strategy. + OneForOne + + /// If one child process terminates and is to be restarted, all other child + /// processes are terminated and then all child processes are restarted. + OneForAll + + /// If one child process terminates and is to be restarted, the 'rest' of the + /// child processes (that is, the child processes after the terminated child + /// process in the start order) are terminated. Then the terminated child + /// process and all child processes after it are restarted. + RestForOne +} + +/// A supervisor can be configured to automatically shut itself down with exit +/// reason shutdown when significant children terminate with the auto_shutdown +/// key in the above map. +pub type AutoShutdown { + /// Automic shutdown is disabled. This is the default setting. + /// + /// With auto_shutdown set to never, child specs with the significant flag + /// set to true are considered invalid and will be rejected. + Never + /// The supervisor will shut itself down when any significant child + /// terminates, that is, when a transient significant child terminates + /// normally or when a temporary significant child terminates normally or + /// abnormally. + AnySignificant + /// The supervisor will shut itself down when all significant children have + /// terminated, that is, when the last active significant child terminates. + /// The same rules as for any_significant apply. + AllSignificant +} + +/// A builder for configuring and starting a supervisor. See each of the +/// functions that take this type for details of the configuration possible. +/// +/// # Example +/// +/// ```gleam +/// import gleam/erlang/actor +/// import gleam/otp/static_supervisor.{type Supervisor} as supervisor +/// import app/database_pool +/// import app/http_server +/// +/// pub fn start_supervisor() -> actor.StartResult(Supervisor) { +/// supervisor.new(supervisor.OneForOne) +/// |> supervisor.add(database_pool.supervised()) +/// |> supervisor.add(http_server.supervised()) +/// |> supervisor.start +/// } +/// ``` +/// +pub opaque type Builder { + Builder( + strategy: Strategy, + intensity: Int, + period: Int, + auto_shutdown: AutoShutdown, + children: List(ChildSpecification(Nil)), + ) +} + +/// Create a new supervisor builder, ready for further configuration. +/// +pub fn new(strategy strategy: Strategy) -> Builder { + Builder( + strategy: strategy, + intensity: 2, + period: 5, + auto_shutdown: Never, + children: [], + ) +} + +/// To prevent a supervisor from getting into an infinite loop of child +/// process terminations and restarts, a maximum restart tolerance is +/// defined using two integer values specified with keys intensity and +/// period in the above map. Assuming the values MaxR for intensity and MaxT +/// for period, then, if more than MaxR restarts occur within MaxT seconds, +/// the supervisor terminates all child processes and then itself. The +/// termination reason for the supervisor itself in that case will be +/// shutdown. +/// +/// Intensity defaults to 2 and period defaults to 5. +/// +pub fn restart_tolerance( + builder: Builder, + intensity intensity: Int, + period period: Int, +) -> Builder { + Builder(..builder, intensity: intensity, period: period) +} + +/// A supervisor can be configured to automatically shut itself down with +/// exit reason shutdown when significant children terminate. +/// +pub fn auto_shutdown(builder: Builder, value: AutoShutdown) -> Builder { + Builder(..builder, auto_shutdown: value) +} + +/// Start a new supervisor process with the configuration and children +/// specified within the builder. +/// +/// Typically you would use the `supervised` function to add your supervisor to +/// a supervision tree instead of using this function directly. +/// +/// The supervisor will be linked to the parent process that calls this +/// function. +/// +/// If any child fails to start the supevisor first terminates all already +/// started child processes with reason shutdown and then terminate itself and +/// returns an error. +/// +pub fn start( + builder: Builder, +) -> Result(actor.Started(Supervisor), actor.StartError) { + let flags = + make_erlang_start_flags([ + Strategy(builder.strategy), + Intensity(builder.intensity), + Period(builder.period), + AutoShutdown(builder.auto_shutdown), + ]) + + let module = atom.create("gleam@otp@static_supervisor") + let children = + builder.children |> list.reverse |> list.index_map(convert_child) + case erlang_start_link(module, #(flags, children)) { + Ok(pid) -> Ok(actor.Started(pid:, data: Supervisor(pid))) + Error(error) -> Error(convert_erlang_start_error(error)) + } +} + +/// Create a `ChildSpecification` that adds this supervisor as the child of +/// another, making it fault tolerant and part of the application's supervision +/// tree. You should prefer to starting unsupervised supervisors with the +/// `start` function. +/// +/// If any child fails to start the supevisor first terminates all already +/// started child processes with reason shutdown and then terminate itself and +/// returns an error. +/// +pub fn supervised(builder: Builder) -> ChildSpecification(Supervisor) { + supervision.supervisor(fn() { start(builder) }) +} + +@external(erlang, "gleam_otp_external", "convert_erlang_start_error") +fn convert_erlang_start_error(dynamic: Dynamic) -> actor.StartError + +@external(erlang, "supervisor", "start_link") +fn erlang_start_link( + module: Atom, + args: #(ErlangStartFlags, List(ErlangChildSpec)), +) -> Result(Pid, Dynamic) + +/// Add a child to the supervisor. +pub fn add(builder: Builder, child: ChildSpecification(data)) -> Builder { + Builder(..builder, children: [ + supervision.map_data(child, fn(_) { Nil }), + ..builder.children + ]) +} + +fn convert_child(child: ChildSpecification(data), id: Int) -> ErlangChildSpec { + let mfa = #( + atom.create("gleam@otp@static_supervisor"), + atom.create("start_child_callback"), + [child.start], + ) + + let #(type_, shutdown) = case child.child_type { + supervision.Supervisor -> #(atom.create("supervisor"), make_timeout(-1)) + supervision.Worker(ms) -> #(atom.create("worker"), make_timeout(ms)) + } + + make_erlang_child_spec([ + Id(id), + Start(mfa), + Restart(child.restart), + Significant(child.significant), + Type(type_), + Shutdown(shutdown), + ]) +} + +type ErlangStartFlags + +@external(erlang, "maps", "from_list") +fn make_erlang_start_flags( + flags: List(ErlangStartFlag(data)), +) -> ErlangStartFlags + +type ErlangStartFlag(data) { + Strategy(Strategy) + Intensity(Int) + Period(Int) + AutoShutdown(AutoShutdown) +} + +type ErlangChildSpec + +@external(erlang, "maps", "from_list") +fn make_erlang_child_spec( + properties: List(ErlangChildSpecProperty(data)), +) -> ErlangChildSpec + +type ErlangChildSpecProperty(data) { + Id(Int) + Start( + #(Atom, Atom, List(fn() -> Result(actor.Started(data), actor.StartError))), + ) + Restart(supervision.Restart) + Significant(Bool) + Type(Atom) + Shutdown(Timeout) +} + +type Timeout + +/// Negative numbers mean an infinite timeout +@external(erlang, "gleam_otp_external", "make_timeout") +fn make_timeout(amount: Int) -> Timeout + +// Callback used by the Erlang supervisor module. +@internal +pub fn init(start_data: Dynamic) -> Result(Dynamic, never) { + Ok(start_data) +} + +// Callback used by the Erlang supervisor module. +@internal +pub fn start_child_callback( + start: fn() -> Result(actor.Started(anything), actor.StartError), +) -> Result(Pid, actor.StartError) { + case start() { + Ok(started) -> Ok(started.pid) + Error(error) -> Error(error) + } +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/supervision.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/supervision.gleam new file mode 100644 index 0000000..08fb4a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/supervision.gleam @@ -0,0 +1,143 @@ +import gleam/otp/actor + +/// Restart defines when a terminated child process must be restarted. +pub type Restart { + /// A permanent child process is always restarted. + Permanent + /// A transient child process is restarted only if it terminates abnormally, + /// that is, with another exit reason than `normal`, `shutdown`, or + /// `{shutdown,Term}`. + Transient + /// A temporary child process is never restarted (even when the supervisor's + /// restart strategy is `RestForOne` or `OneForAll` and a sibling's death + /// causes the temporary process to be terminated). + Temporary +} + +pub type ChildType { + /// A worker child has to shut-down within a given amount of time. + Worker( + /// The number of milliseconds the child is given to shut down. The + /// supervisor tells the child process to terminate by calling + /// `exit(Child,shutdown)` and then wait for an exit signal with reason + /// shutdown back from the child process. If no exit signal is received + /// within the specified number of milliseconds, the child process is + /// unconditionally terminated using `exit(Child,kill)`. + shutdown_ms: Int, + ) + Supervisor +} + +/// A description a how to start a new child process under an OTP supervisor. +pub type ChildSpecification(data) { + ChildSpecification( + /// A function to call to start the child process. + start: fn() -> Result(actor.Started(data), actor.StartError), + /// When the child is to be restarted. See the `Restart` documentation for + /// more. + /// + /// You most likely want the `Permanent` variant. + restart: Restart, + /// This defines if a child is considered significant for automatic + /// self-shutdown of the supervisor. + /// + /// You most likely do not want to consider any children significant. + /// + /// This will be ignored if the supervisor auto shutdown is set to `Never`, + /// which is the default. + significant: Bool, + /// Whether the child is a supervisor or not. + child_type: ChildType, + ) +} + +/// A regular child process. +/// +/// You should use this unless your process is also a supervisor. +/// +/// The default shutdown timeout is 5000ms. This can be changed with the +/// `timeout` function. +/// +pub fn worker( + run start: fn() -> Result(actor.Started(data), actor.StartError), +) -> ChildSpecification(data) { + ChildSpecification( + start:, + restart: Permanent, + significant: False, + child_type: Worker(5000), + ) +} + +/// A special child that is a supervisor itself. +/// +/// Supervisor children have an unlimited shutdown time, there is no timeout. +/// +pub fn supervisor( + run start: fn() -> Result(actor.Started(data), actor.StartError), +) -> ChildSpecification(data) { + ChildSpecification( + start:, + restart: Permanent, + significant: False, + child_type: Supervisor, + ) +} + +/// This defines if a child is considered significant for automatic +/// self-shutdown of the supervisor. +/// +/// You most likely do not want to consider any children significant. +/// +/// This will be ignored if the supervisor auto shutdown is set to `Never`, +/// which is the default. +/// +/// The default value for significance is `False`. +pub fn significant( + child: ChildSpecification(data), + significant: Bool, +) -> ChildSpecification(data) { + ChildSpecification(..child, significant: significant) +} + +/// This defines the amount of milliseconds a child has to shut down before +/// being brutal killed by the supervisor. +/// +/// If not set the default for a child is 5000ms. +/// +/// This will be ignored if the child is a supervisor itself. +/// +pub fn timeout( + child: ChildSpecification(data), + ms ms: Int, +) -> ChildSpecification(data) { + case child.child_type { + Worker(_) -> ChildSpecification(..child, child_type: Worker(ms)) + _ -> child + } +} + +/// When the child is to be restarted. See the `Restart` documentation for +/// more. +/// +/// The default value for restart is `Permanent`. +pub fn restart( + child: ChildSpecification(data), + restart: Restart, +) -> ChildSpecification(data) { + ChildSpecification(..child, restart: restart) +} + +/// Transform the data of the started child process. +/// +pub fn map_data( + child: ChildSpecification(a), + transform: fn(a) -> b, +) -> ChildSpecification(b) { + ChildSpecification(..child, start: fn() { + case child.start() { + Ok(started) -> Ok(actor.Started(..started, data: transform(started.data))) + Error(e) -> Error(e) + } + }) +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/system.gleam b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/system.gleam new file mode 100644 index 0000000..8916116 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam/otp/system.gleam @@ -0,0 +1,95 @@ +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} + +pub type Mode { + /// Currently handling message as normal. + Running + /// Termporarily not handling messages, other than system messages. + Suspended +} + +pub type DebugOption { + NoDebug +} + +pub type DebugState + +@external(erlang, "sys", "debug_options") +pub fn debug_state(a: List(DebugOption)) -> DebugState + +pub type StatusInfo { + StatusInfo( + module: Atom, + parent: Pid, + mode: Mode, + debug_state: DebugState, + state: Dynamic, + ) +} + +// TODO: document +// TODO: implement remaining messages +pub type SystemMessage { + // {replace_state, StateFn} + // {change_code, Mod, Vsn, Extra} + // {terminate, Reason} + // {debug, {log, Flag}} + // {debug, {trace, Flag}} + // {debug, {log_to_file, FileName}} + // {debug, {statistics, Flag}} + // {debug, no_debug} + // {debug, {install, {Func, FuncState}}} + // {debug, {install, {FuncId, Func, FuncState}}} + // {debug, {remove, FuncOrId}} + Resume(fn() -> Nil) + Suspend(fn() -> Nil) + GetState(fn(Dynamic) -> Nil) + GetStatus(fn(StatusInfo) -> Nil) +} + +type DoNotLeak + +/// Get the state of a given OTP compatible process. This function is only +/// intended for debugging. +/// +/// Requires Erlang/OTP 26.1 or newer, as the underlying interface changed +/// in [OTP-18633][1] from a literal type to a result type. +/// +/// For more information see the [Erlang documentation][2]. +/// +/// [1]: https://www.erlang.org/patches/otp-26.1#stdlib-5.1 +/// [2]: https://erlang.org/doc/man/sys.html#get_state-1 +/// +@external(erlang, "sys", "get_state") +pub fn get_state(from from: Pid) -> Dynamic + +@external(erlang, "sys", "suspend") +fn erl_suspend(a: Pid) -> DoNotLeak + +/// Request an OTP compatible process to suspend, causing it to only handle +/// system messages. +/// +/// For more information see the [Erlang documentation][1]. +/// +/// [1]: https://erlang.org/doc/man/sys.html#suspend-1 +/// +pub fn suspend(pid: Pid) -> Nil { + erl_suspend(pid) + Nil +} + +@external(erlang, "sys", "resume") +fn erl_resume(from from: Pid) -> DoNotLeak + +/// Request a suspended OTP compatible process to result, causing it to handle +/// all messages rather than only system messages. +/// +/// For more information see the [Erlang documentation][1]. +/// +/// [1]: https://erlang.org/doc/man/sys.html#resume-1 +/// +pub fn resume(pid: Pid) -> Nil { + erl_resume(pid) + Nil +} diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@actor.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@actor.erl new file mode 100644 index 0000000..a9eb71e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@actor.erl @@ -0,0 +1,640 @@ +-module(gleam@otp@actor). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/actor.gleam"). +-export([continue/1, stop/0, stop_abnormal/1, with_selector/2, initialised/1, selecting/2, returning/2, new/1, new_with_initialiser/2, on_message/2, named/2, start/1, send/2, call/3]). +-export_type([message/1, next/2, self/2, started/1, initialised/3, builder/3, start_error/0, start_init_message/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " This module provides the _Actor_ abstraction, one of the most common\n" + " building blocks of Gleam OTP programs.\n" + "\n" + " An Actor is a process like any other BEAM process and can be used to hold\n" + " state, execute code, and communicate with other processes by sending and\n" + " receiving messages. The advantage of using the actor abstraction over a bare\n" + " process is that it provides a single interface for commonly needed\n" + " functionality, including support for the [tracing and debugging\n" + " features in OTP][erlang-sys].\n" + "\n" + " Gleam's Actor is similar to Erlang's `gen_server` and Elixir's `GenServer`\n" + " but differs in that it offers a fully typed interface. This different API is\n" + " why Gleam uses the name \"Actor\" rather than some variation of\n" + " \"generic-server\".\n" + "\n" + " [erlang-sys]: https://www.erlang.org/doc/man/sys.html\n" + "\n" + " ## Example\n" + "\n" + " An Actor can be used to create a client-server interaction between an Actor\n" + " (the server) and other processes (the clients). In this example we have an\n" + " Actor that works as a stack, allowing clients to push and pop elements.\n" + "\n" + " ```gleam\n" + " pub fn main() {\n" + " // Start the actor with initial state of an empty list, and the\n" + " // `handle_message` callback function (defined below).\n" + " // We assert that it starts successfully.\n" + " // \n" + " // In real-world Gleam OTP programs we would likely write a wrapper functions\n" + " // called `start`, `push` `pop`, `shutdown` to start and interact with the\n" + " // Actor. We are not doing that here for the sake of showing how the Actor \n" + " // API works.\n" + " let assert Ok(actor) =\n" + " actor.new([]) |> actor.on_message(handle_message) |> actor.start\n" + " let subject = actor.data\n" + "\n" + " // We can send a message to the actor to push elements onto the stack.\n" + " process.send(subject, Push(\"Joe\"))\n" + " process.send(subject, Push(\"Mike\"))\n" + " process.send(subject, Push(\"Robert\"))\n" + "\n" + " // The `Push` message expects no response, these messages are sent purely for\n" + " // the side effect of mutating the state held by the actor.\n" + " //\n" + " // We can also send the `Pop` message to take a value off of the actor's\n" + " // stack. This message expects a response, so we use `process.call` to send a\n" + " // message and wait until a reply is received.\n" + " //\n" + " // In this instance we are giving the actor 10 milliseconds to reply, if the\n" + " // `call` function doesn't get a reply within this time it will panic and\n" + " // crash the client process.\n" + " let assert Ok(\"Robert\") = process.call(subject, 10, Pop)\n" + " let assert Ok(\"Mike\") = process.call(subject, 10, Pop)\n" + " let assert Ok(\"Joe\") = process.call(subject, 10, Pop)\n" + "\n" + " // The stack is now empty, so if we pop again the actor replies with an error.\n" + " let assert Error(Nil) = process.call(subject, 10, Pop)\n" + "\n" + " // Lastly, we can send a message to the actor asking it to shut down.\n" + " process.send(subject, Shutdown)\n" + " }\n" + " ```\n" + "\n" + " Here is the code that is used to implement this actor:\n" + "\n" + " ```gleam\n" + " // First step of implementing the stack Actor is to define the message type that\n" + " // it can receive.\n" + " //\n" + " // The type of the elements in the stack is not fixed so a type parameter\n" + " // is used for it instead of a concrete type such as `String` or `Int`.\n" + " pub type Message(element) {\n" + " // The `Shutdown` message is used to tell the actor to stop.\n" + " // It is the simplest message type, it contains no data.\n" + " //\n" + " // Most the time we don't define an API to shut down an actor, but in this\n" + " // example we do to show how it can be done.\n" + " Shutdown\n" + " \n" + " // The `Push` message is used to add a new element to the stack.\n" + " // It contains the item to add, the type of which is the `element`\n" + " // parameterised type.\n" + " Push(push: element)\n" + " \n" + " // The `Pop` message is used to remove an element from the stack.\n" + " // It contains a `Subject`, which is used to send the response back to the\n" + " // message sender. In this case the reply is of type `Result(element, Nil)`.\n" + " Pop(reply_with: Subject(Result(element, Nil)))\n" + " }\n" + " \n" + " // The last part is to implement the `handle_message` callback function.\n" + " //\n" + " // This function is called by the Actor for each message it receives.\n" + " // Actors are single threaded only doing one thing at a time, so they handle\n" + " // messages sequentially one at a time, in the order they are received.\n" + " //\n" + " // The function takes the current state and a message, and returns a data\n" + " // structure that indicates what to do next, along with the new state.\n" + " fn handle_message(\n" + " stack: List(e),\n" + " message: Message(e),\n" + " ) -> actor.Next(List(e), Message(e)) {\n" + " case message {\n" + " // For the `Shutdown` message we return the `actor.stop` value, which causes\n" + " // the actor to discard any remaining messages and stop.\n" + " // We may chose to do some clean-up work here, but this actor doesn't need\n" + " // to do this.\n" + " Shutdown -> actor.stop()\n" + " \n" + " // For the `Push` message we add the new element to the stack and return\n" + " // `actor.continue` with this new stack, causing the actor to process any\n" + " // queued messages or wait for more.\n" + " Push(value) -> {\n" + " let new_state = [value, ..stack]\n" + " actor.continue(new_state)\n" + " }\n" + " \n" + " // For the `Pop` message we attempt to remove an element from the stack,\n" + " // sending it or an error back to the caller, before continuing.\n" + " Pop(client) -> {\n" + " case stack {\n" + " [] -> {\n" + " // When the stack is empty we can't pop an element, so we send an\n" + " // error back.\n" + " process.send(client, Error(Nil))\n" + " actor.continue([])\n" + " }\n" + " \n" + " [first, ..rest] -> {\n" + " // Otherwise we send the first element back and use the remaining\n" + " // elements as the new state.\n" + " process.send(client, Ok(first))\n" + " actor.continue(rest)\n" + " }\n" + " }\n" + " }\n" + " }\n" + " }\n" + " ```\n" +). + +-type message(EAR) :: {message, EAR} | + {system, gleam@otp@system:system_message()} | + {unexpected, gleam@dynamic:dynamic_()}. + +-opaque next(EAS, EAT) :: {continue, + EAS, + gleam@option:option(gleam@erlang@process:selector(EAT))} | + {stop, gleam@erlang@process:exit_reason()}. + +-type self(EAU, EAV) :: {self, + gleam@otp@system:mode(), + gleam@erlang@process:pid_(), + EAU, + gleam@erlang@process:selector(message(EAV)), + gleam@otp@system:debug_state(), + fun((EAU, EAV) -> next(EAU, EAV))}. + +-type started(EAW) :: {started, gleam@erlang@process:pid_(), EAW}. + +-opaque initialised(EAX, EAY, EAZ) :: {initialised, + EAX, + gleam@option:option(gleam@erlang@process:selector(EAY)), + EAZ}. + +-opaque builder(EBA, EBB, EBC) :: {builder, + fun((gleam@erlang@process:subject(EBB)) -> {ok, + initialised(EBA, EBB, EBC)} | + {error, binary()}), + integer(), + fun((EBA, EBB) -> next(EBA, EBB)), + gleam@option:option(gleam@erlang@process:name(EBB))}. + +-type start_error() :: init_timeout | + {init_failed, binary()} | + {init_exited, gleam@erlang@process:exit_reason()}. + +-type start_init_message(EBD) :: {ack, {ok, EBD} | {error, binary()}} | + {mon, gleam@erlang@process:down()}. + +-file("src/gleam/otp/actor.gleam", 185). +?DOC(" Indicate the actor should continue, processing any waiting or future messages.\n"). +-spec continue(EBI) -> next(EBI, any()). +continue(State) -> + {continue, State, none}. + +-file("src/gleam/otp/actor.gleam", 193). +?DOC( + " Indicate the actor should stop and shut-down, handling no futher messages.\n" + "\n" + " The reason for exiting is `Normal`.\n" +). +-spec stop() -> next(any(), any()). +stop() -> + {stop, normal}. + +-file("src/gleam/otp/actor.gleam", 202). +?DOC( + " Indicate the actor is in a bad state and should shut down. It will not\n" + " handle any new messages, and any linked processes will also exit abnormally.\n" + "\n" + " The provided reason will be given and propagated.\n" +). +-spec stop_abnormal(binary()) -> next(any(), any()). +stop_abnormal(Reason) -> + {stop, {abnormal, gleam_stdlib:identity(Reason)}}. + +-file("src/gleam/otp/actor.gleam", 210). +?DOC( + " Provide a selector to change the messages that the actor is handling\n" + " going forward. This replaces any selector that was previously given\n" + " in the actor's `init` callback, or in any previous `Next` value.\n" +). +-spec with_selector(next(EBU, EBV), gleam@erlang@process:selector(EBV)) -> next(EBU, EBV). +with_selector(Value, Selector) -> + case Value of + {continue, State, _} -> + {continue, State, {some, Selector}}; + + {stop, _} -> + Value + end. + +-file("src/gleam/otp/actor.gleam", 268). +?DOC( + " Takes the post-initialisation state of the actor. This state will be passed\n" + " to the `on_message` callback each time a message is received.\n" +). +-spec initialised(ECB) -> initialised(ECB, any(), nil). +initialised(State) -> + {initialised, State, none, nil}. + +-file("src/gleam/otp/actor.gleam", 277). +?DOC( + " Add a selector for the actor to receive messages with.\n" + "\n" + " If a message is received by the actor but not selected for with the\n" + " selector then the actor will discard it and log a warning.\n" +). +-spec selecting( + initialised(ECG, any(), ECI), + gleam@erlang@process:selector(ECM) +) -> initialised(ECG, ECM, ECI). +selecting(Initialised, Selector) -> + {initialised, + erlang:element(2, Initialised), + {some, Selector}, + erlang:element(4, Initialised)}. + +-file("src/gleam/otp/actor.gleam", 287). +?DOC( + " Add the data to return to the parent process. This might be a subject that\n" + " the actor will receive messages over.\n" +). +-spec returning(initialised(ECR, ECS, any()), ECX) -> initialised(ECR, ECS, ECX). +returning(Initialised, Return) -> + {initialised, + erlang:element(2, Initialised), + erlang:element(3, Initialised), + Return}. + +-file("src/gleam/otp/actor.gleam", 329). +?DOC( + " Create a builder for an actor without a custom initialiser. The actor\n" + " returns a subject to the parent that can be used to send messages to the\n" + " actor.\n" + "\n" + " If the actor has been given a name with the `named` function then the\n" + " subject is a named subject.\n" + "\n" + " If you wish to create an actor with some other initialisation logic that\n" + " runs before it starts handling messages, see `new_with_initialiser`.\n" +). +-spec new(EDB) -> builder(EDB, EDC, gleam@erlang@process:subject(EDC)). +new(State) -> + Initialise = fun(Subject) -> _pipe = initialised(State), + _pipe@1 = returning(_pipe, Subject), + {ok, _pipe@1} end, + {builder, Initialise, 1000, fun(State@1, _) -> continue(State@1) end, none}. + +-file("src/gleam/otp/actor.gleam", 358). +?DOC( + " Create a builder for an actor with a custom initialiser that runs before\n" + " the start function returns to the parent, and before the actor starts\n" + " handling messages.\n" + "\n" + " The first argument is a number of milliseconds that the initialiser\n" + " function is expected to return within. If it takes longer the initialiser\n" + " is considered to have failed and the actor will be killed, and an error\n" + " will be returned to the parent.\n" + "\n" + " The actor's default subject is passed to the initialiser function. You can\n" + " chose to return it to the parent with `returning`, use it in some other\n" + " way, or ignore it completely.\n" + "\n" + " If a custom selector is given using the `selecting` function then this\n" + " overwrites the default selector, which selects for the default subject, so\n" + " you will need to add the subject to the custom selector yourself.\n" +). +-spec new_with_initialiser( + integer(), + fun((gleam@erlang@process:subject(EDH)) -> {ok, initialised(EDJ, EDH, EDK)} | + {error, binary()}) +) -> builder(EDJ, EDH, EDK). +new_with_initialiser(Timeout, Initialise) -> + {builder, Initialise, Timeout, fun(State, _) -> continue(State) end, none}. + +-file("src/gleam/otp/actor.gleam", 376). +?DOC( + " Set the message handler for the actor. This callback function will be\n" + " called each time the actor receives a message.\n" + "\n" + " Actors handle messages sequentially, later messages being handled after the\n" + " previous one has been handled.\n" +). +-spec on_message(builder(EDT, EDU, EDV), fun((EDT, EDU) -> next(EDT, EDU))) -> builder(EDT, EDU, EDV). +on_message(Builder, Handler) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + Handler, + erlang:element(5, Builder)}. + +-file("src/gleam/otp/actor.gleam", 395). +?DOC( + " Provide a name for the actor to be registered with when started, enabling\n" + " it to receive messages via a named subject. This is useful for making\n" + " processes that can take over from an older one that has exited due to a\n" + " failure, or to avoid passing subjects from receiver processes to sender\n" + " processes.\n" + "\n" + " If the name is already registered to another process then the actor will\n" + " fail to start.\n" + "\n" + " When this function is used the actor's default subject will be a named\n" + " subject using this name.\n" +). +-spec named(builder(EEE, EEF, EEG), gleam@erlang@process:name(EEF)) -> builder(EEE, EEF, EEG). +named(Builder, Name) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + {some, Name}}. + +-file("src/gleam/otp/actor.gleam", 402). +-spec exit_process(gleam@erlang@process:exit_reason()) -> gleam@erlang@process:exit_reason(). +exit_process(Reason) -> + case Reason of + {abnormal, Reason@1} -> + gleam@erlang@process:send_abnormal_exit(erlang:self(), Reason@1); + + killed -> + gleam@erlang@process:kill(erlang:self()); + + _ -> + nil + end, + Reason. + +-file("src/gleam/otp/actor.gleam", 443). +-spec select_system_messages(gleam@erlang@process:selector(message(EET))) -> gleam@erlang@process:selector(message(EET)). +select_system_messages(Selector) -> + _pipe = Selector, + gleam@erlang@process:select_record( + _pipe, + erlang:binary_to_atom(<<"system"/utf8>>), + 2, + fun gleam_otp_external:convert_system_message/1 + ). + +-file("src/gleam/otp/actor.gleam", 412). +-spec receive_message(self(any(), EEP)) -> message(EEP). +receive_message(Self) -> + Selector = case erlang:element(2, Self) of + suspended -> + _pipe = gleam_erlang_ffi:new_selector(), + select_system_messages(_pipe); + + running -> + _pipe@1 = gleam_erlang_ffi:new_selector(), + _pipe@2 = gleam@erlang@process:select_other( + _pipe@1, + fun(Field@0) -> {unexpected, Field@0} end + ), + _pipe@3 = gleam_erlang_ffi:merge_selector( + _pipe@2, + erlang:element(5, Self) + ), + select_system_messages(_pipe@3) + end, + gleam_erlang_ffi:select(Selector). + +-file("src/gleam/otp/actor.gleam", 453). +-spec process_status_info(self(any(), any())) -> gleam@otp@system:status_info(). +process_status_info(Self) -> + {status_info, + erlang:binary_to_atom(<<"gleam@otp@actor"/utf8>>), + erlang:element(3, Self), + erlang:element(2, Self), + erlang:element(6, Self), + gleam_otp_external:identity(erlang:element(4, Self))}. + +-file("src/gleam/otp/actor.gleam", 463). +-spec loop(self(any(), any())) -> gleam@erlang@process:exit_reason(). +loop(Self) -> + case receive_message(Self) of + {system, System} -> + case System of + {get_state, Callback} -> + Callback( + gleam_otp_external:identity(erlang:element(4, Self)) + ), + loop(Self); + + {resume, Callback@1} -> + Callback@1(), + loop( + {self, + running, + erlang:element(3, Self), + erlang:element(4, Self), + erlang:element(5, Self), + erlang:element(6, Self), + erlang:element(7, Self)} + ); + + {suspend, Callback@2} -> + Callback@2(), + loop( + {self, + suspended, + erlang:element(3, Self), + erlang:element(4, Self), + erlang:element(5, Self), + erlang:element(6, Self), + erlang:element(7, Self)} + ); + + {get_status, Callback@3} -> + Callback@3(process_status_info(Self)), + loop(Self) + end; + + {unexpected, Message} -> + logger:warning( + unicode:characters_to_list( + <<"Actor discarding unexpected message: ~s"/utf8>> + ), + [unicode:characters_to_list(gleam@string:inspect(Message))] + ), + loop(Self); + + {message, Msg} -> + case (erlang:element(7, Self))(erlang:element(4, Self), Msg) of + {stop, Reason} -> + exit_process(Reason); + + {continue, State, New_selector} -> + Selector = case New_selector of + none -> + erlang:element(5, Self); + + {some, S} -> + gleam_erlang_ffi:map_selector( + S, + fun(Field@0) -> {message, Field@0} end + ) + end, + loop( + {self, + erlang:element(2, Self), + erlang:element(3, Self), + State, + Selector, + erlang:element(6, Self), + erlang:element(7, Self)} + ) + end + end. + +-file("src/gleam/otp/actor.gleam", 573). +-spec try_register_self(gleam@erlang@process:name(any())) -> {ok, nil} | + {error, binary()}. +try_register_self(Name) -> + case gleam_erlang_ffi:register_process(erlang:self(), Name) of + {ok, nil} -> + {ok, nil}; + + {error, _} -> + {error, <<"name already registered"/utf8>>} + end. + +-file("src/gleam/otp/actor.gleam", 522). +-spec initialise_actor( + builder(any(), any(), EFM), + gleam@erlang@process:pid_(), + gleam@erlang@process:subject({ok, EFM} | {error, binary()}) +) -> gleam@erlang@process:exit_reason(). +initialise_actor(Builder, Parent, Ack) -> + Result@1 = begin + gleam@result:'try'(case erlang:element(5, Builder) of + none -> + {ok, gleam@erlang@process:new_subject()}; + + {some, Name} -> + gleam@result:'try'( + try_register_self(Name), + fun(_) -> + {ok, gleam@erlang@process:named_subject(Name)} + end + ) + end, fun(Subject) -> + gleam@result:'try'( + (erlang:element(2, Builder))(Subject), + fun(Result) -> {ok, {Subject, Result}} end + ) + end) + end, + case Result@1 of + {ok, {Subject@1, {initialised, State, Selector, Return}}} -> + Selector@2 = case Selector of + {some, Selector@1} -> + Selector@1; + + none -> + _pipe = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe, Subject@1) + end, + Selector@3 = gleam_erlang_ffi:map_selector( + Selector@2, + fun(Field@0) -> {message, Field@0} end + ), + gleam@erlang@process:send(Ack, {ok, Return}), + Self = {self, + running, + Parent, + State, + Selector@3, + sys:debug_options([]), + erlang:element(4, Builder)}, + loop(Self); + + {error, Reason} -> + gleam@erlang@process:send(Ack, {error, Reason}), + exit_process(normal) + end. + +-file("src/gleam/otp/actor.gleam", 592). +?DOC(" Starts an actor from a given `Builder`. On failure, `start` returns a `StartError`\n"). +-spec start(builder(any(), any(), EFZ)) -> {ok, started(EFZ)} | + {error, start_error()}. +start(Builder) -> + Timeout = erlang:element(3, Builder), + Ack_subject = gleam@erlang@process:new_subject(), + Self = erlang:self(), + Child = proc_lib:spawn_link( + fun() -> initialise_actor(Builder, Self, Ack_subject) end + ), + Monitor = gleam@erlang@process:monitor(Child), + Selector = begin + _pipe = gleam_erlang_ffi:new_selector(), + _pipe@1 = gleam@erlang@process:select_map( + _pipe, + Ack_subject, + fun(Field@0) -> {ack, Field@0} end + ), + gleam@erlang@process:select_specific_monitor( + _pipe@1, + Monitor, + fun(Field@0) -> {mon, Field@0} end + ) + end, + Result = case gleam_erlang_ffi:select(Selector, Timeout) of + {ok, {ack, {ok, Subject}}} -> + {ok, Subject}; + + {ok, {ack, {error, Reason}}} -> + {error, {init_failed, Reason}}; + + {ok, {mon, Down}} -> + {error, {init_exited, erlang:element(4, Down)}}; + + {error, nil} -> + gleam@erlang@process:unlink(Child), + gleam@erlang@process:kill(Child), + {error, init_timeout} + end, + gleam@erlang@process:demonitor_process(Monitor), + case Result of + {ok, Data} -> + {ok, {started, Child, Data}}; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/otp/actor.gleam", 642). +?DOC( + " Send a message over a given channel.\n" + "\n" + " This is a re-export of `process.send`, for the sake of convenience.\n" +). +-spec send(gleam@erlang@process:subject(EGG), EGG) -> nil. +send(Subject, Msg) -> + gleam@erlang@process:send(Subject, Msg). + +-file("src/gleam/otp/actor.gleam", 654). +?DOC( + " Send a synchronous message and wait for a response from the receiving\n" + " process.\n" + "\n" + " If a reply is not received within the given timeout then the sender process\n" + " crashes rather than leaving the processes in an invalid state. \n" + "\n" + " This is a re-export of `process.call`, for the sake of convenience.\n" +). +-spec call( + gleam@erlang@process:subject(EGI), + integer(), + fun((gleam@erlang@process:subject(EGK)) -> EGI) +) -> EGK. +call(Subject, Timeout, Make_message) -> + gleam@erlang@process:call(Subject, Timeout, Make_message). diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@factory_supervisor.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@factory_supervisor.erl new file mode 100644 index 0000000..4606e73 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@factory_supervisor.erl @@ -0,0 +1,409 @@ +-module(gleam@otp@factory_supervisor). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/factory_supervisor.gleam"). +-export([get_by_name/1, named/2, restart_tolerance/3, timeout/2, restart_strategy/2, start/1, supervised/1, start_child/2, init/1, start_child_callback/2, worker_child/1, supervisor_child/1]). +-export_type([supervisor/2, message/2, builder/2, erlang_start_flags/0, erlang_supervisor_name/2, strategy/0, erlang_start_flag/1, erlang_child_spec/0, erlang_child_spec_property/2, timeout_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A supervisor where child processes are started dynamically from a\n" + " pre-specified template, so new processes can be created as needed\n" + " while the program is running.\n" + "\n" + " When the supervisor is shut down it shuts down all its children\n" + " concurrently and in no specified order.\n" + "\n" + " For further detail see the Erlang documentation, particularly the parts\n" + " about the `simple_one_for_one` restart strategy, which is the Erlang\n" + " equivilent of the factory supervisor.\n" + " .\n" + "\n" + " ## Usage\n" + "\n" + " Add the factory supervisor to your supervision tree using the `supervised`\n" + " function and a name created at the start of the program. The `new`\n" + " function takes a \"template function\", which is a function that takes one\n" + " argument and starts a linked child process.\n" + "\n" + " You most likely want to give the factory supervisor a name, and to pass\n" + " that name to any other processes that will want to cause new child\n" + " processes to be started under the factory supervisor. In this example a\n" + " web server is used.\n" + "\n" + " ```gleam\n" + " import gleam/erlang/process.{type Name}\n" + " import gleam/otp/actor.{type StartResult}\n" + " import gleam/otp/factory_supervisor as factory\n" + " import gleam/otp/static_supervisor as supervisor\n" + " import my_app\n" + " \n" + " /// This function starts the application's supervision tree.\n" + " ///\n" + " /// It takes a record as an argument that \n" + " ///\n" + " pub fn start_supervision_tree(reporters_name: Name(_)) -> StartResult(_) {\n" + " // Define a named factory supervisor that can create new child processes\n" + " // using the `my_app.start_reporter_actor` function, which is defined\n" + " // elsewhere in the program.\n" + " let reporter_factory_supervisor =\n" + " factory.worker_child(my_app.start_reporter_actor)\n" + " |> factory.named(reporters_name)\n" + " |> factory.supervised\n" + " \n" + " // This web server process takes the name, so it can contact the factory\n" + " // supervisor to command it to start new processes as needed.\n" + " let web_server = my_app.supervised_web_server(reporters_name)\n" + " \n" + " // Create the top-level static supervisor with the supervisor and web\n" + " // server as its children\n" + " supervisor.new(supervisor.RestForOne)\n" + " |> supervisor.add(reporter_factory_supervisor)\n" + " |> supervisor.add(web_server)\n" + " |> supervisor.start\n" + " }\n" + " ```\n" + "\n" + " Any process with the name of the factory supervisor can use the\n" + " `get_by_name` function to get a reference to the supervisor, and then use\n" + " the `start_child` function to have it start new child processes.\n" + "\n" + " Remember! Each process name created with `process.new_name` is unique.\n" + " Two names created by calling the function twice are different names, even\n" + " if the same string is given as an argument. You must create the name value\n" + " at the start of your program and then pass it down into application code\n" + " and library code that uses names.\n" + "\n" + " ```gleam\n" + " import gleam/http/request.{type Request}\n" + " import gleam/http/response.{type Response}\n" + " import gleam/otp/factory_supervisor\n" + " import my_app\n" + " \n" + " /// In our example this function is called each time a HTTP request is \n" + " /// received by the web server.\n" + " pub fn handle_request(req: Request(_), reporters: Name(_)) -> Response(_) {\n" + " // Get a reference to the supervisor using the name\n" + " let supervisor = factory_supervisor.get_by_name(reporters)\n" + " \n" + " // Start a new child process under the supervisor, passing the request path \n" + " // to use as the argument for the child-starting template function.\n" + " let start_result = factory_supervisor.start_child(supervisor, request.path)\n" + " \n" + " // A response is sent to the HTTP client.\n" + " // The child starting template function returns a result, with the error case\n" + " // being used when children fail to start. Because of this the `start_child`\n" + " // function also returns a result, so it must be handled too.\n" + " case start_result {\n" + " Ok(_) -> response.new(200)\n" + " Error(_) -> response.new(500)\n" + " }\n" + " }\n" + " ```\n" +). + +-opaque supervisor(EWK, EWL) :: {supervisor, gleam@erlang@process:pid_()} | + {named_supervisor, gleam@erlang@process:name(message(EWK, EWL))}. + +-type message(EWM, EWN) :: any() | {gleam_phantom, EWM, EWN}. + +-opaque builder(EWO, EWP) :: {builder, + gleam@otp@supervision:child_type(), + fun((EWO) -> {ok, gleam@otp@actor:started(EWP)} | + {error, gleam@otp@actor:start_error()}), + gleam@otp@supervision:restart(), + integer(), + integer(), + gleam@option:option(gleam@erlang@process:name(message(EWO, EWP)))}. + +-type erlang_start_flags() :: any(). + +-type erlang_supervisor_name(EWQ, EWR) :: {local, + gleam@erlang@process:name(message(EWQ, EWR))}. + +-type strategy() :: simple_one_for_one. + +-type erlang_start_flag(EWS) :: {strategy, strategy()} | + {intensity, integer()} | + {period, integer()} | + {gleam_phantom, EWS}. + +-type erlang_child_spec() :: any(). + +-type erlang_child_spec_property(EWT, EWU) :: {id, integer()} | + {start, + {gleam@erlang@atom:atom_(), + gleam@erlang@atom:atom_(), + list(fun((EWT) -> {ok, gleam@otp@actor:started(EWU)} | + {error, gleam@otp@actor:start_error()}))}} | + {restart, gleam@otp@supervision:restart()} | + {type, gleam@erlang@atom:atom_()} | + {shutdown, timeout_()}. + +-type timeout_() :: any(). + +-file("src/gleam/otp/factory_supervisor.gleam", 137). +?DOC( + " Get a reference to a supervisor using its registered name.\n" + "\n" + " If no supervisor has been started using this name then functions\n" + " using this reference will fail.\n" + "\n" + " # Panics\n" + "\n" + " Functions using the `Supervisor` reference returned by this function\n" + " will panic if there is no factory supervisor registered with the name\n" + " when they are called. Always make sure your supervisors are themselves\n" + " supervised.\n" +). +-spec get_by_name(gleam@erlang@process:name(message(EWV, EWW))) -> supervisor(EWV, EWW). +get_by_name(Name) -> + {named_supervisor, Name}. + +-file("src/gleam/otp/factory_supervisor.gleam", 206). +?DOC( + " Provide a name for the supervisor to be registered with when started,\n" + " enabling it be more easily contacted by other processes. This is useful for\n" + " enabling processes that can take over from an older one that has exited due\n" + " to a failure.\n" + "\n" + " If the name is already registered to another process then the factory\n" + " supervisor will fail to start.\n" +). +-spec named(builder(EXM, EXN), gleam@erlang@process:name(message(EXM, EXN))) -> builder(EXM, EXN). +named(Builder, Name) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + {some, Name}}. + +-file("src/gleam/otp/factory_supervisor.gleam", 224). +?DOC( + " To prevent a supervisor from getting into an infinite loop of child\n" + " process terminations and restarts, a maximum restart tolerance is\n" + " defined using two integer values specified with keys intensity and\n" + " period in the above map. Assuming the values MaxR for intensity and MaxT\n" + " for period, then, if more than MaxR restarts occur within MaxT seconds,\n" + " the supervisor terminates all child processes and then itself. The\n" + " termination reason for the supervisor itself in that case will be\n" + " shutdown. \n" + "\n" + " Intensity defaults to 2 and period defaults to 5.\n" +). +-spec restart_tolerance(builder(EXV, EXW), integer(), integer()) -> builder(EXV, EXW). +restart_tolerance(Builder, Intensity, Period) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + Intensity, + Period, + erlang:element(7, Builder)}. + +-file("src/gleam/otp/factory_supervisor.gleam", 239). +?DOC( + " Configure the amount of milliseconds a child has to shut down before\n" + " being brutal killed by the supervisor.\n" + "\n" + " If not set the default for a child is 5000ms.\n" + "\n" + " This will be ignored if the child is a supervisor itself.\n" +). +-spec timeout(builder(EYB, EYC), integer()) -> builder(EYB, EYC). +timeout(Builder, Ms) -> + case erlang:element(2, Builder) of + {worker, _} -> + {builder, + {worker, Ms}, + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder)}; + + _ -> + Builder + end. + +-file("src/gleam/otp/factory_supervisor.gleam", 256). +?DOC( + " Configure the strategy for restarting children when they exit. See the\n" + " documentation for the `supervision.Restart` for details.\n" + "\n" + " If not set the default strategy is `supervision.Transient`, so children\n" + " will be restarted if they terminate abnormally.\n" +). +-spec restart_strategy(builder(EYH, EYI), gleam@otp@supervision:restart()) -> builder(EYH, EYI). +restart_strategy(Builder, Restart_strategy) -> + case erlang:element(2, Builder) of + {worker, _} -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + Restart_strategy, + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder)}; + + _ -> + Builder + end. + +-file("src/gleam/otp/factory_supervisor.gleam", 275). +?DOC( + " Start a new supervisor process with the configuration and child template\n" + " specified within the builder.\n" + "\n" + " Typically you would use the `supervised` function to add your supervisor to\n" + " a supervision tree instead of using this function directly.\n" + "\n" + " The supervisor will be linked to the parent process that calls this\n" + " function.\n" +). +-spec start(builder(EYN, EYO)) -> {ok, + gleam@otp@actor:started(supervisor(EYN, EYO))} | + {error, gleam@otp@actor:start_error()}. +start(Builder) -> + Flags = maps:from_list( + [{strategy, simple_one_for_one}, + {intensity, erlang:element(5, Builder)}, + {period, erlang:element(6, Builder)}] + ), + Module_atom = erlang:binary_to_atom(<<"gleam@otp@factory_supervisor"/utf8>>), + Function_atom = erlang:binary_to_atom(<<"start_child_callback"/utf8>>), + Mfa = {Module_atom, Function_atom, [erlang:element(3, Builder)]}, + {Type_, Shutdown} = case erlang:element(2, Builder) of + supervisor -> + {erlang:binary_to_atom(<<"supervisor"/utf8>>), + gleam_otp_external:make_timeout(-1)}; + + {worker, Ms} -> + {erlang:binary_to_atom(<<"worker"/utf8>>), + gleam_otp_external:make_timeout(Ms)} + end, + Child = maps:from_list( + [{id, 0}, + {start, Mfa}, + {restart, erlang:element(4, Builder)}, + {type, Type_}, + {shutdown, Shutdown}] + ), + Configuration = {Flags, [Child]}, + Start_result = case erlang:element(7, Builder) of + none -> + supervisor:start_link(Module_atom, Configuration); + + {some, Name} -> + supervisor:start_link({local, Name}, Module_atom, Configuration) + end, + case Start_result of + {ok, Pid} -> + {ok, {started, Pid, {supervisor, Pid}}}; + + {error, Error} -> + {error, gleam_otp_external:convert_erlang_start_error(Error)} + end. + +-file("src/gleam/otp/factory_supervisor.gleam", 388). +?DOC( + " Create a `ChildSpecification` that adds this supervisor as the child of\n" + " another, making it fault tolerant and part of the application's supervision\n" + " tree. You should prefer to starting unsupervised supervisors with the\n" + " `start` function.\n" + "\n" + " If any child fails to start the supevisor first terminates all already\n" + " started child processes with reason shutdown and then terminate itself and\n" + " returns an error.\n" +). +-spec supervised(builder(EZM, EZN)) -> gleam@otp@supervision:child_specification(supervisor(EZM, EZN)). +supervised(Builder) -> + gleam@otp@supervision:supervisor(fun() -> start(Builder) end). + +-file("src/gleam/otp/factory_supervisor.gleam", 397). +?DOC( + " Start a new child using the supervisor's child template and the given\n" + " argument. The start result of the child is returned.\n" +). +-spec start_child(supervisor(EZT, EZU), EZT) -> {ok, + gleam@otp@actor:started(EZU)} | + {error, gleam@otp@actor:start_error()}. +start_child(Supervisor, Argument) -> + Start = case Supervisor of + {named_supervisor, Name} -> + fun(_capture) -> supervisor:start_child(Name, _capture) end; + + {supervisor, Pid} -> + fun(_capture@1) -> supervisor:start_child(Pid, _capture@1) end + end, + case Start([Argument]) of + {ok, Pid@1, Data} -> + {ok, {started, Pid@1, Data}}; + + {error, Reason} -> + {error, Reason} + end. + +-file("src/gleam/otp/factory_supervisor.gleam", 425). +?DOC(false). +-spec init(gleam@dynamic:dynamic_()) -> {ok, gleam@dynamic:dynamic_()} | + {error, any()}. +init(Start_data) -> + {ok, Start_data}. + +-file("src/gleam/otp/factory_supervisor.gleam", 431). +?DOC(false). +-spec start_child_callback( + fun((FAR) -> {ok, gleam@otp@actor:started(FAS)} | + {error, gleam@otp@actor:start_error()}), + FAR +) -> gleam@otp@internal@result2:result2(gleam@erlang@process:pid_(), FAS, gleam@otp@actor:start_error()). +start_child_callback(Start, Argument) -> + case Start(Argument) of + {ok, Started} -> + {ok, erlang:element(2, Started), erlang:element(3, Started)}; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/otp/factory_supervisor.gleam", 164). +?DOC( + " Configure a supervisor with a child-starting template function.\n" + "\n" + " You should use this unless the child processes are also supervisors.\n" + "\n" + " The default shutdown timeout is 5000ms. This can be changed with the\n" + " `timeout` function.\n" +). +-spec worker_child( + fun((EXC) -> {ok, gleam@otp@actor:started(EXD)} | + {error, gleam@otp@actor:start_error()}) +) -> builder(EXC, EXD). +worker_child(Template) -> + {builder, {worker, 5000}, Template, transient, 2, 5, none}. + +-file("src/gleam/otp/factory_supervisor.gleam", 185). +?DOC( + " Configure a supervisor with a template that will start children that are\n" + " also supervisors.\n" + "\n" + " You should only use this if the child processes are also supervisors.\n" + "\n" + " Supervisor children have an unlimited amount of time to shutdown, there is\n" + " no timeout.\n" +). +-spec supervisor_child( + fun((EXH) -> {ok, gleam@otp@actor:started(EXI)} | + {error, gleam@otp@actor:start_error()}) +) -> builder(EXH, EXI). +supervisor_child(Template) -> + {builder, supervisor, Template, transient, 2, 5, none}. diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@internal@result2.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@internal@result2.erl new file mode 100644 index 0000000..5a5455c --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@internal@result2.erl @@ -0,0 +1,18 @@ +-module(gleam@otp@internal@result2). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/internal/result2.gleam"). +-export_type([result2/3]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type result2(ETS, ETT, ETU) :: {ok, ETS, ETT} | {error, ETU}. + + diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@port.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@port.erl new file mode 100644 index 0000000..772a2aa --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@port.erl @@ -0,0 +1,8 @@ +-module(gleam@otp@port). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/port.gleam"). +-export_type([port_/0]). + +-type port_() :: any(). + + diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@static_supervisor.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@static_supervisor.erl new file mode 100644 index 0000000..b3a5897 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@static_supervisor.erl @@ -0,0 +1,226 @@ +-module(gleam@otp@static_supervisor). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/static_supervisor.gleam"). +-export([new/1, restart_tolerance/3, auto_shutdown/2, add/2, start/1, supervised/1, init/1, start_child_callback/1]). +-export_type([supervisor/0, strategy/0, auto_shutdown/0, builder/0, erlang_start_flags/0, erlang_start_flag/1, erlang_child_spec/0, erlang_child_spec_property/1, timeout_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A supervisor where the number and types of the children are specified\n" + " once, and the supervisor manages them using a configured restart strategy.\n" + "\n" + " For further detail see the Erlang documentation:\n" + " .\n" + "\n" + " # Example\n" + "\n" + " ```gleam\n" + " import gleam/otp/actor\n" + " import gleam/otp/static_supervisor.{type Supervisor} as supervisor\n" + " import app/database_pool\n" + " import app/http_server\n" + " \n" + " pub fn start_supervisor() -> actor.StartResult(Supervisor) {\n" + " supervisor.new(supervisor.OneForOne)\n" + " |> supervisor.add(database_pool.supervised())\n" + " |> supervisor.add(http_server.supervised())\n" + " |> supervisor.start\n" + " }\n" + " ```\n" +). + +-opaque supervisor() :: {supervisor, gleam@erlang@process:pid_()}. + +-type strategy() :: one_for_one | one_for_all | rest_for_one. + +-type auto_shutdown() :: never | any_significant | all_significant. + +-opaque builder() :: {builder, + strategy(), + integer(), + integer(), + auto_shutdown(), + list(gleam@otp@supervision:child_specification(nil))}. + +-type erlang_start_flags() :: any(). + +-type erlang_start_flag(FHY) :: {strategy, strategy()} | + {intensity, integer()} | + {period, integer()} | + {auto_shutdown, auto_shutdown()} | + {gleam_phantom, FHY}. + +-type erlang_child_spec() :: any(). + +-type erlang_child_spec_property(FHZ) :: {id, integer()} | + {start, + {gleam@erlang@atom:atom_(), + gleam@erlang@atom:atom_(), + list(fun(() -> {ok, gleam@otp@actor:started(FHZ)} | + {error, gleam@otp@actor:start_error()}))}} | + {restart, gleam@otp@supervision:restart()} | + {significant, boolean()} | + {type, gleam@erlang@atom:atom_()} | + {shutdown, timeout_()}. + +-type timeout_() :: any(). + +-file("src/gleam/otp/static_supervisor.gleam", 110). +?DOC(" Create a new supervisor builder, ready for further configuration.\n"). +-spec new(strategy()) -> builder(). +new(Strategy) -> + {builder, Strategy, 2, 5, never, []}. + +-file("src/gleam/otp/static_supervisor.gleam", 131). +?DOC( + " To prevent a supervisor from getting into an infinite loop of child\n" + " process terminations and restarts, a maximum restart tolerance is\n" + " defined using two integer values specified with keys intensity and\n" + " period in the above map. Assuming the values MaxR for intensity and MaxT\n" + " for period, then, if more than MaxR restarts occur within MaxT seconds,\n" + " the supervisor terminates all child processes and then itself. The\n" + " termination reason for the supervisor itself in that case will be\n" + " shutdown. \n" + "\n" + " Intensity defaults to 2 and period defaults to 5.\n" +). +-spec restart_tolerance(builder(), integer(), integer()) -> builder(). +restart_tolerance(Builder, Intensity, Period) -> + {builder, + erlang:element(2, Builder), + Intensity, + Period, + erlang:element(5, Builder), + erlang:element(6, Builder)}. + +-file("src/gleam/otp/static_supervisor.gleam", 142). +?DOC( + " A supervisor can be configured to automatically shut itself down with\n" + " exit reason shutdown when significant children terminate.\n" +). +-spec auto_shutdown(builder(), auto_shutdown()) -> builder(). +auto_shutdown(Builder, Value) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + Value, + erlang:element(6, Builder)}. + +-file("src/gleam/otp/static_supervisor.gleam", 202). +?DOC(" Add a child to the supervisor.\n"). +-spec add(builder(), gleam@otp@supervision:child_specification(any())) -> builder(). +add(Builder, Child) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + [gleam@otp@supervision:map_data(Child, fun(_) -> nil end) | + erlang:element(6, Builder)]}. + +-file("src/gleam/otp/static_supervisor.gleam", 209). +-spec convert_child(gleam@otp@supervision:child_specification(any()), integer()) -> erlang_child_spec(). +convert_child(Child, Id) -> + Mfa = {erlang:binary_to_atom(<<"gleam@otp@static_supervisor"/utf8>>), + erlang:binary_to_atom(<<"start_child_callback"/utf8>>), + [erlang:element(2, Child)]}, + {Type_, Shutdown} = case erlang:element(5, Child) of + supervisor -> + {erlang:binary_to_atom(<<"supervisor"/utf8>>), + gleam_otp_external:make_timeout(-1)}; + + {worker, Ms} -> + {erlang:binary_to_atom(<<"worker"/utf8>>), + gleam_otp_external:make_timeout(Ms)} + end, + maps:from_list( + [{id, Id}, + {start, Mfa}, + {restart, erlang:element(3, Child)}, + {significant, erlang:element(4, Child)}, + {type, Type_}, + {shutdown, Shutdown}] + ). + +-file("src/gleam/otp/static_supervisor.gleam", 159). +?DOC( + " Start a new supervisor process with the configuration and children\n" + " specified within the builder.\n" + "\n" + " Typically you would use the `supervised` function to add your supervisor to\n" + " a supervision tree instead of using this function directly.\n" + "\n" + " The supervisor will be linked to the parent process that calls this\n" + " function.\n" + "\n" + " If any child fails to start the supevisor first terminates all already\n" + " started child processes with reason shutdown and then terminate itself and\n" + " returns an error.\n" +). +-spec start(builder()) -> {ok, gleam@otp@actor:started(supervisor())} | + {error, gleam@otp@actor:start_error()}. +start(Builder) -> + Flags = maps:from_list( + [{strategy, erlang:element(2, Builder)}, + {intensity, erlang:element(3, Builder)}, + {period, erlang:element(4, Builder)}, + {auto_shutdown, erlang:element(5, Builder)}] + ), + Module = erlang:binary_to_atom(<<"gleam@otp@static_supervisor"/utf8>>), + Children = begin + _pipe = erlang:element(6, Builder), + _pipe@1 = lists:reverse(_pipe), + gleam@list:index_map(_pipe@1, fun convert_child/2) + end, + case supervisor:start_link(Module, {Flags, Children}) of + {ok, Pid} -> + {ok, {started, Pid, {supervisor, Pid}}}; + + {error, Error} -> + {error, gleam_otp_external:convert_erlang_start_error(Error)} + end. + +-file("src/gleam/otp/static_supervisor.gleam", 188). +?DOC( + " Create a `ChildSpecification` that adds this supervisor as the child of\n" + " another, making it fault tolerant and part of the application's supervision\n" + " tree. You should prefer to starting unsupervised supervisors with the\n" + " `start` function.\n" + "\n" + " If any child fails to start the supevisor first terminates all already\n" + " started child processes with reason shutdown and then terminate itself and\n" + " returns an error.\n" +). +-spec supervised(builder()) -> gleam@otp@supervision:child_specification(supervisor()). +supervised(Builder) -> + gleam@otp@supervision:supervisor(fun() -> start(Builder) end). + +-file("src/gleam/otp/static_supervisor.gleam", 271). +?DOC(false). +-spec init(gleam@dynamic:dynamic_()) -> {ok, gleam@dynamic:dynamic_()} | + {error, any()}. +init(Start_data) -> + {ok, Start_data}. + +-file("src/gleam/otp/static_supervisor.gleam", 277). +?DOC(false). +-spec start_child_callback( + fun(() -> {ok, gleam@otp@actor:started(any())} | + {error, gleam@otp@actor:start_error()}) +) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@otp@actor:start_error()}. +start_child_callback(Start) -> + case Start() of + {ok, Started} -> + {ok, erlang:element(2, Started)}; + + {error, Error} -> + {error, Error} + end. diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@supervision.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@supervision.erl new file mode 100644 index 0000000..3bfd229 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@supervision.erl @@ -0,0 +1,129 @@ +-module(gleam@otp@supervision). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/supervision.gleam"). +-export([worker/1, supervisor/1, significant/2, timeout/2, restart/2, map_data/2]). +-export_type([restart/0, child_type/0, child_specification/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type restart() :: permanent | transient | temporary. + +-type child_type() :: {worker, integer()} | supervisor. + +-type child_specification(ETW) :: {child_specification, + fun(() -> {ok, gleam@otp@actor:started(ETW)} | + {error, gleam@otp@actor:start_error()}), + restart(), + boolean(), + child_type()}. + +-file("src/gleam/otp/supervision.gleam", 61). +?DOC( + " A regular child process.\n" + "\n" + " You should use this unless your process is also a supervisor.\n" + "\n" + " The default shutdown timeout is 5000ms. This can be changed with the\n" + " `timeout` function.\n" +). +-spec worker( + fun(() -> {ok, gleam@otp@actor:started(ETX)} | + {error, gleam@otp@actor:start_error()}) +) -> child_specification(ETX). +worker(Start) -> + {child_specification, Start, permanent, false, {worker, 5000}}. + +-file("src/gleam/otp/supervision.gleam", 76). +?DOC( + " A special child that is a supervisor itself.\n" + "\n" + " Supervisor children have an unlimited shutdown time, there is no timeout.\n" +). +-spec supervisor( + fun(() -> {ok, gleam@otp@actor:started(EUC)} | + {error, gleam@otp@actor:start_error()}) +) -> child_specification(EUC). +supervisor(Start) -> + {child_specification, Start, permanent, false, supervisor}. + +-file("src/gleam/otp/supervision.gleam", 96). +?DOC( + " This defines if a child is considered significant for automatic\n" + " self-shutdown of the supervisor.\n" + "\n" + " You most likely do not want to consider any children significant.\n" + "\n" + " This will be ignored if the supervisor auto shutdown is set to `Never`,\n" + " which is the default.\n" + "\n" + " The default value for significance is `False`.\n" +). +-spec significant(child_specification(EUH), boolean()) -> child_specification(EUH). +significant(Child, Significant) -> + {child_specification, + erlang:element(2, Child), + erlang:element(3, Child), + Significant, + erlang:element(5, Child)}. + +-file("src/gleam/otp/supervision.gleam", 110). +?DOC( + " This defines the amount of milliseconds a child has to shut down before\n" + " being brutal killed by the supervisor.\n" + "\n" + " If not set the default for a child is 5000ms.\n" + "\n" + " This will be ignored if the child is a supervisor itself.\n" +). +-spec timeout(child_specification(EUK), integer()) -> child_specification(EUK). +timeout(Child, Ms) -> + case erlang:element(5, Child) of + {worker, _} -> + {child_specification, + erlang:element(2, Child), + erlang:element(3, Child), + erlang:element(4, Child), + {worker, Ms}}; + + _ -> + Child + end. + +-file("src/gleam/otp/supervision.gleam", 124). +?DOC( + " When the child is to be restarted. See the `Restart` documentation for\n" + " more.\n" + "\n" + " The default value for restart is `Permanent`.\n" +). +-spec restart(child_specification(EUN), restart()) -> child_specification(EUN). +restart(Child, Restart) -> + {child_specification, + erlang:element(2, Child), + Restart, + erlang:element(4, Child), + erlang:element(5, Child)}. + +-file("src/gleam/otp/supervision.gleam", 133). +?DOC(" Transform the data of the started child process.\n"). +-spec map_data(child_specification(EUQ), fun((EUQ) -> EUS)) -> child_specification(EUS). +map_data(Child, Transform) -> + {child_specification, fun() -> case (erlang:element(2, Child))() of + {ok, Started} -> + {ok, + {started, + erlang:element(2, Started), + Transform(erlang:element(3, Started))}}; + + {error, E} -> + {error, E} + end end, erlang:element(3, Child), erlang:element(4, Child), erlang:element( + 5, + Child + )}. diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@system.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@system.erl new file mode 100644 index 0000000..c991970 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam@otp@system.erl @@ -0,0 +1,83 @@ +-module(gleam@otp@system). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/otp/system.gleam"). +-export([debug_state/1, get_state/1, suspend/1, resume/1]). +-export_type([mode/0, debug_option/0, debug_state/0, status_info/0, system_message/0, do_not_leak/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type mode() :: running | suspended. + +-type debug_option() :: no_debug. + +-type debug_state() :: any(). + +-type status_info() :: {status_info, + gleam@erlang@atom:atom_(), + gleam@erlang@process:pid_(), + mode(), + debug_state(), + gleam@dynamic:dynamic_()}. + +-type system_message() :: {resume, fun(() -> nil)} | + {suspend, fun(() -> nil)} | + {get_state, fun((gleam@dynamic:dynamic_()) -> nil)} | + {get_status, fun((status_info()) -> nil)}. + +-type do_not_leak() :: any(). + +-file("src/gleam/otp/system.gleam", 19). +-spec debug_state(list(debug_option())) -> debug_state(). +debug_state(A) -> + sys:debug_options(A). + +-file("src/gleam/otp/system.gleam", 65). +?DOC( + " Get the state of a given OTP compatible process. This function is only\n" + " intended for debugging.\n" + "\n" + " Requires Erlang/OTP 26.1 or newer, as the underlying interface changed\n" + " in [OTP-18633][1] from a literal type to a result type.\n" + "\n" + " For more information see the [Erlang documentation][2].\n" + "\n" + " [1]: https://www.erlang.org/patches/otp-26.1#stdlib-5.1\n" + " [2]: https://erlang.org/doc/man/sys.html#get_state-1\n" +). +-spec get_state(gleam@erlang@process:pid_()) -> gleam@dynamic:dynamic_(). +get_state(From) -> + sys:get_state(From). + +-file("src/gleam/otp/system.gleam", 77). +?DOC( + " Request an OTP compatible process to suspend, causing it to only handle\n" + " system messages.\n" + "\n" + " For more information see the [Erlang documentation][1].\n" + "\n" + " [1]: https://erlang.org/doc/man/sys.html#suspend-1\n" +). +-spec suspend(gleam@erlang@process:pid_()) -> nil. +suspend(Pid) -> + sys:suspend(Pid), + nil. + +-file("src/gleam/otp/system.gleam", 92). +?DOC( + " Request a suspended OTP compatible process to result, causing it to handle\n" + " all messages rather than only system messages.\n" + "\n" + " For more information see the [Erlang documentation][1].\n" + "\n" + " [1]: https://erlang.org/doc/man/sys.html#resume-1\n" +). +-spec resume(gleam@erlang@process:pid_()) -> nil. +resume(Pid) -> + sys:resume(Pid), + nil. diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp.app.src b/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp.app.src new file mode 100644 index 0000000..29eb2d4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp.app.src @@ -0,0 +1,17 @@ +{application, gleam_otp, [ + {vsn, "1.2.0"}, + {applications, [gleam_erlang, + gleam_stdlib]}, + {description, "Fault tolerant multi-core programs with OTP, the BEAM actor framework."}, + {modules, [gleam@otp@actor, + gleam@otp@factory_supervisor, + gleam@otp@internal@result2, + gleam@otp@port, + gleam@otp@static_supervisor, + gleam@otp@supervision, + gleam@otp@system, + gleam_otp@@main, + gleam_otp_external, + gleam_otp_test_external]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp_external.erl b/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp_external.erl new file mode 100644 index 0000000..6caa0f2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_otp/src/gleam_otp_external.erl @@ -0,0 +1,69 @@ +-module(gleam_otp_external). + +-export([ + application_stopped/0, convert_system_message/1, identity/1, + make_timeout/1, convert_erlang_start_error/1 +]). + +identity(X) -> X. + +make_timeout(X) when X < 0 -> infinity; +make_timeout(X) -> X. + +% TODO: support other system messages +% {replace_state, StateFn} +% {change_code, Mod, Vsn, Extra} +% {terminate, Reason} +% {debug, {log, Flag}} +% {debug, {trace, Flag}} +% {debug, {log_to_file, FileName}} +% {debug, {statistics, Flag}} +% {debug, no_debug} +% {debug, {install, {Func, FuncState}}} +% {debug, {install, {FuncId, Func, FuncState}}} +% {debug, {remove, FuncOrId}} +convert_system_message({system, {From, Ref}, Request}) when is_pid(From) -> + Reply = fun(Msg) -> + case Ref of + [alias|Alias] = Tag when is_reference(Alias) -> + erlang:send(Alias, {Tag, Msg}); + [[alias|Alias] | _] = Tag when is_reference(Alias) -> + erlang:send(Alias, {Tag, Msg}); + _ -> + erlang:send(From, {Ref, Msg}) + end, + nil + end, + System = fun(Callback) -> + {system, {Request, Callback}} + end, + case Request of + get_status -> System(fun(Status) -> Reply(process_status(Status)) end); + get_state -> System(fun(State) -> Reply({ok, State}) end); + suspend -> System(fun() -> Reply(ok) end); + resume -> System(fun() -> Reply(ok) end); + Other -> {unexpected, Other} + end. + +process_status({status_info, Module, Parent, Mode, DebugState, State}) -> + Data = [ + get(), Mode, Parent, DebugState, + [{header, "Status for Gleam process " ++ pid_to_list(self())}, + {data, [ + {"Gleam behaviour", Module}, + {"Status", Mode}, + {"Parent", Parent}]}, + {data, [{"State", State}]} + ] + ], + {status, self(), {module, Module}, Data}. + +application_stopped() -> + ok. + +convert_erlang_start_error({already_started, _}) -> + {init_failed, <<"already started">>}; +convert_erlang_start_error({shutdown, _}) -> + {init_failed, <<"shutdown">>}; +convert_erlang_start_error(Term) -> + {init_exited, {abnormal, Term}}. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/LICENCE b/examples/http_interactions/build/packages/gleam_stdlib/LICENCE new file mode 100644 index 0000000..c1dabd0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2018, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleam_stdlib/README.md b/examples/http_interactions/build/packages/gleam_stdlib/README.md new file mode 100644 index 0000000..2951442 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/README.md @@ -0,0 +1,34 @@ +# stdlib + +[![Package Version](https://img.shields.io/hexpm/v/gleam_stdlib)](https://hex.pm/packages/gleam_stdlib) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_stdlib/) +[![Discord chat](https://img.shields.io/discord/768594524158427167?color=blue)](https://discord.gg/Fm8Pwmy) + +Gleam's standard library! +Documentation available on [HexDocs](https://hexdocs.pm/gleam_stdlib/). + +## Installation + +Add `gleam_stdlib` to your Gleam project. + +```sh +gleam add gleam_stdlib@1 +``` +```gleam +import gleam/io + +pub fn greet(name: String) -> Nil { + io.println("Hello " <> name <> "!") +} +``` + +## Targets + +Gleam's standard library supports both targets: Erlang and JavaScript. + +### Compatibility + +This library is compatible with all versions of Erlang/OTP 26 and higher, +as well as all NodeJS, Deno, Bun, and major browsers that are currently +supported by their maintainers. If you have a compatibility issue with +any platform open an issue and we'll see what we can do to help. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/gleam.toml b/examples/http_interactions/build/packages/gleam_stdlib/gleam.toml new file mode 100644 index 0000000..b641806 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/gleam.toml @@ -0,0 +1,14 @@ +name = "gleam_stdlib" +version = "1.0.0" +gleam = ">= 1.14.0" +licences = ["Apache-2.0"] +description = "A standard library for the Gleam programming language" + +repository = { type = "github", user = "gleam-lang", repo = "stdlib" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[javascript.deno] +allow_read = ["./"] diff --git a/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl new file mode 100644 index 0000000..b1135f2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_DecodeError.hrl @@ -0,0 +1,5 @@ +-record(decode_error, { + expected :: binary(), + found :: binary(), + path :: list(binary()) +}). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl new file mode 100644 index 0000000..a2fcfc6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@dynamic@decode_Decoder.hrl @@ -0,0 +1,4 @@ +-record(decoder, { + function :: fun((gleam@dynamic:dynamic_()) -> {any(), + list(gleam@dynamic@decode:decode_error())}) +}). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@set_Set.hrl b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@set_Set.hrl new file mode 100644 index 0000000..51fb778 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@set_Set.hrl @@ -0,0 +1 @@ +-record(set, {dict :: gleam@dict:dict(any(), list(nil))}). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl new file mode 100644 index 0000000..50150f4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl @@ -0,0 +1,9 @@ +-record(uri, { + scheme :: gleam@option:option(binary()), + userinfo :: gleam@option:option(binary()), + host :: gleam@option:option(binary()), + port :: gleam@option:option(integer()), + path :: binary(), + 'query' :: gleam@option:option(binary()), + fragment :: gleam@option:option(binary()) +}). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/dict.mjs b/examples/http_interactions/build/packages/gleam_stdlib/src/dict.mjs new file mode 100644 index 0000000..3db3b5f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/dict.mjs @@ -0,0 +1,710 @@ +/** + * This file uses jsdoc to annotate types. + * These types can be checked using the typescript compiler with "checkjs" option. + */ + +import { isEqual, Result$Error, Result$Ok } from "./gleam.mjs"; + +// -- HASH -------------------------------------------------------------------- + +const referenceMap = /* @__PURE__ */ new WeakMap(); +const tempDataView = /* @__PURE__ */ new DataView( + /* @__PURE__ */ new ArrayBuffer(8), +); +let referenceUID = 0; +/** + * hash the object by reference using a weak map and incrementing uid + * @param {any} o + * @returns {number} + */ +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== undefined) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 0x7fffffff) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} + +/** + * merge two hashes in an order sensitive way + * @param {number} a + * @param {number} b + * @returns {number} + */ +function hashMerge(a, b) { + return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0; +} + +/** + * standard string hash popularised by Java + * @param {string} s + * @returns {number} + */ +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0; i < len; i++) { + hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0; + } + return hash; +} + +/** + * hash a number by converting to two integers and do some jumbling + * @param {number} n + * @returns {number} + */ +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j; +} + +/** + * hash a BigInt by converting it to a string and hashing that + * @param {BigInt} n + * @returns {number} + */ +function hashBigInt(n) { + return hashString(n.toString()); +} + +/** + * hash any js object + * @param {any} o + * @returns {number} + */ +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code = o.hashCode(o); + if (typeof code === "number") { + return code; + } + } catch {} + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0; i < o.length; i++) { + h = (Math.imul(31, h) + getHash(o[i])) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = (h + getHash(v)) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = (h + hashMerge(getHash(v), getHash(k))) | 0; + }); + } else { + const keys = Object.keys(o); + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + const v = o[k]; + h = (h + hashMerge(getHash(v), hashString(k))) | 0; + } + } + return h; +} + +/** + * hash any js value + * @param {any} u + * @returns {number} + */ +export function getHash(u) { + if (u === null) return 0x42108422; + if (u === undefined) return 0x42108423; + if (u === true) return 0x42108421; + if (u === false) return 0x42108420; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; // should be unreachable + } +} + +// -- DICT -------------------------------------------------------------------- + +/** + * An implementation of the CHAMP data structure, an optimised HAMT. + * + * See: M. J. Steindorfer, J.J. Vinju (2015). Optimizing Hash-Array Mapped Tries for + Fast and Lean Immutable JVM Collections. Available: https://michael.steindorfer.name/publications/oopsla15.pdf + */ +export default class Dict { + constructor(size, root) { + this.size = size; + this.root = root; + } +} + +/// The power-of-2 branching factor for the dict. For example, a value of `5` indicates a 32-ary tree. +const bits = 5; +const mask = (1 << bits) - 1; + +/// This symbol is used internally to avoid constructing results. +const noElementMarker = Symbol(); + +/// This symbol is used to store the "generation" on a node. +/// Using a symbol makes the property not enumerable, which means the generation +/// will be ignored during equality checks. +const generationKey = Symbol(); + +// Some commonly used constants throughout the code. +const emptyNode = /* @__PURE__ */ newNode(0); +const emptyDict = /* @__PURE__ */ new Dict(0, emptyNode); +const errorNil = /* @__PURE__ */ Result$Error(undefined); + +function makeNode(generation, datamap, nodemap, data) { + // The order of fields is important, as they define the order `isEqual` will + // compare our fields. Putting the bitmaps first means that equality can + // early-out if the bitmaps are not equal. + return { + // A node is a high-arity (32 in practice) hybrid tree node. + // Hybrid means that it stores data directly as well as pointers to child nodes. + // + // Each node contains 2 bitmaps: + // - The datamap has a bit set if that slot in the node contains direct data + // - The nodemap has a bit set if that slot in the node contains another node. + // + // Both are exclusive to on another, so datamap & nodemap == 0. + // + // Every key/hash value directly correlates to a specific bit by using a trie + // suffix (least significant bits first) encoding. + // For example, if the last 5 bits of the hash are 1101, the bit to check for + // that value is the 13th bit. + datamap, + nodemap, + // The slots itself are stored in a single contiguous array that contains + // both direct k/v-pairs and child nodes. + // + // The direct children come first, followed by the child nodes in _reverse order_: + // + // 7654321 + // datamap: 1000100 + // nodemap: 10011 + // data: [key3, value3, key7, value7, child5, child2, child1] + // -------------------------> <--------------------- + // datamap nodemap + // + // Every `1` bit in the datamap corresponds to a pair of [key, value] entries, + // and every `1` bit in the nodemap corresponds to a child node entry. + // + // Children are stored in reverse order to avoid having to store or calculate an + // "offset" value to skip over the direct children. + data, + // The generation is used to track which nodes need to be copied during transient updates. + // Using a symbol here makes `isEqual` ignore this field. + [generationKey]: generation, + }; +} + +function newNode(generation) { + return makeNode(generation, 0, 0, []); +} + +/** + * Copies a node and its data array if it's from another generation, making it safe + * to mutate the node. + */ +function copyNode(node, generation) { + if (node[generationKey] === generation) { + return node; + } + + const newData = node.data.slice(0); + return makeNode(generation, node.datamap, node.nodemap, newData); +} + +/** + * Copies a node if needed and sets a new value. + */ +function copyAndSet(node, generation, idx, val) { + if (node.data[idx] === val) { + return node; + } + + // Using copyNode is faster than a specialised implementation. + node = copyNode(node, generation); + node.data[idx] = val; + return node; +} + +/** + * Copies a node if needed, and then inserts a new key-value pair. + */ +function copyAndInsertPair(node, generation, bit, idx, key, val) { + const data = node.data; + const length = data.length; + + // the fastest way to insert a pair is to always copy. + const newData = new Array(length + 2); + + let readIndex = 0; + let writeIndex = 0; + + while (readIndex < idx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = key; + newData[writeIndex++] = val; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap | bit, node.nodemap, newData); +} + +function copyAndRemovePair(node, generation, bit, idx) { + node = copyNode(node, generation); + + const data = node.data; + const length = data.length; + for (let w = idx, r = idx + 2; r < length; ++r, ++w) { + data[w] = data[r]; + } + data.pop(); + data.pop(); + + node.datamap ^= bit; + return node; +} + +export function make() { + return emptyDict; +} + +export function from(iterable) { + let transient = toTransient(emptyDict); + for (const [key, value] of iterable) { + transient = destructiveTransientInsert(key, value, transient); + } + return fromTransient(transient); +} + +export function size(dict) { + return dict.size; +} + +export function get(dict, key) { + const result = lookup(dict.root, key, getHash(key)); + return result !== noElementMarker ? Result$Ok(result) : errorNil; +} + +export function has(dict, key) { + return lookup(dict.root, key, getHash(key)) !== noElementMarker; +} + +function lookup(node, key, hash) { + for (let shift = 0; shift < 32; shift += bits) { + const data = node.data; + const bit = hashbit(hash, shift); + + if (node.nodemap & bit) { + // we found our hash inside the nodemap, so we can continue our search there. + node = data[data.length - 1 - index(node.nodemap, bit)]; + } else if (node.datamap & bit) { + // we store this hash directly! + // + // this also means that there are no other values with the same + // hash prefix in this dict. + // + // We still need to check if the key matches, but if it does we know for + // sure that this is the correct value, and if it doesn't that we don't + // contain the value in question. + const dataidx = Math.imul(index(node.datamap, bit), 2); + return isEqual(key, data[dataidx]) ? data[dataidx + 1] : noElementMarker; + } else { + // if the hash bit is not set in neither bitmaps, we immediately know that + // this key cannot be inside this dict. + return noElementMarker; + } + } + + // our shift has exceeded 32 bits. Everything that follows is + // implicitely an overflow node and only contains direct children. + const overflow = node.data; + for (let i = 0; i < overflow.length; i += 2) { + if (isEqual(key, overflow[i])) { + return overflow[i + 1]; + } + } + + return noElementMarker; +} + +/** + * We use "transient" values to allow for safer internal mutations of the data + * structure. This is an optimisation only. No mutable API is exposed to the user. + * + * Transients are to be treated as having a linear (single-use, think rust) type. + * A transient value becomes invalid as soon as it's passed to one of the functions. + * + * Internally, we track a "generation" value on each node. If the generation + * doesn't match the one for the current transient, we have to copy - the node + * could still be referenced by another dict instance! + * After that, no other references than the transient one exists, so it's safe + * to mutate in place. + */ +export function toTransient(dict) { + return { + generation: nextGeneration(dict), + root: dict.root, + size: dict.size, + dict: dict, + }; +} + +/** + * Consume a transient, producing a normal Dict again. + */ +export function fromTransient(transient) { + if (transient.root === transient.dict.root) { + return transient.dict; + } + + return new Dict(transient.size, transient.root); +} + +/** + * Find and allocate the next generation id. + * + * @template K,V + * @param {Dict} dict + * @returns {number} + */ +function nextGeneration(dict) { + const root = dict.root; + if (root[generationKey] < Number.MAX_SAFE_INTEGER) { + return root[generationKey] + 1; + } + + // we have reached MAX_SAFE_INTEGER generations - + // at this point, we have to walk the dictionary once to reset the counter + // on every node. This is safe since it's part of the contract for transient + // that only one of them exists at any given time. + // + const queue = [root]; + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + + // reset the generation to 0 + node[generationKey] = 0; + + // queue all other referenced nodes + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const nodeStart = data.length - popcount(node.nodemap); + for (let i = nodeStart; i < node.data.length; ++i) { + queue.push(node.data[i]); + } + } + + return 1; +} + +/// Insert is the second-most performance-sensitive operation. +/// We use a global "transient" value here to avoid doing a memory allocation. +const globalTransient = /* @__PURE__ */ toTransient(emptyDict); + +export function insert(dict, key, value) { + globalTransient.generation = nextGeneration(dict); + globalTransient.size = dict.size; + + const hash = getHash(key); + const root = insertIntoNode(globalTransient, dict.root, key, value, hash, 0); + if (root === dict.root) { + return dict; + } + + return new Dict(globalTransient.size, root); +} + +/** + * Consume a transient, writing a new key/value pair into the dictionary it + * represents. If the key already exists, it will be overwritten. + * + * Returns a new transient. + */ +export function destructiveTransientInsert(key, value, transient) { + const hash = getHash(key); + transient.root = insertIntoNode(transient, transient.root, key, value, hash, 0); + return transient; +} + +/** + * Consume a transient, writing a new key/value pair if the key doesn't exist or updating + * the existing value with a function if it does. + * + * Returns a new transient. + */ +export function destructiveTransientUpdateWith(key, fun, value, transient) { + const hash = getHash(key); + + const existing = lookup(transient.root, key, hash); + if (existing !== noElementMarker) { + value = fun(existing); + } + transient.root = insertIntoNode(transient, transient.root, key, value, hash, 0); + return transient; +} + +function insertIntoNode(transient, node, key, value, hash, shift) { + const data = node.data; + const generation = transient.generation; + + // 1. Overflow Node + // overflow nodes only contain key/value-pairs. we walk the data linearly trying to find a match. + if (shift > 32) { + for (let i = 0; i < data.length; i += 2) { + if (isEqual(key, data[i])) { + return copyAndSet(node, generation, i + 1, value); + } + } + + transient.size += 1; + return copyAndInsertPair(node, generation, 0, data.length, key, value); + } + + const bit = hashbit(hash, shift); + + // 2. Child Node + // We have to check first if there is already a child node we have to traverse to. + if (node.nodemap & bit) { + const nodeidx = data.length - 1 - index(node.nodemap, bit); + + let child = data[nodeidx]; + child = insertIntoNode(transient, child, key, value, hash, shift + bits); + return copyAndSet(node, generation, nodeidx, child); + } + + // 3. New Data Node + // No child node and no data node exists yet, so we can potentially just insert a new value. + const dataidx = Math.imul(index(node.datamap, bit), 2); + if ((node.datamap & bit) === 0) { + transient.size += 1; + return copyAndInsertPair(node, generation, bit, dataidx, key, value); + } + + // 4. Existing Data Node + // We have a match that we can update, or remove. + if (isEqual(key, data[dataidx])) { + return copyAndSet(node, generation, dataidx + 1, value); + } + + // 5. Collision + // There is no child node, but a data node with the same hash, but with a different key. + // To resolve this, we push both nodes down one level. + const childShift = shift + bits; + + let child = emptyNode; + child = insertIntoNode(transient, child, key, value, hash, childShift); + + const key2 = data[dataidx]; + const value2 = data[dataidx + 1]; + const hash2 = getHash(key2); + child = insertIntoNode(transient, child, key2, value2, hash2, childShift); + + // we inserted 2 elements, but implicitely deleted the one we pushed down from the datamap. + transient.size -= 1; + + // remove the old data pair, and insert the new child node. + const length = data.length; + const nodeidx = length - 1 - index(node.nodemap, bit); + + // writing these loops in javascript instead of a combination of splices + // turns out to be faster. Copying always turned out to be faster. + const newData = new Array(length - 1); + + let readIndex = 0; + let writeIndex = 0; + + // [0..dataidx, skip 2 elements, ..nodeidx, newChild, ..rest] + while (readIndex < dataidx) newData[writeIndex++] = data[readIndex++]; + readIndex += 2; + while (readIndex <= nodeidx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = child; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap ^ bit, node.nodemap | bit, newData); +} + +/** + * Consume a transient, removing a key if it exists. + * Returns a new transient. + */ +export function destructiveTransientDelete(key, transient) { + const hash = getHash(key); + transient.root = deleteFromNode(transient, transient.root, key, hash, 0); + return transient; +} + +function deleteFromNode(transient, node, key, hash, shift) { + const data = node.data; + const generation = transient.generation; + + // 1. Overflow Node + // overflow nodes only contain key/value-pairs. we walk the data linearly trying to find a match. + if (shift > 32) { + for (let i = 0; i < data.length; i += 2) { + if (isEqual(key, data[i])) { + transient.size -= 1; + return copyAndRemovePair(node, generation, 0, i); + } + } + + return node; + } + + const bit = hashbit(hash, shift); + const dataidx = Math.imul(index(node.datamap, bit), 2); + + // 2. Child Node + // We have to check first if there is already a child node we have to traverse to. + if ((node.nodemap & bit) !== 0) { + const nodeidx = data.length - 1 - index(node.nodemap, bit); + + let child = data[nodeidx]; + child = deleteFromNode(transient, child, key, hash, shift + bits); + + // the node did change, so let's copy to incorporate that change. + if (child.nodemap !== 0 || child.data.length > 2) { + return copyAndSet(node, generation, nodeidx, child); + } + + // this node only has a single data (k/v-pair) child. + // to restore the CHAMP invariant, we "pull" that pair up into ourselves. + // this ensures that every tree stays in its single optimal representation, + // and allows dicts to be structurally compared. + const length = data.length; + const newData = new Array(length + 1); + + let readIndex = 0; + let writeIndex = 0; + + while (readIndex < dataidx) newData[writeIndex++] = data[readIndex++]; + newData[writeIndex++] = child.data[0]; + newData[writeIndex++] = child.data[1]; + while (readIndex < nodeidx) newData[writeIndex++] = data[readIndex++]; + readIndex++; + while (readIndex < length) newData[writeIndex++] = data[readIndex++]; + + return makeNode(generation, node.datamap | bit, node.nodemap ^ bit, newData); + } + + // 3. Data Node + // There is no data entry here, or it is a prefix for a different key + if ((node.datamap & bit) === 0 || !isEqual(key, data[dataidx])) { + return node; + } + + // we found a data entry that we can delete. + transient.size -= 1; + return copyAndRemovePair(node, generation, bit, dataidx); +} + +export function map(dict, fun) { + // map can never modify the structure, so we can walk the dictionary directly, + // but still move to a new generation to make sure we get a new copy of every node. + const generation = nextGeneration(dict); + const root = copyNode(dict.root, generation); + const queue = [root]; + + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + const data = node.data; + // every node contains popcount(datamap) direct entries + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const edgesStart = data.length - popcount(node.nodemap); + for (let i = 0; i < edgesStart; i += 2) { + // we copied the node while queueing it, so direct mutation here is safe. + data[i + 1] = fun(data[i], data[i + 1]); + } + // the remaining entries are other nodes we can queue + for (let i = edgesStart; i < data.length; ++i) { + // copy the node first to make it safe to mutate + data[i] = copyNode(data[i], generation); + queue.push(data[i]); + } + } + + return new Dict(dict.size, root); +} + +export function fold(dict, state, fun) { + const queue = [dict.root]; + + while (queue.length) { + // order doesn't matter, so we can use push/pop for faster array usage. + const node = queue.pop(); + const data = node.data; + // every node contains popcount(datamap) direct entries + // We need to query the length from the nodemap, as we don't know if this + // is an overflow node or not! if it is, it will never have datamap set! + const edgesStart = data.length - popcount(node.nodemap); + for (let i = 0; i < edgesStart; i += 2) { + state = fun(state, data[i], data[i + 1]); + } + // the remaining entries are child nodes we can queue. + for (let i = edgesStart; i < data.length; ++i) { + queue.push(data[i]); + } + } + + return state; +} + +/** + * How many `1` bits are set in a 32-bit integer. + */ +function popcount(n) { + n -= (n >>> 1) & 0x55555555; + n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); + return Math.imul((n + (n >>> 4)) & 0x0f0f0f0f, 0x01010101) >>> 24; +} + +/** + * Given a population bitmap and a bit selected from that map, returns + * how many less significant 1 bits there are. + * + * For example, index(10101, 100) returns 1, since there is a single less + * significant `1` bit. This translates to the 0-based "index" of that bit. + */ +function index(bitmap, bit) { + return popcount(bitmap & (bit - 1)); +} + +/** + * Extracts a single slice of the hash, and returns a bitmask for the resulting value. + * For example, if the slice returns 5, this function returns 10000 = 1 << 5. + */ +function hashbit(hash, shift) { + return 1 << ((hash >>> shift) & mask); +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bit_array.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bit_array.gleam new file mode 100644 index 0000000..1fefa8b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bit_array.gleam @@ -0,0 +1,280 @@ +//// BitArrays are a sequence of binary data of any length. + +import gleam/int +import gleam/order +import gleam/string + +/// Converts a UTF-8 `String` type into a `BitArray`. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_from_string") +pub fn from_string(x: String) -> BitArray + +/// Returns an integer which is the number of bits in the bit array. +/// +@external(erlang, "erlang", "bit_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_bit_size") +pub fn bit_size(x: BitArray) -> Int + +/// Returns an integer which is the number of bytes in the bit array. +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_byte_size") +pub fn byte_size(x: BitArray) -> Int + +/// Pads a bit array with zeros so that it is a whole number of bytes. +/// +@external(erlang, "gleam_stdlib", "bit_array_pad_to_bytes") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_pad_to_bytes") +pub fn pad_to_bytes(x: BitArray) -> BitArray + +/// Creates a new bit array by joining two bit arrays. +/// +/// ## Examples +/// +/// ```gleam +/// assert append(to: from_string("butter"), suffix: from_string("fly")) +/// == from_string("butterfly") +/// ``` +/// +pub fn append(to first: BitArray, suffix second: BitArray) -> BitArray { + concat([first, second]) +} + +/// Extracts a sub-section of a bit array. +/// +/// The slice will start at given position and continue up to specified +/// length. +/// A negative length can be used to extract bytes at the end of a bit array. +/// +/// This function runs in constant time. +/// +@external(erlang, "gleam_stdlib", "bit_array_slice") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_slice") +pub fn slice( + from string: BitArray, + at position: Int, + take length: Int, +) -> Result(BitArray, Nil) + +/// Tests to see whether a bit array is valid UTF-8. +/// +pub fn is_utf8(bits: BitArray) -> Bool { + is_utf8_loop(bits) +} + +@target(erlang) +fn is_utf8_loop(bits: BitArray) -> Bool { + case bits { + <<>> -> True + <<_:utf8, rest:bytes>> -> is_utf8_loop(rest) + _ -> False + } +} + +@target(javascript) +fn is_utf8_loop(bits: BitArray) -> Bool { + case to_string(bits) { + Ok(_) -> True + Error(_) -> False + } +} + +/// Converts a bit array to a string. +/// +/// Returns an error if the bit array is invalid UTF-8 data. +/// +@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_string") +pub fn to_string(bits: BitArray) -> Result(String, Nil) { + case is_utf8(bits) { + True -> Ok(unsafe_to_string(bits)) + False -> Error(Nil) + } +} + +@external(erlang, "gleam_stdlib", "identity") +fn unsafe_to_string(a: BitArray) -> String + +/// Creates a new bit array by joining multiple binaries. +/// +/// ## Examples +/// +/// ```gleam +/// assert concat([from_string("butter"), from_string("fly")]) +/// == from_string("butterfly") +/// ``` +/// +@external(erlang, "gleam_stdlib", "bit_array_concat") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_concat") +pub fn concat(bit_arrays: List(BitArray)) -> BitArray + +/// Encodes a BitArray into a base 64 encoded string. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +@external(erlang, "gleam_stdlib", "base64_encode") +@external(javascript, "../gleam_stdlib.mjs", "base64_encode") +pub fn base64_encode(input: BitArray, padding: Bool) -> String + +/// Decodes a base 64 encoded string into a `BitArray`. +/// +pub fn base64_decode(encoded: String) -> Result(BitArray, Nil) { + let padded = case byte_size(from_string(encoded)) % 4 { + 0 -> encoded + n -> string.append(encoded, string.repeat("=", 4 - n)) + } + decode64(padded) +} + +@external(erlang, "gleam_stdlib", "base64_decode") +@external(javascript, "../gleam_stdlib.mjs", "base64_decode") +fn decode64(a: String) -> Result(BitArray, Nil) + +/// Encodes a `BitArray` into a base 64 encoded string with URL and filename +/// safe alphabet. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +pub fn base64_url_encode(input: BitArray, padding: Bool) -> String { + input + |> base64_encode(padding) + |> string.replace("+", "-") + |> string.replace("/", "_") +} + +/// Decodes a base 64 encoded string with URL and filename safe alphabet into a +/// `BitArray`. +/// +pub fn base64_url_decode(encoded: String) -> Result(BitArray, Nil) { + encoded + |> string.replace("-", "+") + |> string.replace("_", "/") + |> base64_decode() +} + +/// Encodes a `BitArray` into a base 16 encoded string. +/// +/// If the bit array does not contain a whole number of bytes then it is padded +/// with zero bits prior to being encoded. +/// +@external(erlang, "gleam_stdlib", "base16_encode") +@external(javascript, "../gleam_stdlib.mjs", "base16_encode") +pub fn base16_encode(input: BitArray) -> String + +/// Decodes a base 16 encoded string into a `BitArray`. +/// +@external(erlang, "gleam_stdlib", "base16_decode") +@external(javascript, "../gleam_stdlib.mjs", "base16_decode") +pub fn base16_decode(input: String) -> Result(BitArray, Nil) + +/// Converts a bit array to a string containing the decimal value of each byte. +/// +/// Use this over `string.inspect` when you have a bit array you want printed +/// in the array syntax even if it is valid UTF-8. +/// +/// ## Examples +/// +/// ```gleam +/// assert inspect(<<0, 20, 0x20, 255>>) == "<<0, 20, 32, 255>>" +/// ``` +/// +/// ```gleam +/// assert inspect(<<100, 5:3>>) == "<<100, 5:size(3)>>" +/// ``` +/// +pub fn inspect(input: BitArray) -> String { + inspect_loop(input, "<<") <> ">>" +} + +fn inspect_loop(input: BitArray, accumulator: String) -> String { + case input { + <<>> -> accumulator + + <> -> accumulator <> int.to_string(x) <> ":size(1)" + <> -> accumulator <> int.to_string(x) <> ":size(2)" + <> -> accumulator <> int.to_string(x) <> ":size(3)" + <> -> accumulator <> int.to_string(x) <> ":size(4)" + <> -> accumulator <> int.to_string(x) <> ":size(5)" + <> -> accumulator <> int.to_string(x) <> ":size(6)" + <> -> accumulator <> int.to_string(x) <> ":size(7)" + + <> -> { + let suffix = case rest { + <<>> -> "" + _ -> ", " + } + + let accumulator = accumulator <> int.to_string(x) <> suffix + inspect_loop(rest, accumulator) + } + + _ -> accumulator + } +} + +/// Compare two bit arrays as sequences of bytes. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(<<1>>, <<2>>) == Lt +/// ``` +/// +/// ```gleam +/// assert compare(<<"AB":utf8>>, <<"AA":utf8>>) == Gt +/// ``` +/// +/// ```gleam +/// assert compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>) == Eq +/// ``` +/// +pub fn compare(a: BitArray, with b: BitArray) -> order.Order { + case a, b { + <>, <> -> + case first_byte, second_byte { + f, s if f > s -> order.Gt + f, s if f < s -> order.Lt + _, _ -> compare(first_rest, second_rest) + } + + <<>>, <<>> -> order.Eq + // First has more items, example: "AB" > "A": + _, <<>> -> order.Gt + // Second has more items, example: "A" < "AB": + <<>>, _ -> order.Lt + // This happens when there's unusually sized elements. + // Handle these special cases via custom erlang function. + first, second -> + case bit_array_to_int_and_size(first), bit_array_to_int_and_size(second) { + #(a, _), #(b, _) if a > b -> order.Gt + #(a, _), #(b, _) if a < b -> order.Lt + #(_, size_a), #(_, size_b) if size_a > size_b -> order.Gt + #(_, size_a), #(_, size_b) if size_a < size_b -> order.Lt + _, _ -> order.Eq + } + } +} + +@external(erlang, "gleam_stdlib", "bit_array_to_int_and_size") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_int_and_size") +fn bit_array_to_int_and_size(a: BitArray) -> #(Int, Int) + +/// Checks whether the first `BitArray` starts with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert starts_with(<<1, 2, 3, 4>>, <<1, 2>>) +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "bit_array_starts_with") +pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { + let prefix_size = bit_size(prefix) + + case bits { + <> if pref == prefix -> True + _ -> False + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bool.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bool.gleam new file mode 100644 index 0000000..92147fa --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bool.gleam @@ -0,0 +1,298 @@ +//// A type with two possible values, `True` and `False`. Used to indicate whether +//// things are... true or false! +//// +//// It is often clearer and offers more type safety to define a custom type +//// than to use `Bool`. For example, rather than having a `is_teacher: Bool` +//// field consider having a `role: SchoolRole` field where `SchoolRole` is a custom +//// type that can be either `Student` or `Teacher`. + +/// Returns the and of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `&&` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert and(True, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, True) +/// ``` +/// +/// ```gleam +/// assert !and(False, False) +/// ``` +/// +pub fn and(a: Bool, b: Bool) -> Bool { + a && b +} + +/// Returns the or of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `||` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(True, True) +/// ``` +/// +/// ```gleam +/// assert or(False, True) +/// ``` +/// +/// ```gleam +/// assert or(True, False) +/// ``` +/// +/// ```gleam +/// assert !or(False, False) +/// ``` +/// +pub fn or(a: Bool, b: Bool) -> Bool { + a || b +} + +/// Returns the opposite bool value. +/// +/// This is the same as the `!` or `not` operators in some other languages. +/// +/// ## Examples +/// +/// ```gleam +/// assert !negate(True) +/// ``` +/// +/// ```gleam +/// assert negate(False) +/// ``` +/// +pub fn negate(bool: Bool) -> Bool { + !bool +} + +/// Returns the nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert nor(False, False) +/// ``` +/// +/// ```gleam +/// assert !nor(False, True) +/// ``` +/// +/// ```gleam +/// assert !nor(True, False) +/// ``` +/// +/// ```gleam +/// assert !nor(True, True) +/// ``` +/// +pub fn nor(a: Bool, b: Bool) -> Bool { + !{ a || b } +} + +/// Returns the nand of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert nand(False, False) +/// ``` +/// +/// ```gleam +/// assert nand(False, True) +/// ``` +/// +/// ```gleam +/// assert nand(True, False) +/// ``` +/// +/// ```gleam +/// assert !nand(True, True) +/// ``` +/// +pub fn nand(a: Bool, b: Bool) -> Bool { + !{ a && b } +} + +/// Returns the exclusive or of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert !exclusive_or(False, False) +/// ``` +/// +/// ```gleam +/// assert exclusive_or(False, True) +/// ``` +/// +/// ```gleam +/// assert exclusive_or(True, False) +/// ``` +/// +/// ```gleam +/// assert !exclusive_or(True, True) +/// ``` +/// +pub fn exclusive_or(a: Bool, b: Bool) -> Bool { + a != b +} + +/// Returns the exclusive nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// assert exclusive_nor(False, False) +/// ``` +/// +/// ```gleam +/// assert !exclusive_nor(False, True) +/// ``` +/// +/// ```gleam +/// assert !exclusive_nor(True, False) +/// ``` +/// +/// ```gleam +/// assert exclusive_nor(True, True) +/// ``` +/// +pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { + a == b +} + +/// Returns a string representation of the given bool. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(True) == "True" +/// ``` +/// +/// ```gleam +/// assert to_string(False) == "False" +/// ``` +/// +pub fn to_string(bool: Bool) -> String { + case bool { + False -> "False" + True -> "True" + } +} + +/// Run a callback function if the given bool is `False`, otherwise return a +/// default value. +/// +/// With a `use` expression this function can simulate the early-return pattern +/// found in some other programming languages. +/// +/// In a procedural language: +/// +/// ```js +/// if (predicate) return value; +/// // ... +/// ``` +/// +/// In Gleam with a `use` expression: +/// +/// ```gleam +/// use <- guard(when: predicate, return: value) +/// // ... +/// ``` +/// +/// Like everything in Gleam `use` is an expression, so it short circuits the +/// current block, not the entire function. As a result you can assign the value +/// to a variable: +/// +/// ```gleam +/// let x = { +/// use <- guard(when: predicate, return: value) +/// // ... +/// } +/// ``` +/// +/// Note that unlike in procedural languages the `return` value is evaluated +/// even when the predicate is `False`, so it is advisable not to perform +/// expensive computation nor side-effects there. +/// +/// +/// ## Examples +/// +/// ```gleam +/// let name = "" +/// use <- guard(when: name == "", return: "Welcome!") +/// "Hello, " <> name +/// // -> "Welcome!" +/// ``` +/// +/// ```gleam +/// let name = "Kamaka" +/// use <- guard(when: name == "", return: "Welcome!") +/// "Hello, " <> name +/// // -> "Hello, Kamaka" +/// ``` +/// +pub fn guard( + when requirement: Bool, + return consequence: a, + otherwise alternative: fn() -> a, +) -> a { + case requirement { + True -> consequence + False -> alternative() + } +} + +/// Runs a callback function if the given bool is `True`, otherwise runs an +/// alternative callback function. +/// +/// Useful when further computation should be delayed regardless of the given +/// bool's value. +/// +/// See [`guard`](#guard) for more info. +/// +/// ## Examples +/// +/// ```gleam +/// let name = "Kamaka" +/// let inquiry = fn() { "How may we address you?" } +/// use <- lazy_guard(when: name == "", return: inquiry) +/// "Hello, " <> name +/// // -> "Hello, Kamaka" +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// let name = "" +/// let greeting = fn() { "Hello, " <> name } +/// use <- lazy_guard(when: name == "", otherwise: greeting) +/// let number = int.random(99) +/// let name = "User " <> int.to_string(number) +/// "Welcome, " <> name +/// // -> "Welcome, User 54" +/// ``` +/// +pub fn lazy_guard( + when requirement: Bool, + return consequence: fn() -> a, + otherwise alternative: fn() -> a, +) -> a { + case requirement { + True -> consequence() + False -> alternative() + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam new file mode 100644 index 0000000..e712fa9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam @@ -0,0 +1,190 @@ +//// `BytesTree` is a type used for efficiently building binary content to be +//// written to a file or a socket. Internally it is represented as a tree so to +//// append or prepend to a bytes tree is a constant time operation that +//// allocates a new node in the tree without copying any of the content. When +//// writing to an output stream the tree is traversed and the content is sent +//// directly rather than copying it into a single buffer beforehand. +//// +//// If we append one bit array to another the bit arrays must be copied to a +//// new location in memory so that they can sit together. This behaviour +//// enables efficient reading of the data but copying can be expensive, +//// especially if we want to join many bit arrays together. +//// +//// BytesTree is different in that it can be joined together in constant +//// time using minimal memory, and then can be efficiently converted to a +//// bit array using the `to_bit_array` function. +//// +//// Byte trees are always byte aligned, so that a number of bits that is not +//// divisible by 8 will be padded with 0s. +//// +//// On Erlang this type is compatible with Erlang's iolists. + +import gleam/bit_array +import gleam/list +import gleam/string_tree.{type StringTree} + +pub opaque type BytesTree { + Bytes(BitArray) + Text(StringTree) + Many(List(BytesTree)) +} + +/// Create an empty `BytesTree`. Useful as the start of a pipe chaining many +/// trees together. +/// +pub fn new() -> BytesTree { + concat([]) +} + +/// Prepends a bit array to the start of a bytes tree. +/// +/// Runs in constant time. +/// +pub fn prepend(to second: BytesTree, prefix first: BitArray) -> BytesTree { + append_tree(from_bit_array(first), second) +} + +/// Appends a bit array to the end of a bytes tree. +/// +/// Runs in constant time. +/// +pub fn append(to first: BytesTree, suffix second: BitArray) -> BytesTree { + append_tree(first, from_bit_array(second)) +} + +/// Prepends a bytes tree onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_tree(to second: BytesTree, prefix first: BytesTree) -> BytesTree { + append_tree(first, second) +} + +/// Appends a bytes tree onto the end of another. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "iodata_append") +pub fn append_tree(to first: BytesTree, suffix second: BytesTree) -> BytesTree { + case second { + Many(trees) -> Many([first, ..trees]) + Text(_) | Bytes(_) -> Many([first, second]) + } +} + +/// Prepends a string onto the start of a bytes tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn prepend_string(to second: BytesTree, prefix first: String) -> BytesTree { + append_tree(from_string(first), second) +} + +/// Appends a string onto the end of a bytes tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn append_string(to first: BytesTree, suffix second: String) -> BytesTree { + append_tree(first, from_string(second)) +} + +/// Joins a list of bytes trees into a single one. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +pub fn concat(trees: List(BytesTree)) -> BytesTree { + Many(trees) +} + +/// Joins a list of bit arrays into a single bytes tree. +/// +/// Runs in constant time. +/// +pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesTree { + bits + |> list.map(from_bit_array) + |> concat() +} + +/// Creates a new bytes tree from a string. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string(string: String) -> BytesTree { + Text(string_tree.from_string(string)) +} + +/// Creates a new bytes tree from a string tree. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string_tree(tree: string_tree.StringTree) -> BytesTree { + Text(tree) +} + +/// Creates a new bytes tree from a bit array. +/// +/// Runs in constant time. +/// +pub fn from_bit_array(bits: BitArray) -> BytesTree { + bits + |> bit_array.pad_to_bytes + |> wrap_list +} + +@external(erlang, "gleam_stdlib", "wrap_list") +fn wrap_list(bits: BitArray) -> BytesTree { + Bytes(bits) +} + +/// Turns a bytes tree into a bit array. +/// +/// Runs in linear time. +/// +/// When running on Erlang this function is implemented natively by the +/// virtual machine and is highly optimised. +/// +@external(erlang, "erlang", "list_to_bitstring") +pub fn to_bit_array(tree: BytesTree) -> BitArray { + [[tree]] + |> to_list([]) + |> list.reverse + |> bit_array.concat +} + +fn to_list(stack: List(List(BytesTree)), acc: List(BitArray)) -> List(BitArray) { + case stack { + [] -> acc + + [[], ..remaining_stack] -> to_list(remaining_stack, acc) + + [[Bytes(bits), ..rest], ..remaining_stack] -> + to_list([rest, ..remaining_stack], [bits, ..acc]) + + [[Text(tree), ..rest], ..remaining_stack] -> { + let bits = bit_array.from_string(string_tree.to_string(tree)) + to_list([rest, ..remaining_stack], [bits, ..acc]) + } + + [[Many(trees), ..rest], ..remaining_stack] -> + to_list([trees, rest, ..remaining_stack], acc) + } +} + +/// Returns the size of the bytes tree's content in bytes. +/// +/// Runs in linear time. +/// +@external(erlang, "erlang", "iolist_size") +pub fn byte_size(tree: BytesTree) -> Int { + [[tree]] + |> to_list([]) + |> list.fold(0, fn(acc, bits) { bit_array.byte_size(bits) + acc }) +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dict.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dict.gleam new file mode 100644 index 0000000..4a81207 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dict.gleam @@ -0,0 +1,582 @@ +import gleam/option.{type Option} + +/// A dictionary of keys and values. +/// +/// Any type can be used for the keys and values of a dict, but all the keys +/// must be of the same type and all the values must be of the same type. +/// +/// Each key can only be present in a dict once. +/// +/// Dicts are not ordered in any way, and any unintentional ordering is not to +/// be relied upon in your code as it may change in future versions of Erlang +/// or Gleam. +/// +/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more +/// information. +/// +pub type Dict(key, value) + +/// "TransientDict" is a mutable view on a dictionary used internally by the +/// JavaScript target. No mutable API is exposed to the user. +/// +/// Transients are to be treated as having a linear (single-use, think rust) type. +/// A transient value becomes invalid as soon as it's passed to one of the functions. +type TransientDict(key, value) + +/// Convert a normal Dict to a transient dict. +/// A transient dict is a mutable copy of the original. +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../dict.mjs", "toTransient") +fn to_transient(dict: Dict(key, value)) -> TransientDict(key, value) + +/// Convert a transient dict back into a normal dict, freezing its contents. +/// Using the transient after this point is highly unsafe and leads to undefined behavior. +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../dict.mjs", "fromTransient") +fn from_transient(transient: TransientDict(key, value)) -> Dict(key, value) + +/// Determines the number of key-value pairs in the dict. +/// This function runs in constant time and does not need to iterate the dict. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> size == 0 +/// ``` +/// +/// ```gleam +/// assert new() |> insert("key", "value") |> size == 1 +/// ``` +/// +@external(erlang, "maps", "size") +@external(javascript, "../dict.mjs", "size") +pub fn size(dict: Dict(k, v)) -> Int + +/// Determines whether or not the dict is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> is_empty +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert("b", 1) |> is_empty } +/// ``` +/// +pub fn is_empty(dict: Dict(k, v)) -> Bool { + size(dict) == 0 +} + +/// Converts the dict to a list of 2-element tuples `#(key, value)`, one for +/// each key-value pair in the dict. +/// +/// The tuples in the list have no specific order. +/// +/// ## Examples +/// +/// Calling `to_list` on an empty `dict` returns an empty list. +/// +/// ```gleam +/// assert new() |> to_list == [] +/// ``` +/// +/// The ordering of elements in the resulting list is an implementation detail +/// that should not be relied upon. +/// +/// ```gleam +/// assert new() +/// |> insert("b", 1) +/// |> insert("a", 0) +/// |> insert("c", 2) +/// |> to_list +/// == [#("a", 0), #("b", 1), #("c", 2)] +/// ``` +/// +@external(erlang, "maps", "to_list") +pub fn to_list(dict: Dict(k, v)) -> List(#(k, v)) { + fold(dict, from: [], with: fn(acc, key, value) { [#(key, value), ..acc] }) +} + +/// Converts a list of 2-element tuples `#(key, value)` to a dict. +/// +/// If two tuples have the same key the last one in the list will be the one +/// that is present in the dict. +/// +@external(erlang, "maps", "from_list") +pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { + from_list_loop(to_transient(new()), list) +} + +fn from_list_loop( + transient: TransientDict(k, v), + list: List(#(k, v)), +) -> Dict(k, v) { + case list { + [] -> from_transient(transient) + [#(key, value), ..rest] -> + from_list_loop(transient_insert(key, value, transient), rest) + } +} + +/// Determines whether or not a value is present in the dict for a given key. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> has_key("a") +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert("a", 0) |> has_key("b") } +/// ``` +/// +@external(javascript, "../dict.mjs", "has") +pub fn has_key(dict: Dict(k, v), key: k) -> Bool { + do_has_key(key, dict) +} + +@external(erlang, "maps", "is_key") +fn do_has_key(key: k, dict: Dict(k, v)) -> Bool + +/// Creates a fresh dict that contains no values. +/// +@external(erlang, "maps", "new") +@external(javascript, "../dict.mjs", "make") +pub fn new() -> Dict(k, v) + +/// Fetches a value from a dict for a given key. +/// +/// The dict may not have a value for the key, so the value is wrapped in a +/// `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> get("a") == Ok(0) +/// ``` +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> get("b") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "map_get") +@external(javascript, "../dict.mjs", "get") +pub fn get(from: Dict(k, v), get: k) -> Result(v, Nil) + +/// Inserts a value into the dict with the given key. +/// +/// If the dict already has a value for the given key then the value is +/// replaced with the new value. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert("a", 0) == from_list([#("a", 0)]) +/// ``` +/// +/// ```gleam +/// assert new() |> insert("a", 0) |> insert("a", 5) == from_list([#("a", 5)]) +/// ``` +/// +@external(javascript, "../dict.mjs", "insert") +pub fn insert(into dict: Dict(k, v), for key: k, insert value: v) -> Dict(k, v) { + do_insert(key, value, dict) +} + +@external(erlang, "maps", "put") +fn do_insert(key: k, value: v, dict: Dict(k, v)) -> Dict(k, v) + +@external(erlang, "maps", "put") +@external(javascript, "../dict.mjs", "destructiveTransientInsert") +fn transient_insert( + key: k, + value: v, + transient: TransientDict(k, v), +) -> TransientDict(k, v) + +/// Updates all values in a given dict by calling a given function on each key +/// and value. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#(3, 3), #(2, 4)]) +/// |> map_values(fn(key, value) { key * value }) +/// == from_list([#(3, 9), #(2, 8)]) +/// ``` +/// +@external(javascript, "../dict.mjs", "map") +pub fn map_values(in dict: Dict(k, v), with fun: fn(k, v) -> a) -> Dict(k, a) { + do_map_values(fun, dict) +} + +@external(erlang, "maps", "map") +fn do_map_values(f: fn(k, v) -> a, dict: Dict(k, v)) -> Dict(k, a) + +/// Gets a list of all keys in a given dict. +/// +/// Dicts are not ordered so the keys are not returned in any specific order. Do +/// not write code that relies on the order keys are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> keys == ["a", "b"] +/// ``` +/// +@external(erlang, "maps", "keys") +pub fn keys(dict: Dict(k, v)) -> List(k) { + fold(dict, [], fn(acc, key, _value) { [key, ..acc] }) +} + +/// Gets a list of all values in a given dict. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order values are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> values == [0, 1] +/// ``` +/// +@external(erlang, "maps", "values") +pub fn values(dict: Dict(k, v)) -> List(v) { + fold(dict, [], fn(acc, _key, value) { [value, ..acc] }) +} + +/// Creates a new dict from a given dict, minus any entries that a given function +/// returns `False` for. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> filter(fn(key, value) { value != 0 }) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> filter(fn(key, value) { True }) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn filter( + in dict: Dict(k, v), + keeping predicate: fn(k, v) -> Bool, +) -> Dict(k, v) { + do_filter(predicate, dict) +} + +@external(erlang, "maps", "filter") +fn do_filter(f: fn(k, v) -> Bool, dict: Dict(k, v)) -> Dict(k, v) { + to_transient(new()) + |> fold(over: dict, with: fn(transient, key, value) { + case f(key, value) { + True -> transient_insert(key, value, transient) + False -> transient + } + }) + |> from_transient +} + +/// Creates a new dict from a given dict, only including any entries for which the +/// keys are in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> take(["b"]) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) +/// |> take(["a", "b", "c"]) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn take(from dict: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) { + do_take(desired_keys, dict) +} + +@external(erlang, "maps", "with") +fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + do_take_loop(dict, desired_keys, to_transient(new())) +} + +fn do_take_loop( + dict: Dict(k, v), + desired_keys: List(k), + acc: TransientDict(k, v), +) -> Dict(k, v) { + case desired_keys { + [] -> from_transient(acc) + [key, ..rest] -> + case get(dict, key) { + Ok(value) -> do_take_loop(dict, rest, transient_insert(key, value, acc)) + Error(_) -> do_take_loop(dict, rest, acc) + } + } +} + +/// Creates a new dict from a pair of given dicts by combining their entries. +/// +/// If there are entries with the same keys in both dicts the entry from the +/// second dict takes precedence. +/// +/// ## Examples +/// +/// ```gleam +/// let a = from_list([#("a", 0), #("b", 1)]) +/// let b = from_list([#("b", 2), #("c", 3)]) +/// assert merge(a, b) == from_list([#("a", 0), #("b", 2), #("c", 3)]) +/// ``` +/// +@external(erlang, "maps", "merge") +pub fn merge(into dict: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) { + combine(dict, new_entries, fn(_, new_entry) { new_entry }) +} + +/// Creates a new dict from a given dict with all the same entries except for the +/// one with a given key, if it exists. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> delete("a") +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> delete("c") +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn delete(from dict: Dict(k, v), delete key: k) -> Dict(k, v) { + to_transient(dict) |> transient_delete(key, _) |> from_transient +} + +@external(erlang, "maps", "remove") +@external(javascript, "../dict.mjs", "destructiveTransientDelete") +fn transient_delete(a: k, b: TransientDict(k, v)) -> TransientDict(k, v) + +/// Creates a new dict from a given dict with all the same entries except any with +/// keys found in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["a"]) +/// == from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["c"]) +/// == from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +/// ```gleam +/// assert from_list([#("a", 0), #("b", 1)]) |> drop(["a", "b", "c"]) +/// == from_list([]) +/// ``` +/// +pub fn drop(from dict: Dict(k, v), drop disallowed_keys: List(k)) -> Dict(k, v) { + do_drop(disallowed_keys, dict) +} + +@external(erlang, "maps", "without") +fn do_drop(disallowed_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + drop_loop(to_transient(dict), disallowed_keys) +} + +fn drop_loop( + transient: TransientDict(k, v), + disallowed_keys: List(k), +) -> Dict(k, v) { + case disallowed_keys { + [] -> from_transient(transient) + [key, ..rest] -> drop_loop(transient_delete(key, transient), rest) + } +} + +/// Creates a new dict with one entry inserted or updated using a given function. +/// +/// If there was not an entry in the dict for the given key then the function +/// gets `None` as its argument, otherwise it gets `Some(value)`. +/// +/// ## Examples +/// +/// ```gleam +/// let dict = from_list([#("a", 0)]) +/// let increment = fn(x) { +/// case x { +/// Some(i) -> i + 1 +/// None -> 0 +/// } +/// } +/// +/// assert upsert(dict, "a", increment) == from_list([#("a", 1)]) +/// ``` +/// +/// ```gleam +/// assert upsert(dict, "b", increment) == from_list([#("a", 0), #("b", 0)]) +/// ``` +/// +pub fn upsert( + in dict: Dict(k, v), + update key: k, + with fun: fn(Option(v)) -> v, +) -> Dict(k, v) { + case get(dict, key) { + Ok(value) -> insert(dict, key, fun(option.Some(value))) + Error(_) -> insert(dict, key, fun(option.None)) + } +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order entries are used by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// assert fold(dict, 0, fn(accumulator, key, value) { accumulator + value }) +/// == 13 +/// ``` +/// +/// ```gleam +/// import gleam/string +/// +/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// assert +/// fold(dict, "", fn(accumulator, key, value) { +/// string.append(accumulator, key) +/// }) +/// == "abc" +/// ``` +/// +@external(javascript, "../dict.mjs", "fold") +pub fn fold( + over dict: Dict(k, v), + from initial: acc, + with fun: fn(acc, k, v) -> acc, +) -> acc { + let fun = fn(key, value, acc) { fun(acc, key, value) } + do_fold(fun, initial, dict) +} + +@external(erlang, "maps", "fold") +fn do_fold(fun: fn(k, v, acc) -> acc, initial: acc, dict: Dict(k, v)) -> acc + +/// Calls a function for each key and value in a dict, discarding the return +/// value. +/// +/// Useful for producing a side effect for every item of a dict. +/// +/// ```gleam +/// import gleam/io +/// +/// let dict = from_list([#("a", "apple"), #("b", "banana"), #("c", "cherry")]) +/// +/// assert +/// each(dict, fn(k, v) { +/// io.println(k <> " => " <> v) +/// }) +/// == Nil +/// // a => apple +/// // b => banana +/// // c => cherry +/// ``` +/// +/// The order of elements in the iteration is an implementation detail that +/// should not be relied upon. +/// +pub fn each(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil { + fold(dict, Nil, fn(nil, k, v) { + fun(k, v) + nil + }) +} + +/// Creates a new dict from a pair of given dicts by combining their entries. +/// +/// If there are entries with the same keys in both dicts the given function is +/// used to determine the new value to use in the resulting dict. +/// +/// ## Examples +/// +/// ```gleam +/// let a = from_list([#("a", 0), #("b", 1)]) +/// let b = from_list([#("a", 2), #("c", 3)]) +/// assert combine(a, b, fn(one, other) { one + other }) +/// == from_list([#("a", 2), #("b", 1), #("c", 3)]) +/// ``` +/// +pub fn combine( + dict: Dict(k, v), + other: Dict(k, v), + with fun: fn(v, v) -> v, +) -> Dict(k, v) { + do_combine(fn(_, l, r) { fun(l, r) }, dict, other) +} + +@external(erlang, "maps", "merge_with") +fn do_combine( + combine: fn(k, v, v) -> v, + left: Dict(k, v), + right: Dict(k, v), +) -> Dict(k, v) { + let #(big, small, combine) = case size(left) >= size(right) { + True -> #(left, right, combine) + False -> #(right, left, fn(k, l, r) { combine(k, r, l) }) + } + + to_transient(big) + |> fold(over: small, with: fn(transient, key, value) { + let update = fn(existing) { combine(key, existing, value) } + transient_update_with(key, update, value, transient) + }) + |> from_transient +} + +@external(erlang, "maps", "update_with") +@external(javascript, "../dict.mjs", "destructiveTransientUpdateWith") +fn transient_update_with( + key: k, + fun: fn(v) -> v, + init: v, + transient: TransientDict(k, v), +) -> TransientDict(k, v) + +@internal +pub fn group(key: fn(v) -> k, list: List(v)) -> Dict(k, List(v)) { + group_loop(to_transient(new()), key, list) +} + +fn group_loop( + transient: TransientDict(k, List(v)), + to_key: fn(v) -> k, + list: List(v), +) -> Dict(k, List(v)) { + case list { + [] -> from_transient(transient) + [value, ..rest] -> { + let key = to_key(value) + let update = fn(existing) { [value, ..existing] } + + transient + |> transient_update_with(key, update, [value], _) + |> group_loop(to_key, rest) + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic.gleam new file mode 100644 index 0000000..b144da7 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic.gleam @@ -0,0 +1,99 @@ +import gleam/dict + +/// `Dynamic` data is data that we don't know the type of yet. +/// We likely get data like this from interop with Erlang, or from +/// IO with the outside world. +/// +/// This module contains code for forming dynamic data, and the +/// `gleam/dynamic/decode` module contains code for turning dynamic data back +/// into Gleam data with known types. You will likely mostly use the other +/// module in your projects. +/// +/// The exact runtime representation of dynamic values will depend on the +/// compilation target used. +/// +pub type Dynamic + +/// Return a string indicating the type of the dynamic value. +/// +/// This function may be useful for constructing error messages or logs. If you +/// want to turn dynamic data into well typed data then you want the +/// `gleam/dynamic/decode` module. +/// +/// ```gleam +/// assert classify(string("Hello")) == "String" +/// ``` +/// +@external(erlang, "gleam_stdlib", "classify_dynamic") +@external(javascript, "../gleam_stdlib.mjs", "classify_dynamic") +pub fn classify(data: Dynamic) -> String + +/// Create a dynamic value from a bool. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn bool(a: Bool) -> Dynamic + +/// Create a dynamic value from a string. +/// +/// On Erlang this will be a binary string rather than a character list. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn string(a: String) -> Dynamic + +/// Create a dynamic value from a float. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn float(a: Float) -> Dynamic + +/// Create a dynamic value from an int. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn int(a: Int) -> Dynamic + +/// Create a dynamic value from a bit array. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn bit_array(a: BitArray) -> Dynamic + +/// Create a dynamic value from a list. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn list(a: List(Dynamic)) -> Dynamic + +/// Create a dynamic value from a list, converting it to a sequential runtime +/// format rather than the regular list format. +/// +/// On Erlang this will be a tuple, on JavaScript this will be an array. +/// +@external(erlang, "erlang", "list_to_tuple") +@external(javascript, "../gleam_stdlib.mjs", "list_to_array") +pub fn array(a: List(Dynamic)) -> Dynamic + +/// Create a dynamic value made of an unordered series of keys and values, where +/// the keys are unique. +/// +/// On Erlang this will be a map, on JavaScript this will be a Gleam dict +/// object. +/// +pub fn properties(entries: List(#(Dynamic, Dynamic))) -> Dynamic { + cast(dict.from_list(entries)) +} + +/// A dynamic value representing nothing. +/// +/// On Erlang this will be the atom `nil`, on JavaScript this will be +/// `undefined`. +/// +pub fn nil() -> Dynamic { + cast(Nil) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn cast(a: anything) -> Dynamic diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam new file mode 100644 index 0000000..b95b332 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam @@ -0,0 +1,1115 @@ +//// The `Dynamic` type is used to represent dynamically typed data. That is, data +//// that we don't know the precise type of yet, so we need to introspect the data to +//// see if it is of the desired type before we can use it. Typically data like this +//// would come from user input or from untyped languages such as Erlang or JavaScript. +//// +//// This module provides the `Decoder` type and associated functions, which provides +//// a type-safe and composable way to convert dynamic data into some desired type, +//// or into errors if the data doesn't have the desired structure. +//// +//// The `Decoder` type is generic and has 1 type parameter, which is the type that +//// it attempts to decode. A `Decoder(String)` can be used to decode strings, and a +//// `Decoder(Option(Int))` can be used to decode `Option(Int)`s +//// +//// Decoders work using _runtime reflection_ and the data structures of the target +//// platform. Differences between Erlang and JavaScript data structures may impact +//// your decoders, so it is important to test your decoders on all supported +//// platforms. +//// +//// The decoding technique used by this module was inspired by Juraj Petráš' +//// [Toy](https://github.com/Hackder/toy), Go's `encoding/json`, and Elm's +//// `Json.Decode`. Thank you to them! +//// +//// # Generating decoders +//// +//// The language server has the "generate dynamic decoder" code action, which +//// will generate a decoder function when run on a custom type definition. +//// This generated decoder function can be a convenient shortcut when creating +//// your own decoders, and you can edit the generated function to suit your needs. +//// +//// # Examples +//// +//// Dynamic data may come from various sources and so many different syntaxes could +//// be used to describe or construct them. In these examples a pseudocode +//// syntax is used to describe the data. +//// +//// ## Simple types +//// +//// This module defines decoders for simple data types such as [`string`](#string), +//// [`int`](#int), [`float`](#float), [`bit_array`](#bit_array), and [`bool`](#bool). +//// +//// ```gleam +//// // Data: +//// // "Hello, Joe!" +//// +//// let result = decode.run(data, decode.string) +//// assert result == Ok("Hello, Joe!") +//// ``` +//// +//// ## Lists +//// +//// The [`list`](#list) decoder decodes `List`s. To use it you must construct it by +//// passing in another decoder into the `list` function, which is the decoder that +//// is to be used for the elements of the list, type checking both the list and its +//// elements. +//// +//// ```gleam +//// // Data: +//// // [1, 2, 3, 4] +//// +//// let result = decode.run(data, decode.list(decode.int)) +//// assert result == Ok([1, 2, 3, 4]) +//// ``` +//// +//// On Erlang this decoder can decode from lists, and on JavaScript it can +//// decode from lists as well as JavaScript arrays. +//// +//// ## Options +//// +//// The [`optional`](#optional) decoder is used to decode values that may or may not +//// be present. In other environments these might be called "nullable" values. +//// +//// Like the `list` decoder, the `optional` decoder takes another decoder, +//// which is used to decode the value if it is present. +//// +//// ```gleam +//// // Data: +//// // 12.45 +//// +//// let result = decode.run(data, decode.optional(decode.float)) +//// assert result == Ok(option.Some(12.45)) +//// ``` +//// ```gleam +//// // Data: +//// // null +//// +//// let result = decode.run(data, decode.optional(decode.int)) +//// assert result == Ok(option.None) +//// ``` +//// +//// This decoder knows how to handle multiple different runtime representations of +//// absent values, including `Nil`, `None`, `null`, and `undefined`. +//// +//// ## Dicts +//// +//// The [`dict`](#dict) decoder decodes `Dicts` and contains two other decoders, one +//// for the keys, one for the values. +//// +//// ```gleam +//// // Data: +//// // { "Lucy" -> 10, "Nubi" -> 20 } +//// +//// let result = decode.run(data, decode.dict(decode.string, decode.int)) +//// assert result == Ok(dict.from_list([ +//// #("Lucy", 10), +//// #("Nubi", 20), +//// ])) +//// ``` +//// +//// ## Indexing objects +//// +//// The [`at`](#at) decoder can be used to decode a value that is nested within +//// key-value containers such as Gleam dicts, Erlang maps, or JavaScript objects. +//// +//// ```gleam +//// // Data: +//// // { "one" -> { "two" -> 123 } } +//// +//// let result = decode.run(data, decode.at(["one", "two"], decode.int)) +//// assert result == Ok(123) +//// ``` +//// +//// ## Indexing arrays +//// +//// If you use ints as keys then the [`at`](#at) decoder can be used to index into +//// array-like containers such as Gleam or Erlang tuples, or JavaScript arrays. +//// +//// ```gleam +//// // Data: +//// // ["one", "two", "three"] +//// +//// let result = decode.run(data, decode.at([1], decode.string)) +//// assert result == Ok("two") +//// ``` +//// +//// ## Records +//// +//// Decoding records from dynamic data is more complex and requires combining a +//// decoder for each field and a special constructor that builds your records with +//// the decoded field values. +//// +//// ```gleam +//// // Data: +//// // { +//// // "score" -> 180, +//// // "name" -> "Mel Smith", +//// // "is-admin" -> false, +//// // "enrolled" -> true, +//// // "colour" -> "Red", +//// // } +//// +//// let decoder = { +//// use name <- decode.field("name", decode.string) +//// use score <- decode.field("score", decode.int) +//// use colour <- decode.field("colour", decode.string) +//// use enrolled <- decode.field("enrolled", decode.bool) +//// decode.success(Player(name:, score:, colour:, enrolled:)) +//// } +//// +//// let result = decode.run(data, decoder) +//// assert result == Ok(Player("Mel Smith", 180, "Red", True)) +//// ``` +//// +//// ## Enum variants +//// +//// Imagine you have a custom type where all the variants do not contain any values. +//// +//// ```gleam +//// pub type PocketMonsterType { +//// Fire +//// Water +//// Grass +//// Electric +//// } +//// ``` +//// +//// You might choose to encode these variants as strings, `"fire"` for `Fire`, +//// `"water"` for `Water`, and so on. To decode them you'll need to decode the dynamic +//// data as a string, but then you'll need to decode it further still as not all +//// strings are valid values for the enum. This can be done with the `then` +//// function, which enables running a second decoder after the first one +//// succeeds. +//// +//// ```gleam +//// let decoder = { +//// use decoded_string <- decode.then(decode.string) +//// case decoded_string { +//// // Return succeeding decoders for valid strings +//// "fire" -> decode.success(Fire) +//// "water" -> decode.success(Water) +//// "grass" -> decode.success(Grass) +//// "electric" -> decode.success(Electric) +//// // Return a failing decoder for any other strings +//// _ -> decode.failure(Fire, expected: "PocketMonsterType") +//// } +//// } +//// +//// let result = decode.run(dynamic.string("water"), decoder) +//// assert result == Ok(Water) +//// +//// let result = decode.run(dynamic.string("wobble"), decoder) +//// assert result == Error([DecodeError("PocketMonsterType", "String", [])]) +//// ``` +//// +//// ## Record variants +//// +//// Decoding type variants that contain other values is done by combining the +//// techniques from the "enum variants" and "records" examples. Imagine you have +//// this custom type that you want to decode: +//// +//// ```gleam +//// pub type PocketMonsterPerson { +//// Trainer(name: String, badge_count: Int) +//// GymLeader(name: String, speciality: PocketMonsterType) +//// } +//// ``` +//// And you would like to be able to decode these from dynamic data like this: +//// ```erlang +//// { +//// "type" -> "trainer", +//// "name" -> "Ash", +//// "badge-count" -> 1, +//// } +//// ``` +//// ```erlang +//// { +//// "type" -> "gym-leader", +//// "name" -> "Misty", +//// "speciality" -> "water", +//// } +//// ``` +//// +//// Notice how both documents have a `"type"` field, which is used to indicate which +//// variant the data is for. +//// +//// First, define decoders for each of the variants: +//// +//// ```gleam +//// let trainer_decoder = { +//// use name <- decode.field("name", decode.string) +//// use badge_count <- decode.field("badge-count", decode.int) +//// decode.success(Trainer(name, badge_count)) +//// } +//// +//// let gym_leader_decoder = { +//// use name <- decode.field("name", decode.string) +//// use speciality <- decode.field("speciality", pocket_monster_type_decoder) +//// decode.success(GymLeader(name, speciality)) +//// } +//// ``` +//// +//// A third decoder can be used to extract and decode the `"type"` field, and the +//// expression can evaluate to whichever decoder is suitable for the document. +//// +//// ```gleam +//// // Data: +//// // { +//// // "type" -> "gym-leader", +//// // "name" -> "Misty", +//// // "speciality" -> "water", +//// // } +//// +//// let decoder = { +//// use tag <- decode.field("type", decode.string) +//// case tag { +//// "gym-leader" -> gym_leader_decoder +//// _ -> trainer_decoder +//// } +//// } +//// +//// let result = decode.run(data, decoder) +//// assert result == Ok(GymLeader("Misty", Water)) +//// ``` + +import gleam/bit_array +import gleam/dict.{type Dict} +import gleam/dynamic +import gleam/float +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} + +/// `Dynamic` data is data that we don't know the type of yet, originating from +/// external untyped systems. +/// +/// You should never be converting your well typed data to dynamic data. +/// +pub type Dynamic = + dynamic.Dynamic + +/// Error returned when unexpected data is encountered +/// +pub type DecodeError { + DecodeError(expected: String, found: String, path: List(String)) +} + +/// A decoder is a value that can be used to turn dynamically typed `Dynamic` +/// data into typed data using the `run` function. +/// +/// Several smaller decoders can be combined to make larger decoders using +/// functions such as `list` and `field`. +/// +pub opaque type Decoder(t) { + Decoder(function: fn(Dynamic) -> #(t, List(DecodeError))) +} + +/// The same as [`field`](#field), except taking a path to the value rather +/// than a field name. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("data"), dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// ]) +/// +/// let decoder = { +/// use name <- decode.subfield(["data", "name"], decode.string) +/// use email <- decode.subfield(["data", "email"], decode.string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +pub fn subfield( + field_path: List(name), + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + Decoder(function: fn(data) { + let #(out, errors1) = + index(field_path, [], field_decoder.function, data, fn(data, position) { + let #(default, _) = field_decoder.function(data) + #(default, [DecodeError("Field", "Nothing", [])]) + |> push_path(list.reverse(position)) + }) + let #(out, errors2) = next(out).function(data) + #(out, list.append(errors1, errors2)) + }) +} + +/// Run a decoder on a `Dynamic` value, decoding the value if it is of the +/// desired type, or returning errors. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = { +/// use name <- decode.field("name", decode.string) +/// use email <- decode.field("email", decode.string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// decode.run(data, decoder) +/// ``` +/// +pub fn run(data: Dynamic, decoder: Decoder(t)) -> Result(t, List(DecodeError)) { + let #(maybe_invalid_data, errors) = decoder.function(data) + case errors { + [] -> Ok(maybe_invalid_data) + [_, ..] -> Error(errors) + } +} + +/// A decoder that decodes a value that is nested within other values. For +/// example, decoding a value that is within some deeply nested JSON objects. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.at(["one", "two"], decode.int) +/// +/// let data = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.properties([ +/// #(dynamic.string("two"), dynamic.int(1000)), +/// ]), +/// ]) +/// +/// assert decode.run(data, decoder) == Ok(1000) +/// ``` +/// +/// ```gleam +/// assert dynamic.nil() +/// |> decode.run(decode.optional(decode.int)) +/// == Ok(option.None) +/// ``` +/// +pub fn at(path: List(segment), inner: Decoder(a)) -> Decoder(a) { + Decoder(function: fn(data) { + index(path, [], inner.function, data, fn(data, position) { + let #(default, _) = inner.function(data) + #(default, [DecodeError("Field", "Nothing", [])]) + |> push_path(list.reverse(position)) + }) + }) +} + +fn index( + path: List(a), + position: List(a), + inner: fn(Dynamic) -> #(b, List(DecodeError)), + data: Dynamic, + handle_miss: fn(Dynamic, List(a)) -> #(b, List(DecodeError)), +) -> #(b, List(DecodeError)) { + case path { + [] -> { + data + |> inner + |> push_path(list.reverse(position)) + } + + [key, ..path] -> { + case bare_index(data, key) { + Ok(Some(data)) -> { + index(path, [key, ..position], inner, data, handle_miss) + } + Ok(None) -> { + handle_miss(data, [key, ..position]) + } + Error(kind) -> { + let #(default, _) = inner(data) + #(default, [DecodeError(kind, dynamic.classify(data), [])]) + |> push_path(list.reverse(position)) + } + } + } + } +} + +@external(erlang, "gleam_stdlib", "index") +@external(javascript, "../../gleam_stdlib.mjs", "index") +fn bare_index(data: Dynamic, key: anything) -> Result(Option(Dynamic), String) + +fn push_path( + layer: #(t, List(DecodeError)), + path: List(key), +) -> #(t, List(DecodeError)) { + let path = list.map(path, fn(key) { key |> cast |> path_segment_to_string }) + let errors = + list.map(layer.1, fn(error) { + DecodeError(..error, path: list.append(path, error.path)) + }) + #(layer.0, errors) +} + +fn path_segment_to_string(key: Dynamic) -> String { + let decoder = + one_of(string, [ + int |> map(int.to_string), + float |> map(float.to_string), + ]) + case run(key, decoder) { + Ok(key) -> key + Error(_) -> "<" <> dynamic.classify(key) <> ">" + } +} + +/// Finalise a decoder having successfully extracted a value. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.field("email", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +pub fn success(data: t) -> Decoder(t) { + Decoder(function: fn(_) { #(data, []) }) +} + +/// Construct a decode error for some unexpected dynamic data. +/// +pub fn decode_error( + expected expected: String, + found found: Dynamic, +) -> List(DecodeError) { + [DecodeError(expected: expected, found: dynamic.classify(found), path: [])] +} + +/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is +/// of the desired type, or returning errors. An error is returned if there is +/// no field for the specified key. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("email"), dynamic.string("lucy@example.com")), +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.field("email", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com")) +/// ``` +/// +/// If you wish to decode a value that is more deeply nested within the dynamic +/// data, see [`subfield`](#subfield) and [`at`](#at). +/// +/// If you wish to return a default in the event that a field is not present, +/// see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at). +/// +pub fn field( + field_name: name, + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + subfield([field_name], field_decoder, next) +} + +/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is +/// of the desired type, or returning errors. The given default value is +/// returned if there is no field for the specified key. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let data = dynamic.properties([ +/// #(dynamic.string("name"), dynamic.string("Lucy")), +/// ]) +/// +/// let decoder = { +/// use name <- decode.field("name", string) +/// use email <- decode.optional_field("email", "n/a", string) +/// decode.success(SignUp(name: name, email: email)) +/// } +/// +/// let result = decode.run(data, decoder) +/// assert result == Ok(SignUp(name: "Lucy", email: "n/a")) +/// ``` +/// +pub fn optional_field( + key: name, + default: t, + field_decoder: Decoder(t), + next: fn(t) -> Decoder(final), +) -> Decoder(final) { + Decoder(function: fn(data) { + let #(out, errors1) = + case bare_index(data, key) { + Ok(Some(data)) -> field_decoder.function(data) + Ok(None) -> #(default, []) + Error(kind) -> #(default, [ + DecodeError(kind, dynamic.classify(data), []), + ]) + } + |> push_path([key]) + let #(out, errors2) = next(out).function(data) + #(out, list.append(errors1, errors2)) + }) +} + +/// A decoder that decodes a value that is nested within other values. For +/// example, decoding a value that is within some deeply nested JSON objects. +/// +/// This function will index into dictionaries with any key type, and if the key is +/// an int then it'll also index into Erlang tuples and JavaScript arrays, and +/// the first eight elements of Gleam lists. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.optionally_at(["one", "two"], 100, decode.int) +/// +/// let data = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.properties([])), +/// ]) +/// +/// assert decode.run(data, decoder) == Ok(100) +/// ``` +/// +pub fn optionally_at( + path: List(segment), + default: a, + inner: Decoder(a), +) -> Decoder(a) { + Decoder(function: fn(data) { + index(path, [], inner.function, data, fn(_, _) { #(default, []) }) + }) +} + +fn run_dynamic_function( + data: Dynamic, + name: String, + f: fn(Dynamic) -> Result(t, t), +) -> #(t, List(DecodeError)) { + case f(data) { + Ok(data) -> #(data, []) + Error(placeholder) -> #(placeholder, [ + DecodeError(name, dynamic.classify(data), []), + ]) + } +} + +/// A decoder that decodes `String` values. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.string("Hello!"), decode.string) +/// assert result == Ok("Hello!") +/// ``` +/// +pub const string: Decoder(String) = Decoder(decode_string) + +fn decode_string(data: Dynamic) -> #(String, List(DecodeError)) { + run_dynamic_function(data, "String", dynamic_string) +} + +@external(javascript, "../../gleam_stdlib.mjs", "string") +fn dynamic_string(from data: Dynamic) -> Result(String, String) { + case dynamic_bit_array(data) { + Ok(data) -> + case bit_array.to_string(data) { + Ok(string) -> Ok(string) + Error(_) -> Error("") + } + Error(_) -> Error("") + } +} + +/// A decoder that decodes `Bool` values. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.bool(True), decode.bool) +/// assert result == Ok(True) +/// ``` +/// +pub const bool: Decoder(Bool) = Decoder(decode_bool) + +fn decode_bool(data: Dynamic) -> #(Bool, List(DecodeError)) { + case cast(True) == data { + True -> #(True, []) + False -> + case cast(False) == data { + True -> #(False, []) + False -> #(False, decode_error("Bool", data)) + } + } +} + +/// A decoder that decodes `Int` values. +/// +/// This will not coerse float values into int values, so on platforms with +/// distinct runtime int and float types (Erlang, not JavaScript) it will fail, +/// even if the float is a whole number (e.g. 1.0). +/// +/// If you want to decode both ints and floats you may want to use the `one_of` +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.int(147), decode.int) +/// assert result == Ok(147) +/// ``` +/// +pub const int: Decoder(Int) = Decoder(decode_int) + +fn decode_int(data: Dynamic) -> #(Int, List(DecodeError)) { + run_dynamic_function(data, "Int", dynamic_int) +} + +@external(erlang, "gleam_stdlib", "int") +@external(javascript, "../../gleam_stdlib.mjs", "int") +fn dynamic_int(data: Dynamic) -> Result(Int, Int) + +/// A decoder that decodes `Float` values. +/// +/// This will not coerse int values into float values, so on platforms with +/// distinct runtime int and float types (Erlang, not JavaScript) it will fail +/// for ints. One time this may happen is when decoding JSON data. +/// +/// If you want to decode both ints and floats you may want to use the `one_of` +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.float(3.14), decode.float) +/// assert result == Ok(3.14) +/// ``` +/// +pub const float: Decoder(Float) = Decoder(decode_float) + +fn decode_float(data: Dynamic) -> #(Float, List(DecodeError)) { + run_dynamic_function(data, "Float", dynamic_float) +} + +@external(erlang, "gleam_stdlib", "float") +@external(javascript, "../../gleam_stdlib.mjs", "float") +fn dynamic_float(data: Dynamic) -> Result(Float, Float) + +/// A decoder that decodes `Dynamic` values. This decoder never returns an error. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.float(3.14), decode.dynamic) +/// assert result == Ok(dynamic.float(3.14)) +/// ``` +/// +pub const dynamic: Decoder(Dynamic) = Decoder(decode_dynamic) + +fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(DecodeError)) { + #(data, []) +} + +/// A decoder that decodes `BitArray` values. This decoder never returns an error. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.bit_array(<<5, 7>>), decode.bit_array) +/// assert result == Ok(<<5, 7>>) +/// ``` +/// +pub const bit_array: Decoder(BitArray) = Decoder(decode_bit_array) + +fn decode_bit_array(data: Dynamic) -> #(BitArray, List(DecodeError)) { + run_dynamic_function(data, "BitArray", dynamic_bit_array) +} + +@external(erlang, "gleam_stdlib", "bit_array") +@external(javascript, "../../gleam_stdlib.mjs", "bit_array") +fn dynamic_bit_array(data: Dynamic) -> Result(BitArray, BitArray) + +/// A decoder that decodes lists where all elements are decoded with a given +/// decoder. +/// +/// ## Examples +/// +/// ```gleam +/// let result = +/// [1, 2, 3] +/// |> list.map(dynamic.int) +/// |> dynamic.list +/// |> decode.run(decode.list(of: decode.int)) +/// assert result == Ok([1, 2, 3]) +/// ``` +/// +pub fn list(of inner: Decoder(a)) -> Decoder(List(a)) { + Decoder(fn(data) { + decode_list(data, inner.function, fn(p, k) { push_path(p, [k]) }, 0, []) + }) +} + +@external(erlang, "gleam_stdlib", "list") +@external(javascript, "../../gleam_stdlib.mjs", "list") +fn decode_list( + data: Dynamic, + item: fn(Dynamic) -> #(t, List(DecodeError)), + push_path: fn(#(t, List(DecodeError)), key) -> #(t, List(DecodeError)), + index: Int, + acc: List(t), +) -> #(List(t), List(DecodeError)) + +/// A decoder that decodes dicts where all keys and values are decoded with +/// given decoders. +/// +/// ## Examples +/// +/// ```gleam +/// let values = dynamic.properties([ +/// #(dynamic.string("one"), dynamic.int(1)), +/// #(dynamic.string("two"), dynamic.int(2)), +/// ]) +/// +/// let result = +/// decode.run(values, decode.dict(decode.string, decode.int)) +/// assert result == Ok(values) +/// ``` +/// +pub fn dict( + key: Decoder(key), + value: Decoder(value), +) -> Decoder(Dict(key, value)) { + Decoder(fn(data) { + case decode_dict(data) { + Error(_) -> #(dict.new(), decode_error("Dict", data)) + Ok(dict) -> + dict.fold(dict, #(dict.new(), []), fn(a, k, v) { + // If there are any errors from previous key-value pairs then we + // don't need to run the decoders, instead return the existing acc. + case a.1 { + [] -> fold_dict(a, k, v, key.function, value.function) + [_, ..] -> a + } + }) + } + }) +} + +fn fold_dict( + acc: #(Dict(k, v), List(DecodeError)), + key: Dynamic, + value: Dynamic, + key_decoder: fn(Dynamic) -> #(k, List(DecodeError)), + value_decoder: fn(Dynamic) -> #(v, List(DecodeError)), +) -> #(Dict(k, v), List(DecodeError)) { + // First we decode the key. + case key_decoder(key) { + #(key_decoded, []) -> + // Then we decode the value. + case value_decoder(value) { + #(value, []) -> { + // It worked! Insert the new key-value pair so we can move onto the next. + let dict = dict.insert(acc.0, key_decoded, value) + #(dict, acc.1) + } + #(_, errors) -> { + let key_identifier = path_segment_to_string(key) + push_path(#(dict.new(), errors), [key_identifier]) + } + } + #(_, errors) -> push_path(#(dict.new(), errors), ["keys"]) + } +} + +@external(erlang, "gleam_stdlib", "dict") +@external(javascript, "../../gleam_stdlib.mjs", "dict") +fn decode_dict(data: Dynamic) -> Result(Dict(Dynamic, Dynamic), Nil) + +/// A decoder that decodes nullable values of a type decoded by with a given +/// decoder. +/// +/// This function can handle common representations of null on all runtimes, such as +/// `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on +/// JavaScript. +/// +/// ## Examples +/// +/// ```gleam +/// let result = decode.run(dynamic.int(100), decode.optional(decode.int)) +/// assert result == Ok(option.Some(100)) +/// ``` +/// +/// ```gleam +/// let result = decode.run(dynamic.nil(), decode.optional(decode.int)) +/// assert result == Ok(option.None) +/// ``` +/// +pub fn optional(inner: Decoder(a)) -> Decoder(Option(a)) { + Decoder(function: fn(data) { + case is_null(data) { + True -> #(option.None, []) + False -> { + let #(data, errors) = inner.function(data) + #(option.Some(data), errors) + } + } + }) +} + +/// Apply a transformation function to any value decoded by the decoder. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.int |> decode.map(int.to_string) +/// let result = decode.run(dynamic.int(1000), decoder) +/// assert result == Ok("1000") +/// ``` +/// +pub fn map(decoder: Decoder(a), transformer: fn(a) -> b) -> Decoder(b) { + Decoder(function: fn(d) { + let #(data, errors) = decoder.function(d) + #(transformer(data), errors) + }) +} + +/// Apply a transformation function to any errors returned by the decoder. +/// +pub fn map_errors( + decoder: Decoder(a), + transformer: fn(List(DecodeError)) -> List(DecodeError), +) -> Decoder(a) { + Decoder(function: fn(d) { + let #(data, errors) = decoder.function(d) + #(data, transformer(errors)) + }) +} + +/// Replace all errors produced by a decoder with one single error for a named +/// expected type. +/// +/// This function may be useful if you wish to simplify errors before +/// presenting them to a user, particularly when using the `one_of` function. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.string |> decode.collapse_errors("MyThing") +/// let result = decode.run(dynamic.int(1000), decoder) +/// assert result == Error([DecodeError("MyThing", "Int", [])]) +/// ``` +/// +pub fn collapse_errors(decoder: Decoder(a), name: String) -> Decoder(a) { + Decoder(function: fn(dynamic_data) { + let #(data, errors) as layer = decoder.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> #(data, decode_error(name, dynamic_data)) + } + }) +} + +/// Create a new decoder based upon the value of a previous decoder. +/// +/// This may be useful to run one previous decoder to use in further decoding. +/// +pub fn then(decoder: Decoder(a), next: fn(a) -> Decoder(b)) -> Decoder(b) { + Decoder(function: fn(dynamic_data) { + let #(data, errors) = decoder.function(dynamic_data) + let decoder = next(data) + let #(data, _) as layer = decoder.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> #(data, errors) + } + }) +} + +/// Create a new decoder from several other decoders. Each of the inner +/// decoders is run in turn, and the value from the first to succeed is used. +/// +/// If no decoder succeeds then the errors from the first decoder are used. +/// If you wish for different errors then you may wish to use the +/// `collapse_errors` or `map_errors` functions. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = decode.one_of(decode.string, or: [ +/// decode.int |> decode.map(int.to_string), +/// decode.float |> decode.map(float.to_string), +/// ]) +/// assert decode.run(dynamic.int(1000), decoder) == Ok("1000") +/// ``` +/// +pub fn one_of( + first: Decoder(a), + or alternatives: List(Decoder(a)), +) -> Decoder(a) { + Decoder(function: fn(dynamic_data) { + let #(_, errors) as layer = first.function(dynamic_data) + case errors { + [] -> layer + [_, ..] -> run_decoders(dynamic_data, layer, alternatives) + } + }) +} + +fn run_decoders( + data: Dynamic, + failure: #(a, List(DecodeError)), + decoders: List(Decoder(a)), +) -> #(a, List(DecodeError)) { + case decoders { + [] -> failure + + [decoder, ..decoders] -> { + let #(_, errors) as layer = decoder.function(data) + case errors { + [] -> layer + [_, ..] -> run_decoders(data, failure, decoders) + } + } + } +} + +/// Define a decoder that always fails. +/// +/// The first parameter is a "placeholder" value, which is some default value that the +/// decoder uses internally in place of the value that would have been produced +/// if the decoder was successful. It doesn't matter what this value is, it is +/// never returned by the decoder or shown to the user, so pick some arbitrary +/// value. If it is an int you might pick `0`, if it is a list you might pick +/// `[]`. +/// +/// The second parameter is the name of the type that has failed to decode. +/// +/// ```gleam +/// decode.failure(User(name: "", score: 0, tags: []), expected: "User") +/// ``` +/// +pub fn failure(placeholder: a, expected name: String) -> Decoder(a) { + Decoder(function: fn(d) { #(placeholder, decode_error(name, d)) }) +} + +/// Create a decoder for a new data type from a decoding function. +/// +/// This function is used for new primitive types. For example, you might +/// define a decoder for Erlang's pid type. +/// +/// A default "placeholder" value is also required to make a decoder. When this +/// decoder is used as part of a larger decoder this placeholder value is used +/// so that the rest of the decoder can continue to run and +/// collect all decoding errors. It doesn't matter what this value is, it is +/// never returned by the decoder or shown to the user, so pick some arbitrary +/// value. If it is an int you might pick `0`, if it is a list you might pick +/// `[]`. +/// +/// If you were to make a decoder for the `Int` type (rather than using the +/// build-in `Int` decoder) you would define it like so: +/// +/// ```gleam +/// pub fn int_decoder() -> decode.Decoder(Int) { +/// let default = "" +/// decode.new_primitive_decoder("Int", int_from_dynamic) +/// } +/// +/// @external(erlang, "my_module", "int_from_dynamic") +/// fn int_from_dynamic(data: Int) -> Result(Int, Int) +/// ``` +/// +/// ```erlang +/// -module(my_module). +/// -export([int_from_dynamic/1]). +/// +/// int_from_dynamic(Data) -> +/// case is_integer(Data) of +/// true -> {ok, Data}; +/// false -> {error, 0} +/// end. +/// ``` +/// +pub fn new_primitive_decoder( + name: String, + decoding_function: fn(Dynamic) -> Result(t, t), +) -> Decoder(t) { + Decoder(function: fn(d) { + case decoding_function(d) { + Ok(t) -> #(t, []) + Error(placeholder) -> #(placeholder, [ + DecodeError(name, dynamic.classify(d), []), + ]) + } + }) +} + +/// Create a decoder that can refer to itself, useful for decoding deeply +/// nested data. +/// +/// Attempting to create a recursive decoder without this function could result +/// in an infinite loop. If you are using `field` or other `use`able functions +/// then you may not need to use this function. +/// +/// ## Examples +/// +/// ```gleam +/// type Nested { +/// Nested(List(Nested)) +/// Value(String) +/// } +/// +/// fn nested_decoder() -> decode.Decoder(Nested) { +/// use <- decode.recursive +/// decode.one_of(decode.string |> decode.map(Value), [ +/// decode.list(nested_decoder()) |> decode.map(Nested), +/// ]) +/// } +/// ``` +/// +pub fn recursive(inner: fn() -> Decoder(a)) -> Decoder(a) { + Decoder(function: fn(data) { + let decoder = inner() + decoder.function(data) + }) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../../gleam_stdlib.mjs", "identity") +fn cast(a: anything) -> Dynamic + +@external(erlang, "gleam_stdlib", "is_null") +@external(javascript, "../../gleam_stdlib.mjs", "is_null") +fn is_null(a: Dynamic) -> Bool diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/float.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/float.gleam new file mode 100644 index 0000000..aa9bd7f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/float.gleam @@ -0,0 +1,621 @@ +//// Functions for working with floats. +//// +//// ## Float representation +//// +//// Floats are represented as 64 bit floating point numbers on both the Erlang +//// and JavaScript runtimes. The floating point behaviour is native to their +//// respective runtimes, so their exact behaviour will be slightly different on +//// the two runtimes. +//// +//// ### Infinity and NaN +//// +//// Under the JavaScript runtime, exceeding the maximum (or minimum) +//// representable value for a floating point value will result in Infinity (or +//// -Infinity). Should you try to divide two infinities you will get NaN as a +//// result. +//// +//// When running on BEAM, exceeding the maximum (or minimum) representable +//// value for a floating point value will raise an error. +//// +//// ## Division by zero +//// +//// Gleam runs on the Erlang virtual machine, which does not follow the IEEE +//// 754 standard for floating point arithmetic and does not have an `Infinity` +//// value. In Erlang division by zero results in a crash, however Gleam does +//// not have partial functions and operators in core so instead division by zero +//// returns zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/order.{type Order} + +/// Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was +/// not possible. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("2.3") == Ok(2.3) +/// ``` +/// +/// ```gleam +/// assert parse("ABC") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_float") +@external(javascript, "../gleam_stdlib.mjs", "parse_float") +pub fn parse(string: String) -> Result(Float, Nil) + +/// Returns the string representation of the provided `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(2.3) == "2.3" +/// ``` +/// +@external(erlang, "gleam_stdlib", "float_to_string") +@external(javascript, "../gleam_stdlib.mjs", "float_to_string") +pub fn to_string(x: Float) -> String + +/// Restricts a float between two bounds. +/// +/// Note: If the `min` argument is larger than the `max` argument then they +/// will be swapped, so the minimum bound is always lower than the maximum +/// bound. +/// +/// +/// ## Examples +/// +/// ```gleam +/// assert clamp(1.2, min: 1.4, max: 1.6) == 1.4 +/// ``` +/// +/// ```gleam +/// assert clamp(1.2, min: 1.4, max: 0.6) == 1.2 +/// ``` +/// +pub fn clamp(x: Float, min min_bound: Float, max max_bound: Float) -> Float { + case min_bound >=. max_bound { + True -> x |> min(min_bound) |> max(max_bound) + False -> x |> min(max_bound) |> max(min_bound) + } +} + +/// Compares two `Float`s, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(2.0, 2.3) == Lt +/// ``` +/// +/// To handle +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) +/// you may use [`loosely_compare`](#loosely_compare) instead. +/// +pub fn compare(a: Float, with b: Float) -> Order { + case a == b { + True -> order.Eq + False -> + case a <. b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two `Float`s within a tolerance, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// assert loosely_compare(5.0, with: 5.3, tolerating: 0.5) == Eq +/// ``` +/// +/// If you want to check only for equality you may use +/// [`loosely_equals`](#loosely_equals) instead. +/// +pub fn loosely_compare( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Order { + let difference = absolute_value(a -. b) + case difference <=. tolerance { + True -> order.Eq + False -> compare(a, b) + } +} + +/// Checks for equality of two `Float`s within a tolerance, +/// returning a `Bool`. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// assert loosely_equals(5.0, with: 5.3, tolerating: 0.5) +/// ``` +/// +/// ```gleam +/// assert !loosely_equals(5.0, with: 5.1, tolerating: 0.1) +/// ``` +/// +pub fn loosely_equals( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Bool { + let difference = absolute_value(a -. b) + difference <=. tolerance +} + +/// Compares two `Float`s, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert min(2.0, 2.3) == 2.0 +/// ``` +/// +pub fn min(a: Float, b: Float) -> Float { + case a <. b { + True -> a + False -> b + } +} + +/// Compares two `Float`s, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert max(2.0, 2.3) == 2.3 +/// ``` +/// +pub fn max(a: Float, b: Float) -> Float { + case a >. b { + True -> a + False -> b + } +} + +/// Rounds the value to the next highest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert ceiling(2.3) == 3.0 +/// ``` +/// +@external(erlang, "math", "ceil") +@external(javascript, "../gleam_stdlib.mjs", "ceiling") +pub fn ceiling(x: Float) -> Float + +/// Rounds the value to the next lowest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert floor(2.3) == 2.0 +/// ``` +/// +@external(erlang, "math", "floor") +@external(javascript, "../gleam_stdlib.mjs", "floor") +pub fn floor(x: Float) -> Float + +/// Rounds the value to the nearest whole number as an `Int`. +/// +/// ## Examples +/// +/// ```gleam +/// assert round(2.3) == 2 +/// ``` +/// +/// ```gleam +/// assert round(2.5) == 3 +/// ``` +/// +@external(erlang, "erlang", "round") +pub fn round(x: Float) -> Int { + case x >=. 0.0 { + True -> js_round(x) + False -> 0 - js_round(negate(x)) + } +} + +@external(javascript, "../gleam_stdlib.mjs", "round") +fn js_round(a: Float) -> Int + +/// Returns the value as an `Int`, truncating all decimal digits. +/// +/// ## Examples +/// +/// ```gleam +/// assert truncate(2.4343434847383438) == 2 +/// ``` +/// +@external(erlang, "erlang", "trunc") +@external(javascript, "../gleam_stdlib.mjs", "truncate") +pub fn truncate(x: Float) -> Int + +/// Converts the value to a given precision as a `Float`. +/// The precision is the number of allowed decimal places. +/// Negative precisions are allowed and force rounding +/// to the nearest tenth, hundredth, thousandth etc. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_precision(2.43434348473, 2) == 2.43 +/// ``` +/// +/// ```gleam +/// assert to_precision(547890.453444, -3) == 548000.0 +/// ``` +/// +pub fn to_precision(x: Float, precision: Int) -> Float { + case precision <= 0 { + True -> { + let factor = do_power(10.0, do_to_float(-precision)) + do_to_float(round(x /. factor)) *. factor + } + False -> { + let factor = do_power(10.0, do_to_float(precision)) + do_to_float(round(x *. factor)) /. factor + } + } +} + +@external(erlang, "erlang", "float") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_to_float(a: Int) -> Float + +/// Returns the absolute value of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert absolute_value(-12.5) == 12.5 +/// ``` +/// +/// ```gleam +/// assert absolute_value(10.2) == 10.2 +/// ``` +/// +pub fn absolute_value(x: Float) -> Float { + case x >=. 0.0 { + True -> x + False -> 0.0 -. x + } +} + +/// Returns the result of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert power(2.0, -1.0) == Ok(0.5) +/// ``` +/// +/// ```gleam +/// assert power(2.0, 2.0) == Ok(4.0) +/// ``` +/// +/// ```gleam +/// assert power(8.0, 1.5) == Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// assert 4.0 |> power(of: 2.0) == Ok(16.0) +/// ``` +/// +/// ```gleam +/// assert power(-1.0, 0.5) == Error(Nil) +/// ``` +/// +pub fn power(base: Float, of exponent: Float) -> Result(Float, Nil) { + let fractional: Bool = ceiling(exponent) -. exponent >. 0.0 + // In the following check: + // 1. If the base is negative and the exponent is fractional then + // return an error as it will otherwise be an imaginary number + // 2. If the base is 0 and the exponent is negative then the expression + // is equivalent to the exponent divided by 0 and an error should be + // returned + case base <. 0.0 && fractional || base == 0.0 && exponent <. 0.0 { + True -> Error(Nil) + False -> Ok(do_power(base, exponent)) + } +} + +@external(erlang, "math", "pow") +@external(javascript, "../gleam_stdlib.mjs", "power") +fn do_power(a: Float, b: Float) -> Float + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert square_root(4.0) == Ok(2.0) +/// ``` +/// +/// ```gleam +/// assert square_root(-16.0) == Error(Nil) +/// ``` +/// +pub fn square_root(x: Float) -> Result(Float, Nil) { + power(x, 0.5) +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(1.0) == -1.0 +/// ``` +/// +pub fn negate(x: Float) -> Float { + -1.0 *. x +} + +/// Sums a list of `Float`s. +/// +/// ## Example +/// +/// ```gleam +/// assert sum([1.0, 2.2, 3.3]) == 6.5 +/// ``` +/// +pub fn sum(numbers: List(Float)) -> Float { + sum_loop(numbers, 0.0) +} + +fn sum_loop(numbers: List(Float), initial: Float) -> Float { + case numbers { + [first, ..rest] -> sum_loop(rest, first +. initial) + [] -> initial + } +} + +/// Multiplies a list of `Float`s and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// assert product([2.5, 3.2, 4.2]) == 33.6 +/// ``` +/// +pub fn product(numbers: List(Float)) -> Float { + product_loop(numbers, 1.0) +} + +fn product_loop(numbers: List(Float), initial: Float) -> Float { + case numbers { + [first, ..rest] -> product_loop(rest, first *. initial) + [] -> initial + } +} + +/// Generates a random float between the given zero (inclusive) and one +/// (exclusive). +/// +/// On Erlang this updates the random state in the process dictionary. +/// See: +/// +/// ## Examples +/// +/// ```gleam +/// random() +/// // -> 0.646355926896028 +/// ``` +/// +@external(erlang, "rand", "uniform") +@external(javascript, "../gleam_stdlib.mjs", "random_uniform") +pub fn random() -> Float + +/// Computes the modulo of a float division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// The computed value will always have the same sign as the `divisor`. +/// +/// ## Examples +/// +/// ```gleam +/// assert modulo(13.3, by: 3.3) == Ok(0.1) +/// ``` +/// +/// ```gleam +/// assert modulo(-13.3, by: 3.3) == Ok(3.2) +/// ``` +/// +/// ```gleam +/// assert modulo(13.3, by: -3.3) == Ok(-3.2) +/// ``` +/// +/// ```gleam +/// assert modulo(-13.3, by: -3.3) == Ok(-0.1) +/// ``` +/// +pub fn modulo(dividend: Float, by divisor: Float) -> Result(Float, Nil) { + case divisor { + 0.0 -> Error(Nil) + _ -> Ok(dividend -. floor(dividend /. divisor) *. divisor) + } +} + +/// Returns division of the inputs as a `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// assert divide(0.0, 1.0) == Ok(0.0) +/// ``` +/// +/// ```gleam +/// assert divide(1.0, 0.0) == Error(Nil) +/// ``` +/// +pub fn divide(a: Float, by b: Float) -> Result(Float, Nil) { + case b { + 0.0 -> Error(Nil) + b -> Ok(a /. b) + } +} + +/// Adds two floats together. +/// +/// It's the function equivalent of the `+.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert add(1.0, 2.0) == 3.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1.0, 2.0, 3.0], 0.0, add) == 6.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> add(2.0) == 5.0 +/// ``` +/// +pub fn add(a: Float, b: Float) -> Float { + a +. b +} + +/// Multiplies two floats together. +/// +/// It's the function equivalent of the `*.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert multiply(2.0, 4.0) == 8.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([2.0, 3.0, 4.0], 1.0, multiply) == 24.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> multiply(2.0) == 6.0 +/// ``` +/// +pub fn multiply(a: Float, b: Float) -> Float { + a *. b +} + +/// Subtracts one float from another. +/// +/// It's the function equivalent of the `-.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert subtract(3.0, 1.0) == 2.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1.0, 2.0, 3.0], 10.0, subtract) == 4.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> subtract(_, 2.0) == 1.0 +/// ``` +/// +/// ```gleam +/// assert 3.0 |> subtract(2.0, _) == -1.0 +/// ``` +/// +pub fn subtract(a: Float, b: Float) -> Float { + a -. b +} + +/// Returns the natural logarithm (base e) of the given `Float` as a `Result`. If the +/// input is less than or equal to 0, returns `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert logarithm(1.0) == Ok(0.0) +/// ``` +/// +/// ```gleam +/// assert logarithm(2.718281828459045) == Ok(1.0) +/// ``` +/// +/// ```gleam +/// assert logarithm(0.0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert logarithm(-1.0) == Error(Nil) +/// ``` +/// +pub fn logarithm(x: Float) -> Result(Float, Nil) { + // In the following check: + // 1. If x is negative then return an error as the natural logarithm + // of a negative number is undefined (would be a complex number) + // 2. If x is 0 then return an error as the natural logarithm of 0 + // approaches negative infinity + case x <=. 0.0 { + True -> Error(Nil) + False -> Ok(do_log(x)) + } +} + +@external(erlang, "math", "log") +@external(javascript, "../gleam_stdlib.mjs", "log") +fn do_log(x: Float) -> Float + +/// Returns e (Euler's number) raised to the power of the given exponent, as +/// a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert exponential(0.0) == Ok(1.0) +/// ``` +/// +/// ```gleam +/// assert exponential(1.0) == Ok(2.718281828459045) +/// ``` +/// +/// ```gleam +/// assert exponential(-1.0) == Ok(0.36787944117144233) +/// ``` +/// +@external(erlang, "math", "exp") +@external(javascript, "../gleam_stdlib.mjs", "exp") +pub fn exponential(x: Float) -> Float diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/function.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/function.gleam new file mode 100644 index 0000000..3537579 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/function.gleam @@ -0,0 +1,5 @@ +/// Takes a single argument and always returns its input value. +/// +pub fn identity(x: a) -> a { + x +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/int.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/int.gleam new file mode 100644 index 0000000..0cf2742 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/int.gleam @@ -0,0 +1,828 @@ +//// Functions for working with integers. +//// +//// ## Division by zero +//// +//// In Erlang division by zero results in a crash, however Gleam does not have +//// partial functions and operators in core so instead division by zero returns +//// zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/float +import gleam/order.{type Order} + +/// Returns the absolute value of the input. +/// +/// ## Examples +/// +/// ```gleam +/// assert absolute_value(-12) == 12 +/// ``` +/// +/// ```gleam +/// assert absolute_value(10) == 10 +/// ``` +/// +pub fn absolute_value(x: Int) -> Int { + case x >= 0 { + True -> x + False -> x * -1 + } +} + +/// Returns the result of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert power(2, -1.0) == Ok(0.5) +/// ``` +/// +/// ```gleam +/// assert power(2, 2.0) == Ok(4.0) +/// ``` +/// +/// ```gleam +/// assert power(8, 1.5) == Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// assert 4 |> power(of: 2.0) == Ok(16.0) +/// ``` +/// +/// ```gleam +/// assert power(-1, 0.5) == Error(Nil) +/// ``` +/// +pub fn power(base: Int, of exponent: Float) -> Result(Float, Nil) { + base + |> to_float + |> float.power(exponent) +} + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// assert square_root(4) == Ok(2.0) +/// ``` +/// +/// ```gleam +/// assert square_root(-16) == Error(Nil) +/// ``` +/// +pub fn square_root(x: Int) -> Result(Float, Nil) { + x + |> to_float + |> float.square_root() +} + +/// Parses a given string as an int if possible. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("2") == Ok(2) +/// ``` +/// +/// ```gleam +/// assert parse("ABC") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_int") +@external(javascript, "../gleam_stdlib.mjs", "parse_int") +pub fn parse(string: String) -> Result(Int, Nil) + +/// Parses a given string as an int in a given base if possible. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert base_parse("10", 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert base_parse("30", 16) == Ok(48) +/// ``` +/// +/// ```gleam +/// assert base_parse("1C", 36) == Ok(48) +/// ``` +/// +/// ```gleam +/// assert base_parse("48", 1) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert base_parse("48", 37) == Error(Nil) +/// ``` +/// +pub fn base_parse(string: String, base: Int) -> Result(Int, Nil) { + case base >= 2 && base <= 36 { + True -> do_base_parse(string, base) + False -> Error(Nil) + } +} + +@external(erlang, "gleam_stdlib", "int_from_base_string") +@external(javascript, "../gleam_stdlib.mjs", "int_from_base_string") +fn do_base_parse(a: String, b: Int) -> Result(Int, Nil) + +/// Prints a given int to a string. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_string(2) == "2" +/// ``` +/// +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "to_string") +pub fn to_string(x: Int) -> String + +/// Prints a given int to a string using the base number provided. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`. +/// For common bases (2, 8, 16, 36), use the `to_baseN` functions. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base_string(2, 2) == Ok("10") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 16) == Ok("30") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 36) == Ok("1C") +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 1) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert to_base_string(48, 37) == Error(Nil) +/// ``` +/// +pub fn to_base_string(x: Int, base: Int) -> Result(String, Nil) { + case base >= 2 && base <= 36 { + True -> Ok(do_to_base_string(x, base)) + False -> Error(Nil) + } +} + +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "int_to_base_string") +fn do_to_base_string(a: Int, b: Int) -> String + +/// Prints a given int to a string using base-2. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base2(2) == "10" +/// ``` +/// +pub fn to_base2(x: Int) -> String { + do_to_base_string(x, 2) +} + +/// Prints a given int to a string using base-8. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base8(15) == "17" +/// ``` +/// +pub fn to_base8(x: Int) -> String { + do_to_base_string(x, 8) +} + +/// Prints a given int to a string using base-16. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base16(48) == "30" +/// ``` +/// +pub fn to_base16(x: Int) -> String { + do_to_base_string(x, 16) +} + +/// Prints a given int to a string using base-36. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_base36(48) == "1C" +/// ``` +/// +pub fn to_base36(x: Int) -> String { + do_to_base_string(x, 36) +} + +/// Takes an int and returns its value as a float. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_float(5) == 5.0 +/// ``` +/// +/// ```gleam +/// assert to_float(0) == 0.0 +/// ``` +/// +/// ```gleam +/// assert to_float(-3) == -3.0 +/// ``` +/// +@external(erlang, "erlang", "float") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn to_float(x: Int) -> Float + +/// Restricts an int between two bounds. +/// +/// Note: If the `min` argument is larger than the `max` argument then they +/// will be swapped, so the minimum bound is always lower than the maximum +/// bound. +/// +/// ## Examples +/// +/// ```gleam +/// assert clamp(40, min: 50, max: 60) == 50 +/// ``` +/// +/// ```gleam +/// assert clamp(40, min: 50, max: 30) == 40 +/// ``` +/// +pub fn clamp(x: Int, min min_bound: Int, max max_bound: Int) -> Int { + case min_bound >= max_bound { + True -> x |> min(min_bound) |> max(max_bound) + False -> x |> min(max_bound) |> max(min_bound) + } +} + +/// Compares two ints, returning an order. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(2, 3) == Lt +/// ``` +/// +/// ```gleam +/// assert compare(4, 3) == Gt +/// ``` +/// +/// ```gleam +/// assert compare(3, 3) == Eq +/// ``` +/// +pub fn compare(a: Int, with b: Int) -> Order { + case a == b { + True -> order.Eq + False -> + case a < b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two ints, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert min(2, 3) == 2 +/// ``` +/// +pub fn min(a: Int, b: Int) -> Int { + case a < b { + True -> a + False -> b + } +} + +/// Compares two ints, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// assert max(2, 3) == 3 +/// ``` +/// +pub fn max(a: Int, b: Int) -> Int { + case a > b { + True -> a + False -> b + } +} + +/// Returns whether the value provided is even. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_even(2) +/// ``` +/// +/// ```gleam +/// assert !is_even(3) +/// ``` +/// +pub fn is_even(x: Int) -> Bool { + x % 2 == 0 +} + +/// Returns whether the value provided is odd. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_odd(3) +/// ``` +/// +/// ```gleam +/// assert !is_odd(2) +/// ``` +/// +pub fn is_odd(x: Int) -> Bool { + x % 2 != 0 +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(1) == -1 +/// ``` +/// +pub fn negate(x: Int) -> Int { + -1 * x +} + +/// Sums a list of ints. +/// +/// ## Example +/// +/// ```gleam +/// assert sum([1, 2, 3]) == 6 +/// ``` +/// +pub fn sum(numbers: List(Int)) -> Int { + sum_loop(numbers, 0) +} + +fn sum_loop(numbers: List(Int), initial: Int) -> Int { + case numbers { + [first, ..rest] -> sum_loop(rest, first + initial) + [] -> initial + } +} + +/// Multiplies a list of ints and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// assert product([2, 3, 4]) == 24 +/// ``` +/// +pub fn product(numbers: List(Int)) -> Int { + product_loop(numbers, 1) +} + +fn product_loop(numbers: List(Int), initial: Int) -> Int { + case numbers { + [first, ..rest] -> product_loop(rest, first * initial) + [] -> initial + } +} + +/// Generates a random int between zero and the given maximum. +/// +/// The lower number is inclusive, the upper number is exclusive. +/// +/// ## Examples +/// +/// ```gleam +/// random(10) +/// // -> 4 +/// ``` +/// +/// ```gleam +/// random(1) +/// // -> 0 +/// ``` +/// +/// ```gleam +/// random(-1) +/// // -> -1 +/// ``` +/// +pub fn random(max: Int) -> Int { + { float.random() *. to_float(max) } + |> float.floor + |> float.round +} + +/// Performs a truncated integer division. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert divide(0, 1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert divide(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert divide(5, 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert divide(-99, 2) == Ok(-49) +/// ``` +/// +pub fn divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend / divisor) + } +} + +/// Computes the remainder of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Most of the time you will want to use the `%` operator instead of this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// assert remainder(3, 2) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert remainder(10, -1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert remainder(13, by: 3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(-13, by: 3) == Ok(-1) +/// ``` +/// +/// ```gleam +/// assert remainder(13, by: -3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert remainder(-13, by: -3) == Ok(-1) +/// ``` +/// +pub fn remainder(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend % divisor) + } +} + +/// Computes the modulo of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Note that this is different from `int.remainder` and `%` in that the +/// computed value will always have the same sign as the `divisor`. +/// +/// ## Examples +/// +/// ```gleam +/// assert modulo(3, 2) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert modulo(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert modulo(10, -1) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert modulo(13, by: 3) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert modulo(-13, by: 3) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert modulo(13, by: -3) == Ok(-2) +/// ``` +/// +pub fn modulo(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + _ -> { + let remainder = dividend % divisor + case remainder * divisor < 0 { + True -> Ok(remainder + divisor) + False -> Ok(remainder) + } + } + } +} + +/// Performs a *floored* integer division, which means that the result will +/// always be rounded towards negative infinity. +/// +/// If you want to perform truncated integer division (rounding towards zero), +/// use `int.divide()` or the `/` operator instead. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert floor_divide(1, 0) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert floor_divide(5, 2) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert floor_divide(6, -4) == Ok(-2) +/// ``` +/// +/// ```gleam +/// assert floor_divide(-99, 2) == Ok(-50) +/// ``` +/// +pub fn floor_divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> + case dividend * divisor < 0 && dividend % divisor != 0 { + True -> Ok(dividend / divisor - 1) + False -> Ok(dividend / divisor) + } + } +} + +/// Adds two integers together. +/// +/// It's the function equivalent of the `+` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert add(1, 2) == 3 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// assert list.fold([1, 2, 3], 0, add) == 6 +/// ``` +/// +/// ```gleam +/// assert 3 |> add(2) == 5 +/// ``` +/// +pub fn add(a: Int, b: Int) -> Int { + a + b +} + +/// Multiplies two integers together. +/// +/// It's the function equivalent of the `*` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert multiply(2, 4) == 8 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([2, 3, 4], 1, multiply) == 24 +/// ``` +/// +/// ```gleam +/// assert 3 |> multiply(2) == 6 +/// ``` +/// +pub fn multiply(a: Int, b: Int) -> Int { + a * b +} + +/// Subtracts one int from another. +/// +/// It's the function equivalent of the `-` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// assert subtract(3, 1) == 2 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// +/// assert list.fold([1, 2, 3], 10, subtract) == 4 +/// ``` +/// +/// ```gleam +/// assert 3 |> subtract(2) == 1 +/// ``` +/// +/// ```gleam +/// assert 3 |> subtract(2, _) == -1 +/// ``` +/// +pub fn subtract(a: Int, b: Int) -> Int { + a - b +} + +/// Calculates the bitwise AND of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "band") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_and") +pub fn bitwise_and(x: Int, y: Int) -> Int + +/// Calculates the bitwise NOT of its argument. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bnot") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_not") +pub fn bitwise_not(x: Int) -> Int + +/// Calculates the bitwise OR of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_or") +pub fn bitwise_or(x: Int, y: Int) -> Int + +/// Calculates the bitwise XOR of its arguments. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bxor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_exclusive_or") +pub fn bitwise_exclusive_or(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic left bitshift. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bsl") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_left") +pub fn bitwise_shift_left(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic right bitshift. +/// +/// Most the time you should use the bit array syntaxes instead of manipulating +/// bits as ints with bitwise functions. +/// +/// ## Target specific behaviour +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. If you need to avoid the +/// overhead of big-ints on JavaScript use bit arrays or another package that +/// provides faster bitwise operations. +/// +@external(erlang, "erlang", "bsr") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_right") +pub fn bitwise_shift_right(x: Int, y: Int) -> Int + +/// Run a function for each int between ints `from` and `to`. +/// +/// `from` is inclusive, and `to` is exclusive. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// range(from: 0, to: 3, with: "", run: fn(acc, i) { +/// acc <> to_string(i) +/// }) +/// == "012" +/// ``` +/// +/// ```gleam +/// assert range(from: 1, to: -2, with: [], run: list.prepend) == [-1, 0, 1] +/// ``` +/// +pub fn range( + from start: Int, + to stop: Int, + with acc: acc, + run reducer: fn(acc, Int) -> acc, +) -> acc { + let increment = case start < stop { + True -> 1 + False -> -1 + } + range_loop(start, stop, increment, acc, reducer) +} + +fn range_loop( + current: Int, + stop: Int, + increment: Int, + acc: acc, + reducer: fn(acc, Int) -> acc, +) -> acc { + case current == stop { + True -> acc + False -> { + let acc = reducer(acc, current) + let current = current + increment + range_loop(current, stop, increment, acc, reducer) + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/io.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/io.gleam new file mode 100644 index 0000000..0d51c9f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/io.gleam @@ -0,0 +1,55 @@ +/// Writes a string to standard output (stdout). +/// +/// If you want your output to be printed on its own line see `println`. +/// +/// ## Example +/// +/// ```gleam +/// assert io.print("Hi mum") == Nil +/// // Hi mum +/// ``` +/// +@external(erlang, "gleam_stdlib", "print") +@external(javascript, "../gleam_stdlib.mjs", "print") +pub fn print(string: String) -> Nil + +/// Writes a string to standard error (stderr). +/// +/// If you want your output to be printed on its own line see `println_error`. +/// +/// ## Example +/// +/// ```gleam +/// assert io.print_error("Hi pop") == Nil +/// // Hi pop +/// ``` +/// +@external(erlang, "gleam_stdlib", "print_error") +@external(javascript, "../gleam_stdlib.mjs", "print_error") +pub fn print_error(string: String) -> Nil + +/// Writes a string to standard output (stdout), appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// assert io.println("Hi mum") == Nil +/// // Hi mum +/// ``` +/// +@external(erlang, "gleam_stdlib", "println") +@external(javascript, "../gleam_stdlib.mjs", "console_log") +pub fn println(string: String) -> Nil + +/// Writes a string to standard error (stderr), appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// assert io.println_error("Hi pop") == Nil +/// // Hi pop +/// ``` +/// +@external(erlang, "gleam_stdlib", "println_error") +@external(javascript, "../gleam_stdlib.mjs", "console_error") +pub fn println_error(string: String) -> Nil diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/list.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/list.gleam new file mode 100644 index 0000000..3b6fa2a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/list.gleam @@ -0,0 +1,2279 @@ +//// Lists are an ordered sequence of elements and are one of the most common +//// data types in Gleam. +//// +//// New elements can be added and removed from the front of a list in +//// constant time, while adding and removing from the end requires traversing +//// and copying the whole list, so keep this in mind when designing your +//// programs. +//// +//// There is a dedicated syntax for prefixing to a list: +//// +//// ```gleam +//// let new_list = [1, 2, ..existing_list] +//// ``` +//// +//// And a matching syntax for getting the first elements of a list: +//// +//// ```gleam +//// case list { +//// [first_element, ..rest] -> first_element +//// _ -> "this pattern matches when the list is empty" +//// } +//// ``` +//// + +import gleam/dict.{type Dict} +import gleam/float +import gleam/int +import gleam/order.{type Order} + +/// Counts the number of elements in a given list. +/// +/// This function has to traverse the list to determine the number of elements, +/// so it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// assert length([]) == 0 +/// ``` +/// +/// ```gleam +/// assert length([1]) == 1 +/// ``` +/// +/// ```gleam +/// assert length([1, 2]) == 2 +/// ``` +/// +@external(erlang, "erlang", "length") +pub fn length(of list: List(a)) -> Int { + length_loop(list, 0) +} + +fn length_loop(list: List(a), count: Int) -> Int { + case list { + [_, ..list] -> length_loop(list, count + 1) + [] -> count + } +} + +/// Counts the number of elements in a given list satisfying a given predicate. +/// +/// This function has to traverse the list to determine the number of elements, +/// so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert count([], fn(a) { a > 0 }) == 0 +/// ``` +/// +/// ```gleam +/// assert count([1], fn(a) { a > 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert count([1, 2, 3], int.is_odd) == 2 +/// ``` +/// +pub fn count(list: List(a), where predicate: fn(a) -> Bool) -> Int { + count_loop(list, predicate, 0) +} + +fn count_loop(list: List(a), predicate: fn(a) -> Bool, acc: Int) -> Int { + case list { + [] -> acc + [first, ..rest] -> + case predicate(first) { + True -> count_loop(rest, predicate, acc + 1) + False -> count_loop(rest, predicate, acc) + } + } +} + +/// Creates a new list from a given list containing the same elements but in the +/// opposite order. +/// +/// This function has to traverse the list to create the new reversed list, so +/// it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// assert reverse([]) == [] +/// ``` +/// +/// ```gleam +/// assert reverse([1]) == [1] +/// ``` +/// +/// ```gleam +/// assert reverse([1, 2]) == [2, 1] +/// ``` +/// +@external(erlang, "lists", "reverse") +pub fn reverse(list: List(a)) -> List(a) { + reverse_and_prepend(list, []) +} + +/// Reverses a list and prepends it to another list. +/// This function runs in linear time, proportional to the length of the list +/// to prepend. +/// +@external(erlang, "lists", "reverse") +fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { + case prefix { + [] -> suffix + [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) + } +} + +/// Determines whether or not the list is empty. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_empty([]) +/// ``` +/// +/// ```gleam +/// assert !is_empty([1]) +/// ``` +/// +/// ```gleam +/// assert !is_empty([1, 1]) +/// ``` +/// +pub fn is_empty(list: List(a)) -> Bool { + list == [] +} + +/// Determines whether or not a given element exists within a given list. +/// +/// This function traverses the list to find the element, so it runs in linear +/// time. +/// +/// ## Examples +/// +/// ```gleam +/// assert !contains([], any: 0) +/// ``` +/// +/// ```gleam +/// assert [0] |> contains(any: 0) +/// ``` +/// +/// ```gleam +/// assert !contains([1], any: 0) +/// ``` +/// +/// ```gleam +/// assert !contains([1, 1], any: 0) +/// ``` +/// +/// ```gleam +/// assert [1, 0] |> contains(any: 0) +/// ``` +/// +pub fn contains(list: List(a), any elem: a) -> Bool { + case list { + [] -> False + [first, ..] if first == elem -> True + [_, ..rest] -> contains(rest, elem) + } +} + +/// Gets the first element from the start of the list, if there is one. +/// +/// ## Examples +/// +/// ```gleam +/// assert first([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert first([0]) == Ok(0) +/// ``` +/// +/// ```gleam +/// assert first([1, 2]) == Ok(1) +/// ``` +/// +pub fn first(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..] -> Ok(first) + } +} + +/// Returns the list minus the first element. If the list is empty, `Error(Nil)` is +/// returned. +/// +/// This function runs in constant time and does not make a copy of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert rest([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert rest([0]) == Ok([]) +/// ``` +/// +/// ```gleam +/// assert rest([1, 2]) == Ok([2]) +/// ``` +/// +pub fn rest(list: List(a)) -> Result(List(a), Nil) { + case list { + [] -> Error(Nil) + [_, ..rest] -> Ok(rest) + } +} + +/// Groups the elements from the given list by the given key function. +/// +/// Does not preserve the initial value order. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/dict +/// +/// assert +/// [Ok(3), Error("Wrong"), Ok(200), Ok(73)] +/// |> group(by: fn(i) { +/// case i { +/// Ok(_) -> "Successful" +/// Error(_) -> "Failed" +/// } +/// }) +/// |> dict.to_list +/// == [ +/// #("Failed", [Error("Wrong")]), +/// #("Successful", [Ok(73), Ok(200), Ok(3)]) +/// ] +/// ``` +/// +/// ```gleam +/// import gleam/dict +/// +/// assert group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 }) +/// |> dict.to_list +/// == [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])] +/// ``` +/// +pub fn group(list: List(v), by key: fn(v) -> k) -> Dict(k, List(v)) { + dict.group(key, list) +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert filter([2, 4, 6, 1], fn(x) { x > 2 }) == [4, 6] +/// ``` +/// +/// ```gleam +/// assert filter([2, 4, 6, 1], fn(x) { x > 6 }) == [] +/// ``` +/// +pub fn filter(list: List(a), keeping predicate: fn(a) -> Bool) -> List(a) { + filter_loop(list, predicate, []) +} + +fn filter_loop(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let new_acc = case fun(first) { + True -> [first, ..acc] + False -> acc + } + filter_loop(rest, fun, new_acc) + } + } +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `Ok(_)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert filter_map([2, 4, 6, 1], Error) == [] +/// ``` +/// +/// ```gleam +/// assert filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) == [3, 5, 7, 2] +/// ``` +/// +pub fn filter_map(list: List(a), with fun: fn(a) -> Result(b, e)) -> List(b) { + filter_map_loop(list, fun, []) +} + +fn filter_map_loop( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let new_acc = case fun(first) { + Ok(first) -> [first, ..acc] + Error(_) -> acc + } + filter_map_loop(rest, fun, new_acc) + } + } +} + +/// Returns a new list containing the results of applying the supplied function to each element. +/// +/// ## Examples +/// +/// ```gleam +/// assert map([2, 4, 6], fn(x) { x * 2 }) == [4, 8, 12] +/// ``` +/// +pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { + map_loop(list, fun, []) +} + +fn map_loop(list: List(a), fun: fn(a) -> b, acc: List(b)) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> map_loop(rest, fun, [fun(first), ..acc]) + } +} + +/// Combines two lists into a single list using the given function. +/// +/// If a list is longer than the other, the extra elements are dropped. +/// +/// ## Examples +/// +/// ```gleam +/// assert map2([1, 2, 3], [4, 5, 6], fn(x, y) { x + y }) == [5, 7, 9] +/// ``` +/// +/// ```gleam +/// assert map2([1, 2], ["a", "b", "c"], fn(i, x) { #(i, x) }) +/// == [#(1, "a"), #(2, "b")] +/// ``` +/// +pub fn map2(list1: List(a), list2: List(b), with fun: fn(a, b) -> c) -> List(c) { + map2_loop(list1, list2, fun, []) +} + +fn map2_loop( + list1: List(a), + list2: List(b), + fun: fn(a, b) -> c, + acc: List(c), +) -> List(c) { + case list1, list2 { + [], _ | _, [] -> reverse(acc) + [a, ..as_], [b, ..bs] -> map2_loop(as_, bs, fun, [fun(a, b), ..acc]) + } +} + +/// Similar to `map` but also lets you pass around an accumulated value. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// map_fold( +/// over: [1, 2, 3], +/// from: 100, +/// with: fn(memo, i) { #(memo + i, i * 2) } +/// ) +/// == #(106, [2, 4, 6]) +/// ``` +/// +pub fn map_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> #(acc, b), +) -> #(acc, List(b)) { + map_fold_loop(list, fun, initial, []) +} + +fn map_fold_loop( + list: List(a), + fun: fn(acc, a) -> #(acc, b), + acc: acc, + list_acc: List(b), +) -> #(acc, List(b)) { + case list { + [] -> #(acc, reverse(list_acc)) + [first, ..rest] -> { + let #(acc, first) = fun(acc, first) + map_fold_loop(rest, fun, acc, [first, ..list_acc]) + } + } +} + +/// Similar to `map`, but the supplied function will also be passed the index +/// of the element being mapped as an additional argument. +/// +/// The index starts at 0, so the first element is 0, the second is 1, and so +/// on. +/// +/// ## Examples +/// +/// ```gleam +/// assert index_map(["a", "b"], fn(x, i) { #(i, x) }) == [#(0, "a"), #(1, "b")] +/// ``` +/// +pub fn index_map(list: List(a), with fun: fn(a, Int) -> b) -> List(b) { + index_map_loop(list, fun, 0, []) +} + +fn index_map_loop( + list: List(a), + fun: fn(a, Int) -> b, + index: Int, + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [first, ..rest] -> { + let acc = [fun(first, index), ..acc] + index_map_loop(rest, fun, index + 1, acc) + } + } +} + +/// Takes a function that returns a `Result` and applies it to each element in a +/// given list in turn. +/// +/// If the function returns `Ok(new_value)` for all elements in the list then a +/// list of the new values is returned. +/// +/// If the function returns `Error(reason)` for any of the elements then it is +/// returned immediately. None of the elements in the list are processed after +/// one returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert try_map([1, 2, 3], fn(x) { Ok(x + 2) }) == Ok([3, 4, 5]) +/// ``` +/// +/// ```gleam +/// assert try_map([1, 2, 3], fn(_) { Error(0) }) == Error(0) +/// ``` +/// +/// ```gleam +/// assert try_map([[1], [2, 3]], first) == Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// assert try_map([[1], [], [2]], first) == Error(Nil) +/// ``` +/// +pub fn try_map( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(List(b), e) { + try_map_loop(list, fun, []) +} + +fn try_map_loop( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> Result(List(b), e) { + case list { + [] -> Ok(reverse(acc)) + [first, ..rest] -> + case fun(first) { + Ok(first) -> try_map_loop(rest, fun, [first, ..acc]) + Error(error) -> Error(error) + } + } +} + +/// Returns a list that is the given list with up to the given number of +/// elements removed from the front of the list. +/// +/// If the list has less than the number of elements an empty list is +/// returned. +/// +/// This function runs in linear time but does not copy the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop([1, 2, 3, 4], 2) == [3, 4] +/// ``` +/// +/// ```gleam +/// assert drop([1, 2, 3, 4], 9) == [] +/// ``` +/// +pub fn drop(from list: List(a), up_to n: Int) -> List(a) { + case n <= 0 { + True -> list + False -> + case list { + [] -> [] + [_, ..rest] -> drop(rest, n - 1) + } + } +} + +/// Returns a list containing the first given number of elements from the given +/// list. +/// +/// If the list has less than the number of elements then the full list is +/// returned. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert take([1, 2, 3, 4], 2) == [1, 2] +/// ``` +/// +/// ```gleam +/// assert take([1, 2, 3, 4], 9) == [1, 2, 3, 4] +/// ``` +/// +pub fn take(from list: List(a), up_to n: Int) -> List(a) { + take_loop(list, n, []) +} + +fn take_loop(list: List(a), n: Int, acc: List(a)) -> List(a) { + case n <= 0 { + True -> reverse(acc) + False -> + case list { + [] -> reverse(acc) + [first, ..rest] -> take_loop(rest, n - 1, [first, ..acc]) + } + } +} + +/// Returns a new empty list. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() == [] +/// ``` +/// +pub fn new() -> List(a) { + [] +} + +/// Returns the given item wrapped in a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert wrap(1) == [1] +/// ``` +/// +/// ```gleam +/// assert wrap(["a", "b", "c"]) == [["a", "b", "c"]] +/// ``` +/// +/// ```gleam +/// assert wrap([[]]) == [[[]]] +/// ``` +/// +/// +pub fn wrap(item: a) -> List(a) { + [item] +} + +/// Joins one list onto the end of another. +/// +/// This function runs in linear time, and it traverses and copies the first +/// list. +/// +/// ## Examples +/// +/// ```gleam +/// assert append([1, 2], [3]) == [1, 2, 3] +/// ``` +/// +@external(erlang, "lists", "append") +pub fn append(first: List(a), second: List(a)) -> List(a) { + append_loop(reverse(first), second) +} + +fn append_loop(first: List(a), second: List(a)) -> List(a) { + case first { + [] -> second + [first, ..rest] -> append_loop(rest, [first, ..second]) + } +} + +/// Prefixes an item to a list. This can also be done using the dedicated +/// syntax instead. +/// +/// ```gleam +/// let existing_list = [2, 3, 4] +/// assert [1, ..existing_list] == [1, 2, 3, 4] +/// ``` +/// +/// ```gleam +/// let existing_list = [2, 3, 4] +/// assert prepend(to: existing_list, this: 1) == [1, 2, 3, 4] +/// ``` +/// +pub fn prepend(to list: List(a), this item: a) -> List(a) { + [item, ..list] +} + +/// Joins a list of lists into a single list. +/// +/// This function traverses all elements twice on the JavaScript target. +/// This function traverses all elements once on the Erlang target. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten([[1], [2, 3], []]) == [1, 2, 3] +/// ``` +/// +@external(erlang, "lists", "append") +pub fn flatten(lists: List(List(a))) -> List(a) { + flatten_loop(lists, []) +} + +fn flatten_loop(lists: List(List(a)), acc: List(a)) -> List(a) { + case lists { + [] -> reverse(acc) + [list, ..further_lists] -> + flatten_loop(further_lists, reverse_and_prepend(list, to: acc)) + } +} + +/// Maps the list with the given function into a list of lists, and then flattens it. +/// +/// ## Examples +/// +/// ```gleam +/// assert flat_map([2, 4, 6], fn(x) { [x, x + 1] }) == [2, 3, 4, 5, 6, 7] +/// ``` +/// +pub fn flat_map(over list: List(a), with fun: fn(a) -> List(b)) -> List(b) { + flatten(map(list, fun)) +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from left to right. +/// +/// `fold([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 1), 2), 3)`. +/// +/// This function runs in linear time. +/// +pub fn fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [first, ..rest] -> fold(rest, fun(initial, first), fun) + } +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from right to left. +/// +/// `fold_right([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 3), 2), 1)`. +/// +/// This function runs in linear time. +/// +/// Unlike `fold` this function is not tail recursive. Where possible use +/// `fold` instead as it will use less memory. +/// +pub fn fold_right( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [first, ..rest] -> fun(fold_right(rest, initial, fun), first) + } +} + +/// Like `fold` but the folding function also receives the index of the current element. +/// +/// ## Examples +/// +/// ```gleam +/// assert ["a", "b", "c"] +/// |> index_fold("", fn(acc, item, index) { +/// acc <> int.to_string(index) <> ":" <> item <> " " +/// }) +/// == "0:a 1:b 2:c" +/// ``` +/// +/// ```gleam +/// assert [10, 20, 30] +/// |> index_fold(0, fn(acc, item, index) { acc + item * index }) +/// == 80 +/// ``` +/// +pub fn index_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a, Int) -> acc, +) -> acc { + index_fold_loop(list, initial, fun, 0) +} + +fn index_fold_loop( + over: List(a), + acc: acc, + with: fn(acc, a, Int) -> acc, + index: Int, +) -> acc { + case over { + [] -> acc + [first, ..rest] -> + index_fold_loop(rest, with(acc, first, index), with, index + 1) + } +} + +/// A variant of fold that might fail. +/// +/// The folding function should return `Result(accumulator, error)`. +/// If the returned value is `Ok(accumulator)` try_fold will try the next value in the list. +/// If the returned value is `Error(error)` try_fold will stop and return that error. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4] +/// |> try_fold(0, fn(acc, i) { +/// case i < 3 { +/// True -> Ok(acc + i) +/// False -> Error(Nil) +/// } +/// }) +/// == Error(Nil) +/// ``` +/// +pub fn try_fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> Result(acc, e), +) -> Result(acc, e) { + case list { + [] -> Ok(initial) + [first, ..rest] -> + case fun(initial, first) { + Ok(result) -> try_fold(rest, result, fun) + Error(_) as error -> error + } + } +} + +pub type ContinueOrStop(a) { + Continue(a) + Stop(a) +} + +/// A variant of fold that allows to stop folding earlier. +/// +/// The folding function should return `ContinueOrStop(accumulator)`. +/// If the returned value is `Continue(accumulator)` fold_until will try the next value in the list. +/// If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4] +/// |> fold_until(0, fn(acc, i) { +/// case i < 3 { +/// True -> Continue(acc + i) +/// False -> Stop(acc) +/// } +/// }) +/// == 3 +/// ``` +/// +pub fn fold_until( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> ContinueOrStop(acc), +) -> acc { + case list { + [] -> initial + [first, ..rest] -> + case fun(initial, first) { + Continue(next_accumulator) -> fold_until(rest, next_accumulator, fun) + Stop(b) -> b + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `True`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// assert find([1, 2, 3], fn(x) { x > 2 }) == Ok(3) +/// ``` +/// +/// ```gleam +/// assert find([1, 2, 3], fn(x) { x > 4 }) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert find([], fn(_) { True }) == Error(Nil) +/// ``` +/// +pub fn find( + in list: List(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> + case is_desired(first) { + True -> Ok(first) + False -> find(in: rest, one_that: is_desired) + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `Ok(new_value)`, then returns the wrapped `new_value`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// assert find_map([[], [2], [3]], first) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert find_map([[], []], first) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert find_map([], first) == Error(Nil) +/// ``` +/// +pub fn find_map( + in list: List(a), + with fun: fn(a) -> Result(b, c), +) -> Result(b, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> + case fun(first) { + Ok(first) -> Ok(first) + Error(_) -> find_map(in: rest, with: fun) + } + } +} + +/// Returns `True` if the given function returns `True` for all the elements in +/// the given list. If the function returns `False` for any of the elements it +/// immediately returns `False` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert all([4, 5], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert !all([4, 3], fn(x) { x > 3 }) +/// ``` +/// +pub fn all(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> True + [first, ..rest] -> + case predicate(first) { + True -> all(rest, predicate) + False -> False + } + } +} + +/// Returns `True` if the given function returns `True` for any the elements in +/// the given list. If the function returns `True` for any of the elements it +/// immediately returns `True` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert !any([], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert any([4, 5], fn(x) { x > 3 }) +/// ``` +/// +/// ```gleam +/// assert any([4, 3], fn(x) { x > 4 }) +/// ``` +/// +/// ```gleam +/// assert any([3, 4], fn(x) { x > 3 }) +/// ``` +/// +pub fn any(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> False + [first, ..rest] -> + case predicate(first) { + True -> True + False -> any(rest, predicate) + } + } +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, the remaining elements from +/// the longer list are not used. +/// +/// ## Examples +/// +/// ```gleam +/// assert zip([], []) == [] +/// ``` +/// +/// ```gleam +/// assert zip([1, 2], [3]) == [#(1, 3)] +/// ``` +/// +/// ```gleam +/// assert zip([1], [3, 4]) == [#(1, 3)] +/// ``` +/// +/// ```gleam +/// assert zip([1, 2], [3, 4]) == [#(1, 3), #(2, 4)] +/// ``` +/// +pub fn zip(list: List(a), with other: List(b)) -> List(#(a, b)) { + zip_loop(list, other, []) +} + +fn zip_loop(one: List(a), other: List(b), acc: List(#(a, b))) -> List(#(a, b)) { + case one, other { + [first_one, ..rest_one], [first_other, ..rest_other] -> + zip_loop(rest_one, rest_other, [#(first_one, first_other), ..acc]) + _, _ -> reverse(acc) + } +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, an `Error` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// assert strict_zip([], []) == Ok([]) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1, 2], [3]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1], [3, 4]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert strict_zip([1, 2], [3, 4]) == Ok([#(1, 3), #(2, 4)]) +/// ``` +/// +pub fn strict_zip( + list: List(a), + with other: List(b), +) -> Result(List(#(a, b)), Nil) { + strict_zip_loop(list, other, []) +} + +fn strict_zip_loop( + one: List(a), + other: List(b), + acc: List(#(a, b)), +) -> Result(List(#(a, b)), Nil) { + case one, other { + [], [] -> Ok(reverse(acc)) + [], _ | _, [] -> Error(Nil) + [first_one, ..rest_one], [first_other, ..rest_other] -> + strict_zip_loop(rest_one, rest_other, [#(first_one, first_other), ..acc]) + } +} + +/// Takes a single list of 2-element tuples and returns two lists. +/// +/// ## Examples +/// +/// ```gleam +/// assert unzip([#(1, 2), #(3, 4)]) == #([1, 3], [2, 4]) +/// ``` +/// +/// ```gleam +/// assert unzip([]) == #([], []) +/// ``` +/// +pub fn unzip(input: List(#(a, b))) -> #(List(a), List(b)) { + unzip_loop(input, [], []) +} + +fn unzip_loop( + input: List(#(a, b)), + one: List(a), + other: List(b), +) -> #(List(a), List(b)) { + case input { + [] -> #(reverse(one), reverse(other)) + [#(first_one, first_other), ..rest] -> + unzip_loop(rest, [first_one, ..one], [first_other, ..other]) + } +} + +/// Inserts a given value between each existing element in a given list. +/// +/// This function runs in linear time and copies the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert intersperse([1, 1, 1], 2) == [1, 2, 1, 2, 1] +/// ``` +/// +/// ```gleam +/// assert intersperse([], 2) == [] +/// ``` +/// +pub fn intersperse(list: List(a), with elem: a) -> List(a) { + case list { + [] | [_] -> list + [first, ..rest] -> intersperse_loop(rest, elem, [first]) + } +} + +fn intersperse_loop(list: List(a), separator: a, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + intersperse_loop(rest, separator, [first, separator, ..acc]) + } +} + +/// Removes any duplicate elements from a given list. +/// +/// This function returns in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert unique([1, 1, 1, 4, 7, 3, 3, 4]) == [1, 4, 7, 3] +/// ``` +/// +pub fn unique(list: List(a)) -> List(a) { + unique_loop(list, dict.new(), []) +} + +fn unique_loop(list: List(a), seen: Dict(a, Nil), acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + case dict.has_key(seen, first) { + True -> unique_loop(rest, seen, acc) + False -> + unique_loop(rest, dict.insert(seen, first, Nil), [first, ..acc]) + } + } +} + +/// Sorts from smallest to largest based upon the ordering specified by a given +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) == [1, 2, 3, 4, 4, 5, 6] +/// ``` +/// +pub fn sort(list: List(a), by compare: fn(a, a) -> Order) -> List(a) { + // This is a natural, tail recursive, stable merge sort: + // - natural: it is very efficient if you call it on a list that is already + // (pre)sorted because it works on slices of the original list. + // - tail recursive: the stack won't grow linearly with the size of the list. + // - stable: if two items are considered to be equal then their original + // relative order is preserved. + case list { + // If the list has zero/one item then it's already sorted. + [] -> [] + [x] -> [x] + + // Otherwise the algorithm works as follow: we split the list in sequences + // of already sorted values as they appear in the list and then we merge + // those together two by two using `merge_all`. + [x, y, ..rest] -> { + // We need to compare the first two items to properly call `sequences` + // with the correct initial values. If the second item is <= than the + // first, then we know we'll start by growing a descending sequence + // (and an ascending one in the opposite case). + let direction = case compare(x, y) { + order.Lt | order.Eq -> Ascending + order.Gt -> Descending + } + + // `sequences` produces sequences in ascending order so we call the + // `merge_all` function saying it to expect all sequences to be sorted + // that way. + let sequences = sequences(rest, compare, [x], direction, y, []) + merge_all(sequences, Ascending, compare) + } + } +} + +type Sorting { + Ascending + Descending +} + +/// Given a list it returns slices of it that are locally sorted in ascending +/// order. +/// +/// Imagine you have this list: +/// +/// ``` +/// [1, 2, 3, 2, 1, 0] +/// ^^^^^^^ ^^^^^^^ This is a slice in descending order +/// | +/// | This is a slice that is sorted in ascending order +/// ``` +/// +/// So the produced result will contain these two slices, each one sorted in +/// ascending order: `[[1, 2, 3], [0, 1, 2]]`. +/// +/// - `growing` is an accumulator with the current slice being grown +/// - `direction` is the growing direction of the slice being grown, it could +/// either be ascending or strictly descending +/// - `prev` is the previous element that needs to be added to the growing slice +/// it is carried around to check whether we have to keep growing the current +/// slice or not +/// - `acc` is the accumulator containing the slices sorted in ascending order +/// +fn sequences( + list: List(a), + compare: fn(a, a) -> Order, + growing: List(a), + direction: Sorting, + prev: a, + acc: List(List(a)), +) -> List(List(a)) { + // First of all we must not forget to add the previous element to the + // currently growing slice. + let growing = [prev, ..growing] + + case list { + [] -> + case direction { + // Notice how we have to reverse the accumulator we're growing: since + // we always add items to the head, `growing` is built in the opposite + // sorting order of what it actually is in the original list. + Ascending -> [reverse(growing), ..acc] + Descending -> [growing, ..acc] + } + + [new, ..rest] -> + case compare(prev, new), direction { + // In case the new element respects the ordering of the growing + // sequence, then we just keep growing it. + // Notice how a growing sequence is weakly growing (that is it can have + // consecutive equal items) while a decreasing sequence is strictly + // decreasing (no consecutive equal items), this is needed to make the + // algorithm stable! + order.Gt, Descending | order.Lt, Ascending | order.Eq, Ascending -> + sequences(rest, compare, growing, direction, new, acc) + + // We were growing an ascending (descending) sequence and the new item + // is smaller (bigger) than the previous one, this means we have to stop + // growing this sequence and start with a new one whose first item will + // be the one we just found. + order.Gt, Ascending | order.Lt, Descending | order.Eq, Descending -> { + let acc = case direction { + Ascending -> [reverse(growing), ..acc] + Descending -> [growing, ..acc] + } + case rest { + // The list is over so we just create a sequence containing the last + // item we saw and add it to the accumulator before returning it. + [] -> [[new], ..acc] + + // If the list is not over we have a peek at the next item to decide + // in which direction is growing the new sequence and make the + // recursive call with the appropriate arguments. + [next, ..rest] -> { + let direction = case compare(new, next) { + order.Lt | order.Eq -> Ascending + order.Gt -> Descending + } + sequences(rest, compare, [new], direction, next, acc) + } + } + } + } + } +} + +/// Given some some sorted sequences (assumed to be sorted in `direction`) it +/// merges them all together until we're left with just a list sorted in +/// ascending order. +/// +fn merge_all( + sequences: List(List(a)), + direction: Sorting, + compare: fn(a, a) -> Order, +) -> List(a) { + case sequences, direction { + [], _ -> [] + + // If we have a single list in ascending order then we're done. + [sequence], Ascending -> sequence + + // If we have a single list in descending order, we reverse it to make sure + // it's in ascending order and we're done. + [sequence], Descending -> reverse(sequence) + + // Merging together sequences that are in ascending (descending) order + // reverses their order, so the recursive call will assume to be merging + // lists sorted in the opposite order! + _, Ascending -> { + let sequences = merge_ascending_pairs(sequences, compare, []) + merge_all(sequences, Descending, compare) + } + + _, Descending -> { + let sequences = merge_descending_pairs(sequences, compare, []) + merge_all(sequences, Ascending, compare) + } + } +} + +/// Given a list of ascending lists, it merges adjacent pairs into a single +/// descending list, halving their number. +/// It returns a list of the remaining descending lists. +/// +fn merge_ascending_pairs( + sequences: List(List(a)), + compare: fn(a, a) -> Order, + acc: List(List(a)), +) { + case sequences { + [] -> reverse(acc) + + // Beware, if we have just one item left we must reverse it: we take + // ascending lists as input and have to return descending ones. + // If we returned it like it is it would be sorted in ascending order. + [sequence] -> reverse([reverse(sequence), ..acc]) + + [ascending1, ascending2, ..rest] -> { + let descending = merge_ascendings(ascending1, ascending2, compare, []) + merge_ascending_pairs(rest, compare, [descending, ..acc]) + } + } +} + +/// This is the same as merge_ascending_pairs but flipped for descending lists. +/// +fn merge_descending_pairs( + sequences: List(List(a)), + compare: fn(a, a) -> Order, + acc: List(List(a)), +) { + case sequences { + [] -> reverse(acc) + + [sequence] -> reverse([reverse(sequence), ..acc]) + + [descending1, descending2, ..rest] -> { + let ascending = merge_descendings(descending1, descending2, compare, []) + merge_descending_pairs(rest, compare, [ascending, ..acc]) + } + } +} + +/// Merges two lists sorted in ascending order into a single list sorted in +/// descending order according to the given comparator function. +/// +/// This reversing of the sort order is not avoidable if we want to implement +/// merge as a tail recursive function. We could reverse the accumulator before +/// returning it but that would end up being less efficient; so the merging +/// algorithm has to play around this. +/// +fn merge_ascendings( + list1: List(a), + list2: List(a), + compare: fn(a, a) -> Order, + acc: List(a), +) -> List(a) { + case list1, list2 { + [], list | list, [] -> reverse_and_prepend(list, acc) + + [first1, ..rest1], [first2, ..rest2] -> + case compare(first1, first2) { + order.Lt -> merge_ascendings(rest1, list2, compare, [first1, ..acc]) + order.Gt | order.Eq -> + merge_ascendings(list1, rest2, compare, [first2, ..acc]) + } + } +} + +/// This is exactly the same as merge_ascendings but mirrored: it merges two +/// lists sorted in descending order into a single list sorted in ascending +/// order according to the given comparator function. +/// +/// This reversing of the sort order is not avoidable if we want to implement +/// merge as a tail recursive function. We could reverse the accumulator before +/// returning it but that would end up being less efficient; so the merging +/// algorithm has to play around this. +/// +fn merge_descendings( + list1: List(a), + list2: List(a), + compare: fn(a, a) -> Order, + acc: List(a), +) -> List(a) { + case list1, list2 { + [], list | list, [] -> reverse_and_prepend(list, acc) + [first1, ..rest1], [first2, ..rest2] -> + case compare(first1, first2) { + order.Lt -> merge_descendings(list1, rest2, compare, [first2, ..acc]) + order.Gt | order.Eq -> + merge_descendings(rest1, list2, compare, [first1, ..acc]) + } + } +} + +/// Builds a list of a given value a given number of times. +/// +/// ## Examples +/// +/// ```gleam +/// assert repeat("a", times: 0) == [] +/// ``` +/// +/// ```gleam +/// assert repeat("a", times: 5) == ["a", "a", "a", "a", "a"] +/// ``` +/// +pub fn repeat(item a: a, times times: Int) -> List(a) { + repeat_loop(a, times, []) +} + +fn repeat_loop(item: a, times: Int, acc: List(a)) -> List(a) { + case times <= 0 { + True -> acc + False -> repeat_loop(item, times - 1, [item, ..acc]) + } +} + +/// Splits a list in two before the given index. +/// +/// If the list is not long enough to have the given index the before list will +/// be the input list, and the after list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 0) == #([], [6, 7, 8, 9]) +/// ``` +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 2) == #([6, 7], [8, 9]) +/// ``` +/// +/// ```gleam +/// assert split([6, 7, 8, 9], 4) == #([6, 7, 8, 9], []) +/// ``` +/// +pub fn split(list list: List(a), at index: Int) -> #(List(a), List(a)) { + split_loop(list, index, []) +} + +fn split_loop(list: List(a), n: Int, taken: List(a)) -> #(List(a), List(a)) { + case n <= 0 { + True -> #(reverse(taken), list) + False -> + case list { + [] -> #(reverse(taken), []) + [first, ..rest] -> split_loop(rest, n - 1, [first, ..taken]) + } + } +} + +/// Splits a list in two before the first element that a given function returns +/// `False` for. +/// +/// If the function returns `True` for all elements the first list will be the +/// input list, and the second list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 }) +/// == #([1, 2, 3], [4, 5]) +/// ``` +/// +/// ```gleam +/// assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 }) +/// == #([1, 2, 3, 4, 5], []) +/// ``` +/// +pub fn split_while( + list list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> #(List(a), List(a)) { + split_while_loop(list, predicate, []) +} + +fn split_while_loop( + list: List(a), + f: fn(a) -> Bool, + acc: List(a), +) -> #(List(a), List(a)) { + case list { + [] -> #(reverse(acc), []) + [first, ..rest] -> + case f(first) { + True -> split_while_loop(rest, f, [first, ..acc]) + False -> #(reverse(acc), list) + } + } +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element and returns the second element. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "a") == Ok(0) +/// ``` +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "b") == Ok(1) +/// ``` +/// +/// ```gleam +/// assert key_find([#("a", 0), #("b", 1)], "c") == Error(Nil) +/// ``` +/// +pub fn key_find( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> Result(v, Nil) { + find_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, finds all tuples that have a given +/// key as the first element and returns the second element. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_filter([#("a", 0), #("b", 1), #("a", 2)], "a") == [0, 2] +/// ``` +/// +/// ```gleam +/// assert key_filter([#("a", 0), #("b", 1)], "c") == [] +/// ``` +/// +pub fn key_filter( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> List(v) { + filter_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element. This function will return the second element +/// of the found tuple and list with tuple removed. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "a") == Ok(#(0, [#("b", 1)])) +/// ``` +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "b") == Ok(#(1, [#("a", 0)])) +/// ``` +/// +/// ```gleam +/// assert key_pop([#("a", 0), #("b", 1)], "c") == Error(Nil) +/// ``` +/// +pub fn key_pop(list: List(#(k, v)), key: k) -> Result(#(v, List(#(k, v))), Nil) { + key_pop_loop(list, key, []) +} + +fn key_pop_loop( + list: List(#(k, v)), + key: k, + checked: List(#(k, v)), +) -> Result(#(v, List(#(k, v))), Nil) { + case list { + [] -> Error(Nil) + [#(k, v), ..rest] if k == key -> + Ok(#(v, reverse_and_prepend(checked, rest))) + [first, ..rest] -> key_pop_loop(rest, key, [first, ..checked]) + } +} + +/// Given a list of 2-element tuples, inserts a key and value into the list. +/// +/// If there was already a tuple with the key then it is replaced, otherwise it +/// is added to the end of the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert key_set([#(5, 0), #(4, 1)], 4, 100) == [#(5, 0), #(4, 100)] +/// ``` +/// +/// ```gleam +/// assert key_set([#(5, 0), #(4, 1)], 1, 100) == [#(5, 0), #(4, 1), #(1, 100)] +/// ``` +/// +pub fn key_set(list: List(#(k, v)), key: k, value: v) -> List(#(k, v)) { + key_set_loop(list, key, value, []) +} + +fn key_set_loop( + list: List(#(k, v)), + key: k, + value: v, + inspected: List(#(k, v)), +) -> List(#(k, v)) { + case list { + [#(k, _), ..rest] if k == key -> + reverse_and_prepend(inspected, [#(k, value), ..rest]) + [first, ..rest] -> key_set_loop(rest, key, value, [first, ..inspected]) + [] -> reverse([#(key, value), ..inspected]) + } +} + +/// Calls a function for each element in a list, discarding the return value. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ```gleam +/// import gleam/io +/// +/// assert each(["1", "2", "3"], io.println) == Nil +/// // 1 +/// // 2 +/// // 3 +/// ``` +/// +pub fn each(list: List(a), f: fn(a) -> b) -> Nil { + case list { + [] -> Nil + [first, ..rest] -> { + f(first) + each(rest, f) + } + } +} + +/// Calls a `Result` returning function for each element in a list, discarding +/// the return value. If the function returns `Error` then the iteration is +/// stopped and the error is returned. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert +/// try_each( +/// over: [1, 2, 3], +/// with: function_that_might_fail, +/// ) +/// == Ok(Nil) +/// ``` +/// +pub fn try_each( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(Nil, e) { + case list { + [] -> Ok(Nil) + [first, ..rest] -> + case fun(first) { + Ok(_) -> try_each(over: rest, with: fun) + Error(e) -> Error(e) + } + } +} + +/// Partitions a list into a tuple/pair of lists +/// by a given categorisation function. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert [1, 2, 3, 4, 5] |> partition(int.is_odd) == #([1, 3, 5], [2, 4]) +/// ``` +/// +pub fn partition( + list: List(a), + with categorise: fn(a) -> Bool, +) -> #(List(a), List(a)) { + partition_loop(list, categorise, [], []) +} + +fn partition_loop(list, categorise, trues, falses) { + case list { + [] -> #(reverse(trues), reverse(falses)) + [first, ..rest] -> + case categorise(first) { + True -> partition_loop(rest, categorise, [first, ..trues], falses) + False -> partition_loop(rest, categorise, trues, [first, ..falses]) + } + } +} + +/// Returns all the permutations of a list. +/// +/// ## Examples +/// +/// ```gleam +/// assert permutations([1, 2]) == [[1, 2], [2, 1]] +/// ``` +/// +pub fn permutations(list: List(a)) -> List(List(a)) { + case list { + [] -> [[]] + l -> permutation_zip(l, [], []) + } +} + +fn permutation_zip( + list: List(a), + rest: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [] -> reverse(acc) + [head, ..tail] -> + permutation_prepend( + head, + permutations(reverse_and_prepend(rest, tail)), + tail, + [head, ..rest], + acc, + ) + } +} + +fn permutation_prepend( + el: a, + permutations: List(List(a)), + list_1: List(a), + list_2: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case permutations { + [] -> permutation_zip(list_1, list_2, acc) + [head, ..tail] -> + permutation_prepend(el, tail, list_1, list_2, [[el, ..head], ..acc]) + } +} + +/// Returns a list of sliding windows. +/// +/// ## Examples +/// +/// ```gleam +/// assert window([1,2,3,4,5], 3) == [[1, 2, 3], [2, 3, 4], [3, 4, 5]] +/// ``` +/// +/// ```gleam +/// assert window([1, 2], 4) == [] +/// ``` +/// +pub fn window(list: List(a), by n: Int) -> List(List(a)) { + case n <= 0 { + True -> [] + False -> window_loop([], list, n) + } +} + +fn window_loop(acc: List(List(a)), list: List(a), n: Int) -> List(List(a)) { + let window = take(list, n) + + case length(window) == n { + True -> window_loop([window, ..acc], drop(list, 1), n) + False -> reverse(acc) + } +} + +/// Returns a list of tuples containing two contiguous elements. +/// +/// ## Examples +/// +/// ```gleam +/// assert window_by_2([1,2,3,4]) == [#(1, 2), #(2, 3), #(3, 4)] +/// ``` +/// +/// ```gleam +/// assert window_by_2([1]) == [] +/// ``` +/// +pub fn window_by_2(list: List(a)) -> List(#(a, a)) { + zip(list, drop(list, 1)) +} + +/// Drops the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_while([1, 2, 3, 4], fn (x) { x < 3 }) == [3, 4] +/// ``` +/// +pub fn drop_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + case list { + [] -> [] + [first, ..rest] -> + case predicate(first) { + True -> drop_while(rest, predicate) + False -> [first, ..rest] + } + } +} + +/// Takes the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// assert take_while([1, 2, 3, 2, 4], fn (x) { x < 3 }) == [1, 2] +/// ``` +/// +pub fn take_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + take_while_loop(list, predicate, []) +} + +fn take_while_loop( + list: List(a), + predicate: fn(a) -> Bool, + acc: List(a), +) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + case predicate(first) { + True -> take_while_loop(rest, predicate, [first, ..acc]) + False -> reverse(acc) + } + } +} + +/// Returns a list of chunks in which +/// the return value of calling `f` on each element is the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 }) +/// == [[1], [2, 2], [3], [4, 4, 6], [7, 7]] +/// ``` +/// +pub fn chunk(in list: List(a), by f: fn(a) -> k) -> List(List(a)) { + case list { + [] -> [] + [first, ..rest] -> chunk_loop(rest, f, f(first), [first], []) + } +} + +fn chunk_loop( + list: List(a), + f: fn(a) -> k, + previous_key: k, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [first, ..rest] -> { + let key = f(first) + case key == previous_key { + True -> chunk_loop(rest, f, key, [first, ..current_chunk], acc) + False -> { + let new_acc = [reverse(current_chunk), ..acc] + chunk_loop(rest, f, key, [first], new_acc) + } + } + } + [] -> reverse([reverse(current_chunk), ..acc]) + } +} + +/// Returns a list of chunks containing `count` elements each. +/// +/// If the last chunk does not have `count` elements, it is instead +/// a partial chunk, with less than `count` elements. +/// +/// For any `count` less than 1 this function behaves as if it was set to 1. +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5, 6] |> sized_chunk(into: 2) +/// == [[1, 2], [3, 4], [5, 6]] +/// ``` +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5, 6, 7, 8] |> sized_chunk(into: 3) +/// == [[1, 2, 3], [4, 5, 6], [7, 8]] +/// ``` +/// +pub fn sized_chunk(in list: List(a), into count: Int) -> List(List(a)) { + sized_chunk_loop(list, count, count, [], []) +} + +fn sized_chunk_loop( + list: List(a), + count: Int, + left: Int, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [] -> + case current_chunk { + [] -> reverse(acc) + remaining -> reverse([reverse(remaining), ..acc]) + } + [first, ..rest] -> { + let chunk = [first, ..current_chunk] + case left > 1 { + True -> sized_chunk_loop(rest, count, left - 1, chunk, acc) + False -> + sized_chunk_loop(rest, count, count, [], [reverse(chunk), ..acc]) + } + } + } +} + +/// This function acts similar to fold, but does not take an initial state. +/// Instead, it starts from the first element in the list +/// and combines it with each subsequent element in turn using the given +/// function. The function is called as `fun(accumulator, current_element)`. +/// +/// Returns `Ok` to indicate a successful run, and `Error` if called on an +/// empty list. +/// +/// ## Examples +/// +/// ```gleam +/// assert [] |> reduce(fn(acc, x) { acc + x }) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5] |> reduce(fn(acc, x) { acc + x }) == Ok(15) +/// ``` +/// +pub fn reduce(over list: List(a), with fun: fn(a, a) -> a) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> Ok(fold(rest, first, fun)) + } +} + +/// Similar to `fold`, but yields the state of the accumulator at each stage. +/// +/// ## Examples +/// +/// ```gleam +/// assert scan(over: [1, 2, 3], from: 100, with: fn(acc, i) { acc + i }) +/// == [101, 103, 106] +/// ``` +/// +pub fn scan( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> List(acc) { + scan_loop(list, initial, [], fun) +} + +fn scan_loop( + list: List(a), + accumulator: acc, + accumulated: List(acc), + fun: fn(acc, a) -> acc, +) -> List(acc) { + case list { + [] -> reverse(accumulated) + [first, ..rest] -> { + let next = fun(accumulator, first) + scan_loop(rest, next, [next, ..accumulated], fun) + } + } +} + +/// Returns the last element in the given list. +/// +/// Returns `Error(Nil)` if the list is empty. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert last([]) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert last([1, 2, 3, 4, 5]) == Ok(5) +/// ``` +/// +pub fn last(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [last] -> Ok(last) + [_, ..rest] -> last(rest) + } +} + +/// Return unique combinations of elements in the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert combinations([1, 2, 3], 2) == [[1, 2], [1, 3], [2, 3]] +/// ``` +/// +/// ```gleam +/// assert combinations([1, 2, 3, 4], 3) +/// == [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] +/// ``` +/// +pub fn combinations(items: List(a), by n: Int) -> List(List(a)) { + case n, items { + 0, _ -> [[]] + _, [] -> [] + _, [first, ..rest] -> + rest + |> combinations(n - 1) + |> map(fn(combination) { [first, ..combination] }) + |> reverse + |> fold(combinations(rest, n), fn(acc, c) { [c, ..acc] }) + } +} + +/// Return unique pair combinations of elements in the list. +/// +/// ## Examples +/// +/// ```gleam +/// assert combination_pairs([1, 2, 3]) == [#(1, 2), #(1, 3), #(2, 3)] +/// ``` +/// +pub fn combination_pairs(items: List(a)) -> List(#(a, a)) { + combination_pairs_loop(items, []) +} + +fn combination_pairs_loop(items: List(a), acc: List(#(a, a))) -> List(#(a, a)) { + case items { + [] -> reverse(acc) + [first, ..rest] -> { + let first_combinations = map(rest, with: fn(other) { #(first, other) }) + let acc = reverse_and_prepend(first_combinations, acc) + combination_pairs_loop(rest, acc) + } + } +} + +/// Make a list alternating the elements from the given lists +/// +/// ## Examples +/// +/// ```gleam +/// assert interleave([[1, 2], [101, 102], [201, 202]]) +/// == [1, 101, 201, 2, 102, 202] +/// ``` +/// +pub fn interleave(list: List(List(a))) -> List(a) { + list + |> transpose + |> flatten +} + +/// Transpose rows and columns of the list of lists. +/// +/// Notice: This function is not tail recursive, +/// and thus may exceed stack size if called, +/// with large lists (on the JavaScript target). +/// +/// ## Examples +/// +/// ```gleam +/// assert transpose([[1, 2, 3], [101, 102, 103]]) +/// == [[1, 101], [2, 102], [3, 103]] +/// ``` +/// +pub fn transpose(list_of_lists: List(List(a))) -> List(List(a)) { + transpose_loop(list_of_lists, []) +} + +fn transpose_loop(rows: List(List(a)), columns: List(List(a))) -> List(List(a)) { + case rows { + [] -> reverse(columns) + _ -> { + let #(column, rest) = take_firsts(rows, [], []) + case column { + [_, ..] -> transpose_loop(rest, [column, ..columns]) + [] -> transpose_loop(rest, columns) + } + } + } +} + +fn take_firsts( + rows: List(List(a)), + column: List(a), + remaining_rows: List(List(a)), +) -> #(List(a), List(List(a))) { + case rows { + [] -> #(reverse(column), reverse(remaining_rows)) + [[], ..rest] -> take_firsts(rest, column, remaining_rows) + [[first, ..remaining_row], ..rest_rows] -> { + let remaining_rows = [remaining_row, ..remaining_rows] + take_firsts(rest_rows, [first, ..column], remaining_rows) + } + } +} + +/// Takes a list, randomly sorts all items and returns the shuffled list. +/// +/// This function uses `float.random` to decide the order of the elements. +/// +/// ## Example +/// +/// ```gleam +/// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> shuffle +/// // -> [1, 6, 9, 10, 3, 8, 4, 2, 7, 5] +/// ``` +/// +pub fn shuffle(list: List(a)) -> List(a) { + list + |> fold(from: [], with: fn(acc, a) { [#(float.random(), a), ..acc] }) + |> do_shuffle_by_pair_indexes() + |> shuffle_pair_unwrap_loop([]) +} + +fn shuffle_pair_unwrap_loop(list: List(#(Float, a)), acc: List(a)) -> List(a) { + case list { + [] -> acc + [elem_pair, ..enumerable] -> + shuffle_pair_unwrap_loop(enumerable, [elem_pair.1, ..acc]) + } +} + +fn do_shuffle_by_pair_indexes( + list_of_pairs: List(#(Float, a)), +) -> List(#(Float, a)) { + sort(list_of_pairs, fn(a_pair: #(Float, a), b_pair: #(Float, a)) -> Order { + float.compare(a_pair.0, b_pair.0) + }) +} + +/// Takes a list and a comparator, and returns the maximum element in the list +/// +/// ## Examples +/// +/// ```gleam +/// assert [1, 2, 3, 4, 5] |> list.max(int.compare) == Ok(5) +/// ``` +/// +/// ```gleam +/// assert ["a", "c", "b"] |> list.max(string.compare) == Ok("c") +/// ``` +/// +pub fn max( + over list: List(a), + with compare: fn(a, a) -> Order, +) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> Ok(max_loop(rest, compare, first)) + } +} + +fn max_loop(list, compare, max) { + case list { + [] -> max + [first, ..rest] -> + case compare(first, max) { + order.Gt -> max_loop(rest, compare, first) + order.Lt | order.Eq -> max_loop(rest, compare, max) + } + } +} + +/// Returns a random sample of up to n elements from a list using reservoir +/// sampling via [Algorithm L](https://en.wikipedia.org/wiki/Reservoir_sampling#Optimal:_Algorithm_L). +/// Returns an empty list if the sample size is less than or equal to 0. +/// +/// Order is not random, only selection is. +/// +/// ## Examples +/// +/// ```gleam +/// sample([1, 2, 3, 4, 5], 3) +/// // -> [2, 4, 5] // A random sample of 3 items +/// ``` +/// +pub fn sample(from list: List(a), up_to n: Int) -> List(a) { + let #(reservoir, rest) = build_reservoir(from: list, sized: n) + + case dict.is_empty(reservoir) { + // If the reservoire is empty that means we were asking to sample 0 or + // less items. That doesn't make much sense, so we just return an empty + // list. + True -> [] + + // Otherwise we keep looping over the remaining part of the list replacing + // random elements in the reservoir. + False -> { + let w = float.exponential(log_random() /. int.to_float(n)) + dict.values(sample_loop(rest, reservoir, n, w)) + } + } +} + +fn sample_loop( + list: List(a), + reservoir: Dict(Int, a), + n: Int, + w: Float, +) -> Dict(Int, a) { + let skip = { + let assert Ok(log) = float.logarithm(1.0 -. w) + float.round(float.floor(log_random() /. log)) + } + + case drop(list, skip) { + [] -> reservoir + [first, ..rest] -> { + let reservoir = dict.insert(reservoir, int.random(n), first) + let w = w *. float.exponential(log_random() /. int.to_float(n)) + sample_loop(rest, reservoir, n, w) + } + } +} + +const min_positive = 2.2250738585072014e-308 + +fn log_random() -> Float { + let assert Ok(random) = float.logarithm(float.random() +. min_positive) + random +} + +/// Builds the initial reservoir used by Algorithm L. +/// This is a dictionary with keys ranging from `0` up to `n - 1` where each +/// value is the corresponding element at that position in `list`. +/// +/// This also returns the remaining elements of `list` that didn't end up in +/// the reservoir. +/// +fn build_reservoir(from list: List(a), sized n: Int) -> #(Dict(Int, a), List(a)) { + build_reservoir_loop(list, n, dict.new()) +} + +fn build_reservoir_loop( + list: List(a), + size: Int, + reservoir: Dict(Int, a), +) -> #(Dict(Int, a), List(a)) { + let reservoir_size = dict.size(reservoir) + case reservoir_size >= size { + // The reservoir already has the size we wanted. + True -> #(reservoir, list) + + // Otherwise we add another element from the list to the reservoir + False -> + case list { + [] -> #(reservoir, []) + [first, ..rest] -> { + let reservoir = dict.insert(reservoir, reservoir_size, first) + build_reservoir_loop(rest, size, reservoir) + } + } + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/option.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/option.gleam new file mode 100644 index 0000000..7d3dfc7 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/option.gleam @@ -0,0 +1,326 @@ +/// `Option` represents a value that may be present or not. `Some` means the value is +/// present, `None` means the value is not. +/// +/// This is Gleam's alternative to having a value that could be Null, as is +/// possible in some other languages. +/// +/// ## `Option` and `Result` +/// +/// In other languages fallible functions may return either `Result` or +/// `Option` depending on whether there is more information to be given about the +/// failure. In Gleam all fallible functions return `Result`, and `Nil` is used +/// as the error if there is no extra detail to give. This consistency removes +/// the boilerplate that would otherwise be needed to convert between `Option` +/// and `Result` types, and makes APIs more predictable. +/// +/// The `Option` type should only be used for taking optional values as +/// function arguments, or for storing them in other data structures. +/// +pub type Option(a) { + Some(a) + None +} + +/// Combines a list of `Option`s into a single `Option`. +/// If all elements in the list are `Some` then returns a `Some` holding the list of values. +/// If any element is `None` then returns `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([Some(1), Some(2)]) == Some([1, 2]) +/// ``` +/// +/// ```gleam +/// assert all([Some(1), None]) == None +/// ``` +/// +pub fn all(list: List(Option(a))) -> Option(List(a)) { + all_loop(list, []) +} + +fn all_loop(list: List(Option(a)), acc: List(a)) -> Option(List(a)) { + case list { + [] -> Some(reverse(acc)) + [None, ..] -> None + [Some(first), ..rest] -> all_loop(rest, [first, ..acc]) + } +} + +// This is copied from the list module and not imported as importing it would +// result in a circular dependency! +@external(erlang, "lists", "reverse") +fn reverse(list: List(a)) -> List(a) { + reverse_and_prepend(list, []) +} + +fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { + case prefix { + [] -> suffix + [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) + } +} + +/// Checks whether the `Option` is a `Some` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_some(Some(1)) +/// ``` +/// +/// ```gleam +/// assert !is_some(None) +/// ``` +/// +pub fn is_some(option: Option(a)) -> Bool { + option != None +} + +/// Checks whether the `Option` is a `None` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert !is_none(Some(1)) +/// ``` +/// +/// ```gleam +/// assert is_none(None) +/// ``` +/// +pub fn is_none(option: Option(a)) -> Bool { + option == None +} + +/// Converts an `Option` type to a `Result` type. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_result(Some(1), "some_error") == Ok(1) +/// ``` +/// +/// ```gleam +/// assert to_result(None, "some_error") == Error("some_error") +/// ``` +/// +pub fn to_result(option: Option(a), e) -> Result(a, e) { + case option { + Some(a) -> Ok(a) + None -> Error(e) + } +} + +/// Converts a `Result` type to an `Option` type. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_result(Ok(1)) == Some(1) +/// ``` +/// +/// ```gleam +/// assert from_result(Error("some_error")) == None +/// ``` +/// +pub fn from_result(result: Result(a, e)) -> Option(a) { + case result { + Ok(a) -> Some(a) + Error(_) -> None + } +} + +/// Extracts the value from an `Option`, returning a default value if there is none. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap(Some(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap(None, 0) == 0 +/// ``` +/// +pub fn unwrap(option: Option(a), or default: a) -> a { + case option { + Some(x) -> x + None -> default + } +} + +/// Extracts the value from an `Option`, evaluating the default function if the option is `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_unwrap(Some(1), fn() { 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert lazy_unwrap(None, fn() { 0 }) == 0 +/// ``` +/// +pub fn lazy_unwrap(option: Option(a), or default: fn() -> a) -> a { + case option { + Some(x) -> x + None -> default() + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it. +/// +/// If the `Option` is a `None` rather than `Some`, the function is not called and the +/// `Option` stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map(over: Some(1), with: fn(x) { x + 1 }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert map(over: None, with: fn(x) { x + 1 }) == None +/// ``` +/// +pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { + case option { + Some(x) -> Some(fun(x)) + None -> None + } +} + +/// Merges a nested `Option` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten(Some(Some(1))) == Some(1) +/// ``` +/// +/// ```gleam +/// assert flatten(Some(None)) == None +/// ``` +/// +/// ```gleam +/// assert flatten(None) == None +/// ``` +/// +pub fn flatten(option: Option(Option(a))) -> Option(a) { + case option { + Some(x) -> x + None -> None + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it, where the given function also returns an `Option`. The two options are +/// then merged together into one `Option`. +/// +/// If the `Option` is a `None` rather than `Some` the function is not called and the +/// option stays the same. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that return `Option`. +/// +/// ## Examples +/// +/// ```gleam +/// assert then(Some(1), fn(x) { Some(x + 1) }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert then(Some(1), fn(x) { Some(#("a", x)) }) == Some(#("a", 1)) +/// ``` +/// +/// ```gleam +/// assert then(Some(1), fn(_) { None }) == None +/// ``` +/// +/// ```gleam +/// assert then(None, fn(x) { Some(x + 1) }) == None +/// ``` +/// +pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { + case option { + Some(x) -> fun(x) + None -> None + } +} + +/// Returns the first value if it is `Some`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(Some(1), Some(2)) == Some(1) +/// ``` +/// +/// ```gleam +/// assert or(Some(1), None) == Some(1) +/// ``` +/// +/// ```gleam +/// assert or(None, Some(2)) == Some(2) +/// ``` +/// +/// ```gleam +/// assert or(None, None) == None +/// ``` +/// +pub fn or(first: Option(a), second: Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second + } +} + +/// Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_or(Some(1), fn() { Some(2) }) == Some(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Some(1), fn() { None }) == Some(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(None, fn() { Some(2) }) == Some(2) +/// ``` +/// +/// ```gleam +/// assert lazy_or(None, fn() { None }) == None +/// ``` +/// +pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second() + } +} + +/// Given a list of `Option`s, +/// returns only the values inside `Some`. +/// +/// ## Examples +/// +/// ```gleam +/// assert values([Some(1), None, Some(3)]) == [1, 3] +/// ``` +/// +pub fn values(options: List(Option(a))) -> List(a) { + values_loop(options, []) +} + +fn values_loop(list: List(Option(a)), acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [None, ..rest] -> values_loop(rest, acc) + [Some(first), ..rest] -> values_loop(rest, [first, ..acc]) + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/order.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/order.gleam new file mode 100644 index 0000000..49f2d93 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/order.gleam @@ -0,0 +1,144 @@ +/// Represents the result of a single comparison to determine the precise +/// ordering of two values. +/// +pub type Order { + /// Less-than + Lt + + /// Equal + Eq + + /// Greater than + Gt +} + +/// Inverts an order, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// assert negate(Lt) == Gt +/// ``` +/// +/// ```gleam +/// assert negate(Eq) == Eq +/// ``` +/// +/// ```gleam +/// assert negate(Gt) == Lt +/// ``` +/// +pub fn negate(order: Order) -> Order { + case order { + Lt -> Gt + Eq -> Eq + Gt -> Lt + } +} + +/// Produces a numeric representation of the order. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_int(Lt) == -1 +/// ``` +/// +/// ```gleam +/// assert to_int(Eq) == 0 +/// ``` +/// +/// ```gleam +/// assert to_int(Gt) == 1 +/// ``` +/// +pub fn to_int(order: Order) -> Int { + case order { + Lt -> -1 + Eq -> 0 + Gt -> 1 + } +} + +/// Compares two `Order` values to one another, producing a new `Order`. +/// +/// ## Examples +/// +/// ```gleam +/// assert compare(Eq, with: Lt) == Gt +/// ``` +/// +pub fn compare(a: Order, with b: Order) -> Order { + case a, b { + x, y if x == y -> Eq + Lt, _ | Eq, Gt -> Lt + _, _ -> Gt + } +} + +/// Inverts an ordering function, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// import gleam/list +/// +/// assert list.sort([1, 5, 4], by: reverse(int.compare)) == [5, 4, 1] +/// ``` +/// +pub fn reverse(orderer: fn(a, a) -> Order) -> fn(a, a) -> Order { + fn(a, b) { orderer(b, a) } +} + +/// Return a fallback `Order` in case the first argument is `Eq`. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert break_tie(in: int.compare(1, 1), with: Lt) == Lt +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// assert break_tie(in: int.compare(1, 0), with: Eq) == Gt +/// ``` +/// +pub fn break_tie(in order: Order, with other: Order) -> Order { + case order { + Lt | Gt -> order + Eq -> other + } +} + +/// Invokes a fallback function returning an `Order` in case the first argument +/// is `Eq`. +/// +/// This can be useful when the fallback comparison might be expensive and it +/// needs to be delayed until strictly necessary. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt }) == Lt +/// ``` +/// +/// ```gleam +/// import gleam/int +/// +/// assert lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq }) == Gt +/// ``` +/// +pub fn lazy_break_tie(in order: Order, with comparison: fn() -> Order) -> Order { + case order { + Lt | Gt -> order + Eq -> comparison() + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/pair.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/pair.gleam new file mode 100644 index 0000000..8d818b5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/pair.gleam @@ -0,0 +1,79 @@ +/// Returns the first element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// assert first(#(1, 2)) == 1 +/// ``` +/// +pub fn first(pair: #(a, b)) -> a { + let #(a, _) = pair + a +} + +/// Returns the second element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// assert second(#(1, 2)) == 2 +/// ``` +/// +pub fn second(pair: #(a, b)) -> b { + let #(_, a) = pair + a +} + +/// Returns a new pair with the elements swapped. +/// +/// ## Examples +/// +/// ```gleam +/// assert swap(#(1, 2)) == #(2, 1) +/// ``` +/// +pub fn swap(pair: #(a, b)) -> #(b, a) { + let #(a, b) = pair + #(b, a) +} + +/// Returns a new pair with the first element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// assert #(1, 2) |> map_first(fn(n) { n * 2 }) == #(2, 2) +/// ``` +/// +pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { + let #(a, b) = pair + #(fun(a), b) +} + +/// Returns a new pair with the second element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// assert #(1, 2) |> map_second(fn(n) { n * 2 }) == #(1, 4) +/// ``` +/// +pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { + let #(a, b) = pair + #(a, fun(b)) +} + +/// Returns a new pair with the given elements. This can also be done using the dedicated +/// syntax instead: `new(1, 2) == #(1, 2)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert new(1, 2) == #(1, 2) +/// ``` +/// +pub fn new(first: a, second: b) -> #(a, b) { + #(first, second) +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/result.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/result.gleam new file mode 100644 index 0000000..11a298f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/result.gleam @@ -0,0 +1,405 @@ +//// Result represents the result of something that may succeed or not. +//// `Ok` means it was successful, `Error` means it was not successful. + +import gleam/list + +/// Checks whether the result is an `Ok` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_ok(Ok(1)) +/// ``` +/// +/// ```gleam +/// assert !is_ok(Error(Nil)) +/// ``` +/// +pub fn is_ok(result: Result(a, e)) -> Bool { + case result { + Error(_) -> False + Ok(_) -> True + } +} + +/// Checks whether the result is an `Error` value. +/// +/// ## Examples +/// +/// ```gleam +/// assert !is_error(Ok(1)) +/// ``` +/// +/// ```gleam +/// assert is_error(Error(Nil)) +/// ``` +/// +pub fn is_error(result: Result(a, e)) -> Bool { + case result { + Ok(_) -> False + Error(_) -> True + } +} + +/// Updates a value held within the `Ok` of a result by calling a given function +/// on it. +/// +/// If the result is an `Error` rather than `Ok` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map(over: Ok(1), with: fn(x) { x + 1 }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert map(over: Error(1), with: fn(x) { x + 1 }) == Error(1) +/// ``` +/// +pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { + case result { + Ok(x) -> Ok(fun(x)) + Error(e) -> Error(e) + } +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it. +/// +/// If the result is `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// assert map_error(over: Error(1), with: fn(x) { x + 1 }) == Error(2) +/// ``` +/// +/// ```gleam +/// assert map_error(over: Ok(1), with: fn(x) { x + 1 }) == Ok(1) +/// ``` +/// +pub fn map_error( + over result: Result(a, e), + with fun: fn(e) -> f, +) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(error) -> Error(fun(error)) + } +} + +/// Merges a nested `Result` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// assert flatten(Ok(Ok(1))) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert flatten(Ok(Error(""))) == Error("") +/// ``` +/// +/// ```gleam +/// assert flatten(Error(Nil)) == Error(Nil) +/// ``` +/// +pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { + case result { + Ok(x) -> x + Error(error) -> Error(error) + } +} + +/// "Updates" an `Ok` result by passing its value to a function that yields a result, +/// and returning the yielded result. (This may "replace" the `Ok` with an `Error`.) +/// +/// If the input is an `Error` rather than an `Ok`, the function is not called and +/// the original `Error` is returned. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that may fail. +/// +/// ## Examples +/// +/// ```gleam +/// assert try(Ok(1), fn(x) { Ok(x + 1) }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert try(Ok(1), fn(x) { Ok(#("a", x)) }) == Ok(#("a", 1)) +/// ``` +/// +/// ```gleam +/// assert try(Ok(1), fn(_) { Error("Oh no") }) == Error("Oh no") +/// ``` +/// +/// ```gleam +/// assert try(Error(Nil), fn(x) { Ok(x + 1) }) == Error(Nil) +/// ``` +/// +pub fn try( + result: Result(a, e), + apply fun: fn(a) -> Result(b, e), +) -> Result(b, e) { + case result { + Ok(x) -> fun(x) + Error(e) -> Error(e) + } +} + +/// Extracts the `Ok` value from a result, returning a default value if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap(Ok(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap(Error(""), 0) == 0 +/// ``` +/// +pub fn unwrap(result: Result(a, e), or default: a) -> a { + case result { + Ok(v) -> v + Error(_) -> default + } +} + +/// Extracts the `Ok` value from a result, evaluating the default function if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_unwrap(Ok(1), fn() { 0 }) == 1 +/// ``` +/// +/// ```gleam +/// assert lazy_unwrap(Error(""), fn() { 0 }) == 0 +/// ``` +/// +pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { + case result { + Ok(v) -> v + Error(_) -> default() + } +} + +/// Extracts the `Error` value from a result, returning a default value if the result +/// is an `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// assert unwrap_error(Error(1), 0) == 1 +/// ``` +/// +/// ```gleam +/// assert unwrap_error(Ok(""), 0) == 0 +/// ``` +/// +pub fn unwrap_error(result: Result(a, e), or default: e) -> e { + case result { + Ok(_) -> default + Error(e) -> e + } +} + +/// Returns the first value if it is `Ok`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// assert or(Ok(1), Ok(2)) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert or(Ok(1), Error("Error 2")) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert or(Error("Error 1"), Ok(2)) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert or(Error("Error 1"), Error("Error 2")) == Error("Error 2") +/// ``` +/// +pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second + } +} + +/// Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value. +/// +/// If you need access to the initial error value, use `result.try_recover`. +/// +/// ## Examples +/// +/// ```gleam +/// assert lazy_or(Ok(1), fn() { Ok(2) }) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Ok(1), fn() { Error("Error 2") }) == Ok(1) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Error("Error 1"), fn() { Ok(2) }) == Ok(2) +/// ``` +/// +/// ```gleam +/// assert lazy_or(Error("Error 1"), fn() { Error("Error 2") }) +/// == Error("Error 2") +/// ``` +/// +pub fn lazy_or( + first: Result(a, e), + second: fn() -> Result(a, e), +) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second() + } +} + +/// Combines a list of results into a single result. +/// If all elements in the list are `Ok` then returns an `Ok` holding the list of values. +/// If any element is `Error` then returns the first error. +/// +/// ## Examples +/// +/// ```gleam +/// assert all([Ok(1), Ok(2)]) == Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// assert all([Ok(1), Error("e")]) == Error("e") +/// ``` +/// +pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { + list.try_map(results, fn(result) { result }) +} + +/// Given a list of results, returns a pair where the first element is a list +/// of all the values inside `Ok` and the second element is a list with all the +/// values inside `Error`. The values in both lists appear in reverse order with +/// respect to their position in the original list of results. +/// +/// ## Examples +/// +/// ```gleam +/// assert partition([Ok(1), Error("a"), Error("b"), Ok(2)]) +/// == #([2, 1], ["b", "a"]) +/// ``` +/// +pub fn partition(results: List(Result(a, e))) -> #(List(a), List(e)) { + partition_loop(results, [], []) +} + +fn partition_loop(results: List(Result(a, e)), oks: List(a), errors: List(e)) { + case results { + [] -> #(oks, errors) + [Ok(a), ..rest] -> partition_loop(rest, [a, ..oks], errors) + [Error(e), ..rest] -> partition_loop(rest, oks, [e, ..errors]) + } +} + +/// Replace the value within a result +/// +/// ## Examples +/// +/// ```gleam +/// assert replace(Ok(1), Nil) == Ok(Nil) +/// ``` +/// +/// ```gleam +/// assert replace(Error(1), Nil) == Error(1) +/// ``` +/// +pub fn replace(result: Result(a, e), value: b) -> Result(b, e) { + case result { + Ok(_) -> Ok(value) + Error(error) -> Error(error) + } +} + +/// Replace the error within a result +/// +/// ## Examples +/// +/// ```gleam +/// assert replace_error(Error(1), Nil) == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert replace_error(Ok(1), Nil) == Ok(1) +/// ``` +/// +pub fn replace_error(result: Result(a, e), error: f) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(_) -> Error(error) + } +} + +/// Given a list of results, returns only the values inside `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// assert values([Ok(1), Error("a"), Ok(3)]) == [1, 3] +/// ``` +/// +pub fn values(results: List(Result(a, e))) -> List(a) { + list.filter_map(results, fn(result) { result }) +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it, where the given function also returns a result. The two results are +/// then merged together into one result. +/// +/// If the result is an `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// This function is useful for chaining together computations that may fail +/// and trying to recover from possible errors. +/// +/// If you do not need access to the initial error value, use `result.lazy_or`. +/// +/// ## Examples +/// +/// ```gleam +/// assert Ok(1) +/// |> try_recover(with: fn(_) { Error("failed to recover") }) +/// == Ok(1) +/// ``` +/// +/// ```gleam +/// assert Error(1) +/// |> try_recover(with: fn(error) { Ok(error + 1) }) +/// == Ok(2) +/// ``` +/// +/// ```gleam +/// assert Error(1) +/// |> try_recover(with: fn(error) { Error("failed to recover") }) +/// == Error("failed to recover") +/// ``` +/// +pub fn try_recover( + result: Result(a, e), + with fun: fn(e) -> Result(a, f), +) -> Result(a, f) { + case result { + Ok(value) -> Ok(value) + Error(error) -> fun(error) + } +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/set.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/set.gleam new file mode 100644 index 0000000..c553933 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/set.gleam @@ -0,0 +1,410 @@ +import gleam/dict.{type Dict} +import gleam/list +import gleam/result + +// A list is used as the dict value as an empty list has the smallest +// representation in Erlang's binary format +@target(erlang) +type Token = + List(Nil) + +@target(erlang) +const token = [] + +@target(javascript) +type Token = + Nil + +@target(javascript) +const token = Nil + +/// A set is a collection of unique members of the same type. +/// +/// It is implemented using the `gleam/dict` module, so inserts and lookups have +/// logarithmic time complexity. +/// +pub opaque type Set(member) { + Set(dict: Dict(member, Token)) +} + +/// Creates a new empty set. +/// +pub fn new() -> Set(member) { + Set(dict.new()) +} + +/// Gets the number of members in a set. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(1) +/// |> insert(2) +/// |> size +/// == 2 +/// ``` +/// +pub fn size(set: Set(member)) -> Int { + dict.size(set.dict) +} + +/// Determines whether or not the set is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> is_empty +/// ``` +/// +/// ```gleam +/// assert !{ new() |> insert(1) |> is_empty } +/// ``` +/// +pub fn is_empty(set: Set(member)) -> Bool { + set == new() +} + +/// Inserts a member into the set. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(1) +/// |> insert(2) +/// |> size +/// == 2 +/// ``` +/// +pub fn insert(into set: Set(member), this member: member) -> Set(member) { + Set(dict: dict.insert(set.dict, member, token)) +} + +/// Checks whether a set contains a given member. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() +/// |> insert(2) +/// |> contains(2) +/// ``` +/// +/// ```gleam +/// assert !{ +/// new() +/// |> insert(2) +/// |> contains(1) +/// } +/// ``` +/// +pub fn contains(in set: Set(member), this member: member) -> Bool { + set.dict + |> dict.get(member) + |> result.is_ok +} + +/// Removes a member from a set. If the set does not contain the member then +/// the set is returned unchanged. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// assert !{ +/// new() +/// |> insert(2) +/// |> delete(2) +/// |> contains(2) +/// } +/// ``` +/// +pub fn delete(from set: Set(member), this member: member) -> Set(member) { + Set(dict: dict.delete(set.dict, member)) +} + +/// Converts the set into a list of the contained members. +/// +/// The list has no specific ordering, any unintentional ordering may change in +/// future versions of Gleam or Erlang. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert new() |> insert(2) |> to_list == [2] +/// ``` +/// +pub fn to_list(set: Set(member)) -> List(member) { + dict.keys(set.dict) +} + +/// Creates a new set of the members in a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// import gleam/list +/// +/// assert [1, 1, 2, 4, 3, 2] +/// |> from_list +/// |> to_list +/// |> list.sort(by: int.compare) +/// == [1, 2, 3, 4] +/// ``` +/// +pub fn from_list(members: List(member)) -> Set(member) { + let dict = + list.fold(over: members, from: dict.new(), with: fn(m, k) { + dict.insert(m, k, token) + }) + Set(dict) +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Sets are not ordered so the values are not returned in any specific order. +/// Do not write code that relies on the order entries are used by this +/// function as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 3, 9]) +/// |> fold(0, fn(accumulator, member) { accumulator + member }) +/// == 13 +/// ``` +/// +pub fn fold( + over set: Set(member), + from initial: acc, + with reducer: fn(acc, member) -> acc, +) -> acc { + dict.fold(over: set.dict, from: initial, with: fn(a, k, _) { reducer(a, k) }) +} + +/// Creates a new set from an existing set, minus any members that a given +/// function returns `False` for. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/int +/// +/// assert from_list([1, 4, 6, 3, 675, 44, 67]) +/// |> filter(keeping: int.is_even) +/// |> to_list +/// == [4, 6, 44] +/// ``` +/// +pub fn filter( + in set: Set(member), + keeping predicate: fn(member) -> Bool, +) -> Set(member) { + Set(dict.filter(in: set.dict, keeping: fn(m, _) { predicate(m) })) +} + +/// Creates a new set from a given set with the result of applying the given +/// function to each member. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3, 4]) +/// |> map(with: fn(x) { x * 2 }) +/// |> to_list +/// == [2, 4, 6, 8] +/// ``` +/// +pub fn map(set: Set(member), with fun: fn(member) -> mapped) -> Set(mapped) { + fold(over: set, from: new(), with: fn(acc, member) { + insert(acc, fun(member)) + }) +} + +/// Creates a new set from a given set with all the same entries except any +/// entry found on the given list. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3, 4]) +/// |> drop([1, 3]) +/// |> to_list +/// == [2, 4] +/// ``` +/// +pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) { + list.fold(over: disallowed, from: set, with: delete) +} + +/// Creates a new set from a given set, only including any members which are in +/// a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_list([1, 2, 3]) +/// |> take([1, 3, 5]) +/// |> to_list +/// == [1, 3] +/// ``` +/// +pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { + Set(dict.take(from: set.dict, keeping: desired)) +} + +/// Creates a new set that contains all members of both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert union(from_list([1, 2]), from_list([2, 3])) |> to_list +/// == [1, 2, 3] +/// ``` +/// +pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { + let #(larger, smaller) = order(first, second) + fold(over: smaller, from: larger, with: insert) +} + +fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { + case dict.size(first.dict) > dict.size(second.dict) { + True -> #(first, second) + False -> #(second, first) + } +} + +/// Creates a new set that contains members that are present in both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert intersection(from_list([1, 2]), from_list([2, 3])) |> to_list +/// == [2] +/// ``` +/// +pub fn intersection( + of first: Set(member), + and second: Set(member), +) -> Set(member) { + let #(larger, smaller) = order(first, second) + take(from: larger, keeping: to_list(smaller)) +} + +/// Creates a new set that contains members that are present in the first set +/// but not the second. +/// +/// ## Examples +/// +/// ```gleam +/// assert difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list +/// == [1] +/// ``` +/// +pub fn difference( + from first: Set(member), + minus second: Set(member), +) -> Set(member) { + drop(from: first, drop: to_list(second)) +} + +/// Determines if a set is fully contained by another. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_subset(from_list([1]), from_list([1, 2])) +/// ``` +/// +/// ```gleam +/// assert !is_subset(from_list([1, 2, 3]), from_list([3, 4, 5])) +/// ``` +/// +pub fn is_subset(first: Set(member), of second: Set(member)) -> Bool { + intersection(of: first, and: second) == first +} + +/// Determines if two sets contain no common members +/// +/// ## Examples +/// +/// ```gleam +/// assert is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6])) +/// ``` +/// +/// ```gleam +/// assert !is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5])) +/// ``` +/// +pub fn is_disjoint(first: Set(member), from second: Set(member)) -> Bool { + intersection(of: first, and: second) == new() +} + +/// Creates a new set that contains members that are present in either set, but +/// not both. +/// +/// ## Examples +/// +/// ```gleam +/// assert symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) +/// |> to_list +/// == [1, 2, 4] +/// ``` +/// +pub fn symmetric_difference( + of first: Set(member), + and second: Set(member), +) -> Set(member) { + difference( + from: union(of: first, and: second), + minus: intersection(of: first, and: second), + ) +} + +/// Calls a function for each member in a set, discarding the return +/// value. +/// +/// Useful for producing a side effect for every item of a set. +/// +/// The order of elements in the iteration is an implementation detail that +/// should not be relied upon. +/// +/// ## Examples +/// +/// ```gleam +/// let set = from_list(["apple", "banana", "cherry"]) +/// +/// assert each(set, io.println) == Nil +/// // apple +/// // banana +/// // cherry +/// ``` +/// +pub fn each(set: Set(member), fun: fn(member) -> a) -> Nil { + fold(set, Nil, fn(nil, member) { + fun(member) + nil + }) +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string.gleam new file mode 100644 index 0000000..e543bb6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string.gleam @@ -0,0 +1,890 @@ +//// Strings in Gleam are UTF-8 binaries. They can be written in your code as +//// text surrounded by `"double quotes"`. + +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/order +import gleam/string_tree.{type StringTree} + +/// Determines if a `String` is empty. +/// +/// ## Examples +/// +/// ```gleam +/// assert is_empty("") +/// ``` +/// +/// ```gleam +/// assert !is_empty("the world") +/// ``` +/// +pub fn is_empty(str: String) -> Bool { + str == "" +} + +/// Gets the number of grapheme clusters in a given `String`. +/// +/// This function has to iterate across the whole string to count the number of +/// graphemes, so it runs in linear time. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert length("Gleam") == 5 +/// ``` +/// +/// ```gleam +/// assert length("ß↑e̊") == 3 +/// ``` +/// +/// ```gleam +/// assert length("") == 0 +/// ``` +/// +@external(erlang, "string", "length") +@external(javascript, "../gleam_stdlib.mjs", "string_length") +pub fn length(string: String) -> Int + +/// Reverses a `String`. +/// +/// This function has to iterate across the whole `String` so it runs in linear +/// time. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert reverse("stressed") == "desserts" +/// ``` +/// +pub fn reverse(string: String) -> String { + string + |> string_tree.from_string + |> string_tree.reverse + |> string_tree.to_string +} + +/// Creates a new `String` by replacing all occurrences of a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// assert replace("www.example.com", each: ".", with: "-") == "www-example-com" +/// ``` +/// +/// ```gleam +/// assert replace("a,b,c,d,e", each: ",", with: "/") == "a/b/c/d/e" +/// ``` +/// +pub fn replace( + in string: String, + each pattern: String, + with substitute: String, +) -> String { + string + |> string_tree.from_string + |> string_tree.replace(each: pattern, with: substitute) + |> string_tree.to_string +} + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// lowercase. +/// +/// Useful for case-insensitive comparisons. +/// +/// ## Examples +/// +/// ```gleam +/// assert lowercase("X-FILES") == "x-files" +/// ``` +/// +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +pub fn lowercase(string: String) -> String + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// uppercase. +/// +/// Useful for case-insensitive comparisons and VIRTUAL YELLING. +/// +/// ## Examples +/// +/// ```gleam +/// assert uppercase("skinner") == "SKINNER" +/// ``` +/// +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +pub fn uppercase(string: String) -> String + +/// Compares two `String`s to see which is "larger" by comparing their graphemes. +/// +/// This does not compare the size or length of the given `String`s. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/order +/// +/// assert compare("Anthony", "Anthony") == order.Eq +/// ``` +/// +/// ```gleam +/// import gleam/order +/// +/// assert compare("A", "B") == order.Lt +/// ``` +/// +pub fn compare(a: String, b: String) -> order.Order { + case a == b { + True -> order.Eq + _ -> + case less_than(a, b) { + True -> order.Lt + False -> order.Gt + } + } +} + +@external(erlang, "gleam_stdlib", "less_than") +@external(javascript, "../gleam_stdlib.mjs", "less_than") +fn less_than(a: String, b: String) -> Bool + +/// Takes a substring given a start grapheme index and a length. Negative indexes +/// are taken starting from the *end* of the string. +/// +/// This function runs in linear time with the size of the index and the +/// length. Negative indexes are linear with the size of the input string in +/// addition to the other costs. +/// +/// ## Examples +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 1, length: 2) == "le" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 1, length: 10) == "leam" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: 10, length: 3) == "" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: -2, length: 2) == "am" +/// ``` +/// +/// ```gleam +/// assert slice(from: "gleam", at_index: -12, length: 2) == "" +/// ``` +/// +pub fn slice(from string: String, at_index idx: Int, length len: Int) -> String { + case len <= 0 { + True -> "" + False -> + case idx < 0 { + True -> { + let translated_idx = length(string) + idx + case translated_idx < 0 { + True -> "" + False -> grapheme_slice(string, translated_idx, len) + } + } + False -> grapheme_slice(string, idx, len) + } + } +} + +@external(erlang, "gleam_stdlib", "slice") +@external(javascript, "../gleam_stdlib.mjs", "string_grapheme_slice") +fn grapheme_slice(string: String, index: Int, length: Int) -> String + +@external(erlang, "binary", "part") +@external(javascript, "../gleam_stdlib.mjs", "string_byte_slice") +fn unsafe_byte_slice(string: String, index: Int, length: Int) -> String + +/// Drops contents of the first `String` that occur before the second `String`. +/// If the `from` string does not contain the `before` string, `from` is +/// returned unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert crop(from: "The Lone Gunmen", before: "Lone") == "Lone Gunmen" +/// ``` +/// +@external(erlang, "gleam_stdlib", "crop_string") +@external(javascript, "../gleam_stdlib.mjs", "crop_string") +pub fn crop(from string: String, before substring: String) -> String + +/// Drops *n* graphemes from the start of a `String`. +/// +/// This function runs in linear time with the number of graphemes to drop. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_start(from: "The Lone Gunmen", up_to: 2) == "e Lone Gunmen" +/// ``` +/// +pub fn drop_start(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes <= 0 { + True -> string + False -> { + let prefix = grapheme_slice(string, 0, num_graphemes) + let prefix_size = byte_size(prefix) + unsafe_byte_slice(string, prefix_size, byte_size(string) - prefix_size) + } + } +} + +/// Drops *n* graphemes from the end of a `String`. +/// +/// This function traverses the full string, so it runs in linear time with the +/// size of the string. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert drop_end(from: "Cigarette Smoking Man", up_to: 2) +/// == "Cigarette Smoking M" +/// ``` +/// +pub fn drop_end(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes <= 0 { + True -> string + False -> slice(string, 0, length(string) - num_graphemes) + } +} + +/// Checks if the first `String` contains the second. +/// +/// ## Examples +/// +/// ```gleam +/// assert contains(does: "theory", contain: "ory") +/// ``` +/// +/// ```gleam +/// assert contains(does: "theory", contain: "the") +/// ``` +/// +/// ```gleam +/// assert !contains(does: "theory", contain: "THE") +/// ``` +/// +@external(erlang, "gleam_stdlib", "contains_string") +@external(javascript, "../gleam_stdlib.mjs", "contains_string") +pub fn contains(does haystack: String, contain needle: String) -> Bool + +/// Checks whether the first `String` starts with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert !starts_with("theory", "ory") +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_starts_with") +@external(javascript, "../gleam_stdlib.mjs", "starts_with") +pub fn starts_with(string: String, prefix: String) -> Bool + +/// Checks whether the first `String` ends with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// assert ends_with("theory", "ory") +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_ends_with") +@external(javascript, "../gleam_stdlib.mjs", "ends_with") +pub fn ends_with(string: String, suffix: String) -> Bool + +/// Creates a list of `String`s by splitting a given string on a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// assert split("home/gleam/desktop/", on: "/") +/// == ["home", "gleam", "desktop", ""] +/// ``` +/// +pub fn split(x: String, on substring: String) -> List(String) { + case substring { + "" -> to_graphemes(x) + _ -> + x + |> string_tree.from_string + |> string_tree.split(on: substring) + |> list.map(with: string_tree.to_string) + } +} + +/// Splits a `String` a single time on the given substring. +/// +/// Returns an `Error` if substring not present. +/// +/// ## Examples +/// +/// ```gleam +/// assert split_once("home/gleam/desktop/", on: "/") +/// == Ok(#("home", "gleam/desktop/")) +/// ``` +/// +/// ```gleam +/// assert split_once("home/gleam/desktop/", on: "?") == Error(Nil) +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "split_once") +pub fn split_once( + string: String, + on substring: String, +) -> Result(#(String, String), Nil) { + case erl_split(string, substring) { + [first, rest] -> Ok(#(first, rest)) + _ -> Error(Nil) + } +} + +@external(erlang, "string", "split") +fn erl_split(a: String, b: String) -> List(String) + +/// Creates a new `String` by joining two `String`s together. +/// +/// This function typically copies both `String`s and runs in linear time, but +/// the exact behaviour will depend on how the runtime you are using optimises +/// your code. Benchmark and profile your code if you need to understand its +/// performance better. +/// +/// If you are joining together large string and want to avoid copying any data +/// you may want to investigate using the [`string_tree`](../gleam/string_tree.html) +/// module. +/// +/// ## Examples +/// +/// ```gleam +/// assert append(to: "butter", suffix: "fly") == "butterfly" +/// ``` +/// +pub fn append(to first: String, suffix second: String) -> String { + first <> second +} + +/// Creates a new `String` by joining many `String`s together. +/// +/// This function copies all the `String`s and runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert concat(["never", "the", "less"]) == "nevertheless" +/// ``` +/// +@external(erlang, "erlang", "list_to_binary") +pub fn concat(strings: List(String)) -> String { + concat_loop(strings, "") +} + +fn concat_loop(strings: List(String), accumulator: String) -> String { + case strings { + [string, ..strings] -> concat_loop(strings, accumulator <> string) + [] -> accumulator + } +} + +/// Creates a new `String` by repeating a `String` a given number of times. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert repeat("ha", times: 3) == "hahaha" +/// ``` +/// +pub fn repeat(string: String, times times: Int) -> String { + case times <= 0 { + True -> "" + False -> repeat_loop(times, string, "") + } +} + +fn repeat_loop(times: Int, doubling_acc: String, acc: String) -> String { + let acc = case times % 2 { + 0 -> acc + _ -> acc <> doubling_acc + } + let times = times / 2 + case times <= 0 { + True -> acc + False -> repeat_loop(times, doubling_acc <> doubling_acc, acc) + } +} + +/// Joins many `String`s together with a given separator. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// assert join(["home","evan","Desktop"], with: "/") == "home/evan/Desktop" +/// ``` +/// +pub fn join(strings: List(String), with separator: String) -> String { + case strings { + [] -> "" + [first, ..rest] -> join_loop(rest, separator, first) + } +} + +fn join_loop( + strings: List(String), + separator: String, + accumulator: String, +) -> String { + case strings { + [] -> accumulator + [string, ..strings] -> + join_loop(strings, separator, accumulator <> separator <> string) + } +} + +/// Pads the start of a `String` until it has a given length. +/// +/// ## Examples +/// +/// ```gleam +/// assert pad_start("121", to: 5, with: ".") == "..121" +/// ``` +/// +/// ```gleam +/// assert pad_start("121", to: 3, with: ".") == "121" +/// ``` +/// +/// ```gleam +/// assert pad_start("121", to: 2, with: ".") == "121" +/// ``` +/// +pub fn pad_start( + string: String, + to desired_length: Int, + with pad_string: String, +) -> String { + let current_length = length(string) + let to_pad_length = desired_length - current_length + + case to_pad_length <= 0 { + True -> string + False -> padding(to_pad_length, pad_string) <> string + } +} + +/// Pads the end of a `String` until it has a given length. +/// +/// ## Examples +/// +/// ```gleam +/// assert pad_end("123", to: 5, with: ".") == "123.." +/// ``` +/// +/// ```gleam +/// assert pad_end("123", to: 3, with: ".") == "123" +/// ``` +/// +/// ```gleam +/// assert pad_end("123", to: 2, with: ".") == "123" +/// ``` +/// +pub fn pad_end( + string: String, + to desired_length: Int, + with pad_string: String, +) -> String { + let current_length = length(string) + let to_pad_length = desired_length - current_length + + case to_pad_length <= 0 { + True -> string + False -> string <> padding(to_pad_length, pad_string) + } +} + +fn padding(size: Int, pad_string: String) -> String { + let pad_string_length = length(pad_string) + let num_pads = size / pad_string_length + let extra = size % pad_string_length + + repeat(pad_string, num_pads) <> slice(pad_string, 0, extra) +} + +/// Removes whitespace on both sides of a `String`. +/// +/// Whitespace in this function is the set of nonbreakable whitespace +/// codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1]. +/// +/// [1]: https://unicode.org/reports/tr31/ +/// +/// ## Examples +/// +/// ```gleam +/// assert trim(" hats \n") == "hats" +/// ``` +/// +pub fn trim(string: String) -> String { + string |> trim_start |> trim_end +} + +@external(erlang, "string", "trim") +fn erl_trim(a: String, b: Direction) -> String + +type Direction { + Leading + Trailing +} + +/// Removes whitespace at the start of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// assert trim_start(" hats \n") == "hats \n" +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "trim_start") +pub fn trim_start(string: String) -> String { + erl_trim(string, Leading) +} + +/// Removes whitespace at the end of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// assert trim_end(" hats \n") == " hats" +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "trim_end") +pub fn trim_end(string: String) -> String { + erl_trim(string, Trailing) +} + +/// Splits a non-empty `String` into its first element (head) and rest (tail). +/// This lets you pattern match on `String`s exactly as you would with lists. +/// +/// ## Performance +/// +/// There is a notable overhead to using this function, so you may not want to +/// use it in a tight loop. If you wish to efficiently parse a string you may +/// want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter). +/// +/// ## Examples +/// +/// ```gleam +/// assert pop_grapheme("gleam") == Ok(#("g", "leam")) +/// ``` +/// +/// ```gleam +/// assert pop_grapheme("") == Error(Nil) +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_pop_grapheme") +@external(javascript, "../gleam_stdlib.mjs", "pop_grapheme") +pub fn pop_grapheme(string: String) -> Result(#(String, String), Nil) + +/// Converts a `String` to a list of +/// [graphemes](https://en.wikipedia.org/wiki/Grapheme). +/// +/// ```gleam +/// assert to_graphemes("abc") == ["a", "b", "c"] +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +pub fn to_graphemes(string: String) -> List(String) { + string + |> to_graphemes_loop([]) + |> list.reverse +} + +fn to_graphemes_loop(string: String, acc: List(String)) -> List(String) { + case pop_grapheme(string) { + Ok(#(grapheme, rest)) -> to_graphemes_loop(rest, [grapheme, ..acc]) + Error(_) -> acc + } +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "codepoint") +fn unsafe_int_to_utf_codepoint(a: Int) -> UtfCodepoint + +/// Converts a `String` to a `List` of `UtfCodepoint`. +/// +/// See and +/// for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// assert "a" |> to_utf_codepoints == [UtfCodepoint(97)] +/// ``` +/// +/// ```gleam +/// // Semantically the same as: +/// // ["🏳", "️", "‍", "🌈"] or: +/// // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow] +/// assert "🏳️‍🌈" |> to_utf_codepoints +/// == [ +/// UtfCodepoint(127987), +/// UtfCodepoint(65039), +/// UtfCodepoint(8205), +/// UtfCodepoint(127752), +/// ] +/// ``` +/// +pub fn to_utf_codepoints(string: String) -> List(UtfCodepoint) { + do_to_utf_codepoints(string) +} + +@target(erlang) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + to_utf_codepoints_loop(<>, []) +} + +@target(erlang) +fn to_utf_codepoints_loop( + bit_array: BitArray, + acc: List(UtfCodepoint), +) -> List(UtfCodepoint) { + case bit_array { + <> -> + to_utf_codepoints_loop(rest, [first, ..acc]) + _ -> list.reverse(acc) + } +} + +@target(javascript) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + string + |> string_to_codepoint_integer_list + |> list.map(unsafe_int_to_utf_codepoint) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "string_to_codepoint_integer_list") +fn string_to_codepoint_integer_list(string: String) -> List(Int) + +/// Converts a `List` of `UtfCodepoint`s to a `String`. +/// +/// See and +/// for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(a) = utf_codepoint(97) +/// let assert Ok(b) = utf_codepoint(98) +/// let assert Ok(c) = utf_codepoint(99) +/// assert from_utf_codepoints([a, b, c]) == "abc" +/// ``` +/// +@external(erlang, "gleam_stdlib", "utf_codepoint_list_to_string") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_list_to_string") +pub fn from_utf_codepoints(utf_codepoints: List(UtfCodepoint)) -> String + +/// Converts an integer to a `UtfCodepoint`. +/// +/// Returns an `Error` if the integer does not represent a valid UTF codepoint. +/// +pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { + case value { + i if i > 1_114_111 -> Error(Nil) + i if i >= 55_296 && i <= 57_343 -> Error(Nil) + i if i < 0 -> Error(Nil) + i -> Ok(unsafe_int_to_utf_codepoint(i)) + } +} + +/// Converts a `UtfCodepoint` to its ordinal code point value. +/// +/// ## Examples +/// +/// ```gleam +/// let assert [utf_codepoint, ..] = to_utf_codepoints("💜") +/// assert utf_codepoint_to_int(utf_codepoint) == 128156 +/// ``` +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_to_int") +pub fn utf_codepoint_to_int(cp: UtfCodepoint) -> Int + +/// Converts a `String` into `Option(String)` where an empty `String` becomes +/// `None`. +/// +/// ## Examples +/// +/// ```gleam +/// assert to_option("") == None +/// ``` +/// +/// ```gleam +/// assert to_option("hats") == Some("hats") +/// ``` +/// +pub fn to_option(string: String) -> Option(String) { + case string { + "" -> None + _ -> Some(string) + } +} + +/// Returns the first grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// ## Examples +/// +/// ```gleam +/// assert first("") == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert first("icecream") == Ok("i") +/// ``` +/// +pub fn first(string: String) -> Result(String, Nil) { + case pop_grapheme(string) { + Ok(#(first, _)) -> Ok(first) + Error(e) -> Error(e) + } +} + +/// Returns the last grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// This function traverses the full string, so it runs in linear time with the +/// length of the string. Avoid using this in a loop. +/// +/// ## Examples +/// +/// ```gleam +/// assert last("") == Error(Nil) +/// ``` +/// +/// ```gleam +/// assert last("icecream") == Ok("m") +/// ``` +/// +pub fn last(string: String) -> Result(String, Nil) { + case pop_grapheme(string) { + Ok(#(first, "")) -> Ok(first) + Ok(#(_, rest)) -> Ok(slice(rest, -1, 1)) + Error(e) -> Error(e) + } +} + +/// Creates a new `String` with the first grapheme in the input `String` +/// converted to uppercase and the remaining graphemes to lowercase. +/// +/// ## Examples +/// +/// ```gleam +/// assert capitalise("mamouna") == "Mamouna" +/// ``` +/// +pub fn capitalise(string: String) -> String { + case pop_grapheme(string) { + Ok(#(first, rest)) -> append(to: uppercase(first), suffix: lowercase(rest)) + Error(_) -> "" + } +} + +/// Returns a `String` representation of a term in Gleam syntax. +/// +/// This may be occasionally useful for quick-and-dirty printing of values in +/// scripts. For error reporting and other uses prefer constructing strings by +/// pattern matching on the values. +/// +/// ## Limitations +/// +/// The output format of this function is not stable and could change at any +/// time. The output is not suitable for parsing. +/// +/// This function works using runtime reflection, so the output may not be +/// perfectly accurate for data structures where the runtime structure doesn't +/// hold enough information to determine the original syntax. For example, +/// tuples with an Erlang atom in the first position will be mistaken for Gleam +/// records. +/// +/// ## Security and safety +/// +/// There is no limit to how large the strings that this function can produce. +/// Be careful not to call this function with large data structures or you +/// could use very large amounts of memory, potentially causing runtime +/// problems. +/// +pub fn inspect(term: anything) -> String { + term + |> do_inspect + |> string_tree.to_string +} + +@external(erlang, "gleam_stdlib", "inspect") +@external(javascript, "../gleam_stdlib.mjs", "inspect") +fn do_inspect(term: anything) -> StringTree + +/// Returns the number of bytes in a `String`. +/// +/// This function runs in constant time on Erlang and in linear time on +/// JavaScript. +/// +/// ## Examples +/// +/// ```gleam +/// assert byte_size("🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻") == 58 +/// ``` +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "byte_size") +pub fn byte_size(string: String) -> Int + +/// Removes the given prefix from the start of a `String`, if present. +/// +/// If the `String` does not start with the given prefix the string is returned +/// unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert remove_prefix("@lpil", "@") == "lpil" +/// ``` +/// +/// ```gleam +/// assert remove_prefix("hello!", "@") == "hello!" +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_remove_prefix") +@external(javascript, "../gleam_stdlib.mjs", "string_remove_prefix") +pub fn remove_prefix(from string: String, matching prefix: String) -> String + +/// Removes the given suffix from the end of a `String`, if present. +/// +/// If the `String` does not end with the given suffix the string is returned +/// unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// assert remove_suffix("Hello!", "!") == "Hello" +/// ``` +/// +/// ```gleam +/// assert remove_suffix("Hello!?", "!") == "Hello!?" +/// ``` +/// +@external(erlang, "gleam_stdlib", "string_remove_suffix") +@external(javascript, "../gleam_stdlib.mjs", "string_remove_suffix") +pub fn remove_suffix(from string: String, matching suffix: String) -> String diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string_tree.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string_tree.gleam new file mode 100644 index 0000000..98eba25 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/string_tree.gleam @@ -0,0 +1,203 @@ +import gleam/list + +/// `StringTree` is a type used for efficiently building text content to be +/// written to a file or a socket. Internally it is represented as a tree so to +/// append or prepend to a string tree is a constant time operation that +/// allocates a new node in the tree without copying any of the content. When +/// writing to an output stream the tree is traversed and the content is sent +/// directly rather than copying it into a single buffer beforehand. +/// +/// On Erlang this type is compatible with Erlang's iodata. On JavaScript this +/// type is compatible with normal strings. +/// +/// The BEAM virtual machine has an optimisation for appending strings, where it +/// will mutate the string buffer when safe to do so, so if you are looking to +/// build a string through appending many small strings then you may get better +/// performance by not using a string tree. Always benchmark your performance +/// sensitive code. +/// +pub type StringTree + +/// Create an empty `StringTree`. Useful as the start of a pipe chaining many +/// trees together. +/// +pub fn new() -> StringTree { + from_strings([]) +} + +/// Prepends a `String` onto the start of some `StringTree`. +/// +/// Runs in constant time. +/// +pub fn prepend(to tree: StringTree, prefix prefix: String) -> StringTree { + append_tree(from_string(prefix), tree) +} + +/// Appends a `String` onto the end of some `StringTree`. +/// +/// Runs in constant time. +/// +pub fn append(to tree: StringTree, suffix second: String) -> StringTree { + append_tree(tree, from_string(second)) +} + +/// Prepends some `StringTree` onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_tree( + to tree: StringTree, + prefix prefix: StringTree, +) -> StringTree { + append_tree(prefix, tree) +} + +/// Appends some `StringTree` onto the end of another. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "iodata_append") +@external(javascript, "../gleam_stdlib.mjs", "add") +pub fn append_tree(to tree: StringTree, suffix suffix: StringTree) -> StringTree + +/// Converts a list of strings into a `StringTree`. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +pub fn from_strings(strings: List(String)) -> StringTree + +/// Joins a list of trees into a single tree. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +pub fn concat(trees: List(StringTree)) -> StringTree + +/// Converts a string into a `StringTree`. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn from_string(string: String) -> StringTree + +/// Turns a `StringTree` into a `String`. +/// +/// This function is implemented natively by the virtual machine and is highly +/// optimised. +/// +@external(erlang, "unicode", "characters_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "identity") +pub fn to_string(tree: StringTree) -> String + +/// Returns the size of the `StringTree` in bytes. +/// +@external(erlang, "erlang", "iolist_size") +@external(javascript, "../gleam_stdlib.mjs", "length") +pub fn byte_size(tree: StringTree) -> Int + +/// Joins the given trees into a new tree separated with the given string. +/// +pub fn join(trees: List(StringTree), with sep: String) -> StringTree { + trees + |> list.intersperse(from_string(sep)) + |> concat +} + +/// Converts a `StringTree` to a new one where the contents have been +/// lowercased. +/// +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +pub fn lowercase(tree: StringTree) -> StringTree + +/// Converts a `StringTree` to a new one where the contents have been +/// uppercased. +/// +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +pub fn uppercase(tree: StringTree) -> StringTree + +/// Converts a `StringTree` to a new one with the contents reversed. +/// +@external(erlang, "string", "reverse") +pub fn reverse(tree: StringTree) -> StringTree { + tree + |> to_string + |> do_to_graphemes + |> list.reverse + |> from_strings +} + +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +fn do_to_graphemes(string: String) -> List(String) + +type Direction { + All +} + +/// Splits a `StringTree` on a given pattern into a list of trees. +/// +@external(javascript, "../gleam_stdlib.mjs", "split") +pub fn split(tree: StringTree, on pattern: String) -> List(StringTree) { + erl_split(tree, pattern, All) +} + +@external(erlang, "string", "split") +fn erl_split(a: StringTree, b: String, c: Direction) -> List(StringTree) + +/// Replaces all instances of a pattern with a given string substitute. +/// +@external(erlang, "gleam_stdlib", "string_replace") +@external(javascript, "../gleam_stdlib.mjs", "string_replace") +pub fn replace( + in tree: StringTree, + each pattern: String, + with substitute: String, +) -> StringTree + +/// Compares two string trees to determine if they have the same textual +/// content. +/// +/// Comparing two string trees using the `==` operator may return `False` even +/// if they have the same content as they may have been build in different ways, +/// so using this function is often preferred. +/// +/// ## Examples +/// +/// ```gleam +/// assert from_strings(["a", "b"]) != from_string("ab") +/// ``` +/// +/// ```gleam +/// assert is_equal(from_strings(["a", "b"]), from_string("ab")) +/// ``` +/// +@external(erlang, "string", "equal") +pub fn is_equal(a: StringTree, b: StringTree) -> Bool { + a == b +} + +/// Inspects a `StringTree` to determine if it is equivalent to an empty string. +/// +/// ## Examples +/// +/// ```gleam +/// assert !{ from_string("ok") |> is_empty } +/// ``` +/// +/// ```gleam +/// assert from_string("") |> is_empty +/// ``` +/// +/// ```gleam +/// assert from_strings([]) |> is_empty +/// ``` +/// +@external(erlang, "string", "is_empty") +pub fn is_empty(tree: StringTree) -> Bool { + from_string("") == tree +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/uri.gleam b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/uri.gleam new file mode 100644 index 0000000..cc0c3ac --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam/uri.gleam @@ -0,0 +1,769 @@ +//// Utilities for working with URIs +//// +//// This module provides functions for working with URIs (for example, parsing +//// URIs or encoding query strings). The functions in this module are implemented +//// according to [RFC 3986](https://tools.ietf.org/html/rfc3986). +//// +//// Query encoding (Form encoding) is defined in the +//// [W3C specification](https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data). + +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/string +import gleam/string_tree.{type StringTree} + +/// Type representing the parsed components of an URI. +/// All components of a URI are optional, except the path. +/// +pub type Uri { + Uri( + scheme: Option(String), + userinfo: Option(String), + host: Option(String), + port: Option(Int), + path: String, + query: Option(String), + fragment: Option(String), + ) +} + +/// Constant representing an empty URI, equivalent to "". +/// +/// ## Examples +/// +/// ```gleam +/// assert Uri(..empty, scheme: Some("https"), host: Some("example.com")) +/// == Uri( +/// scheme: Some("https"), +/// userinfo: None, +/// host: Some("example.com"), +/// port: None, +/// path: "", +/// query: None, +/// fragment: None, +/// ) +/// ``` +/// +pub const empty = Uri( + scheme: None, + userinfo: None, + host: None, + port: None, + path: "", + query: None, + fragment: None, +) + +/// Parses a compliant URI string into the `Uri` type. +/// If the string is not a valid URI string then an error is returned. +/// +/// The opposite operation is `uri.to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse("https://example.com:1234/a/b?query=true#fragment") +/// == Ok( +/// Uri( +/// scheme: Some("https"), +/// userinfo: None, +/// host: Some("example.com"), +/// port: Some(1234), +/// path: "/a/b", +/// query: Some("query=true"), +/// fragment: Some("fragment") +/// ) +/// ) +/// ``` +/// +@external(erlang, "gleam_stdlib", "uri_parse") +pub fn parse(uri_string: String) -> Result(Uri, Nil) { + // This parses a uri_string following the regex defined in + // https://tools.ietf.org/html/rfc3986#appendix-B + // + // TODO: This is not perfect and will be more permissive than its Erlang + // counterpart, ideally we want to replicate Erlang's implementation on the js + // target as well. + parse_scheme_loop(uri_string, uri_string, empty, 0) +} + +fn parse_scheme_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `/` is not allowed to appear in a scheme so we know it's over and we can + // start parsing the authority with slashes. + "/" <> _ if size == 0 -> parse_authority_with_slashes(uri_string, pieces) + "/" <> _ -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_authority_with_slashes(uri_string, pieces) + } + + // `?` is not allowed to appear in a scheme, in an authority, or in a path; + // so if we see it we know it marks the beginning of the query part. + "?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces) + "?" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_query_with_question_mark(rest, pieces) + } + + // `#` is not allowed to appear in a scheme, in an authority, in a path or + // in a query; so if we see it we know it marks the beginning of the final + // fragment. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_fragment(rest, pieces) + } + + // A colon marks the end of a uri scheme, but if it is not preceded by any + // character then it's not a valid URI. + ":" <> _ if size == 0 -> Error(Nil) + ":" <> rest -> { + let scheme = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme))) + parse_authority_with_slashes(rest, pieces) + } + + // If we could get to the end of the string and we've met no special + // chars whatsoever, that means the entire string is just a long path. + "" -> Ok(Uri(..pieces, path: original)) + + // In all other cases the first character is just a valid URI scheme + // character and we just keep munching characters until we reach the end of + // the uri scheme (or the end of the string and that would mean this is not + // a valid uri scheme since we found no `:`). + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_scheme_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_authority_with_slashes( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + case uri_string { + // To be a valid authority the string must start with a `//`, otherwise + // there's no authority and we just skip ahead to parsing the path. + "//" -> Ok(Uri(..pieces, host: Some(""))) + "//" <> rest -> parse_authority_pieces(rest, pieces) + _ -> parse_path(uri_string, pieces) + } +} + +fn parse_authority_pieces(string: String, pieces: Uri) -> Result(Uri, Nil) { + parse_userinfo_loop(string, string, pieces, 0) +} + +fn parse_userinfo_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `@` marks the end of the userinfo and the start of the host part in the + // authority string. + "@" <> rest if size == 0 -> parse_host(rest, pieces) + "@" <> rest -> { + let userinfo = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, userinfo: Some(userinfo)) + parse_host(rest, pieces) + } + + // If we reach the end of the authority string without finding an `@` + // special character, then we know that the authority doesn't actually + // contain the userinfo part. + // The entire string we just went through was a host! So we parse it as + // such. + "" | "/" <> _ | "?" <> _ | "#" <> _ -> parse_host(original, pieces) + + // In all other cases we just keep munching characters increasing the size + // of the userinfo bit. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_userinfo_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_host(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + // A host string can be in two formats: + // - \[[:.a-zA-Z0-9]*\] + // - [^:] + case uri_string { + // If we find an opening bracket we know it's the first format. + "[" <> _ -> parse_host_within_brackets(uri_string, pieces) + + // A `:` marks the beginning of the port part of the authority string. + ":" <> _ -> { + let pieces = Uri(..pieces, host: Some("")) + parse_port(uri_string, pieces) + } + + // If the string is empty then there's no need to keep going. The host is + // empty. + "" -> Ok(Uri(..pieces, host: Some(""))) + + // Otherwise it's the second format + _ -> parse_host_outside_of_brackets(uri_string, pieces) + } +} + +fn parse_host_within_brackets( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_host_within_brackets_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_host_within_brackets_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // If the string is over the entire string we were iterating through is the + // host part. + "" -> Ok(Uri(..pieces, host: Some(uri_string))) + + // A `]` marks the end of the host and the start of the port part. + "]" <> rest if size == 0 -> parse_port(rest, pieces) + "]" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size + 1) + let pieces = Uri(..pieces, host: Some(host)) + parse_port(rest, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ if size == 0 -> parse_path(uri_string, pieces) + "/" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_path(uri_string, pieces) + } + + // `?` marks the beginning of the query with question mark. + "?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces) + "?" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_fragment(rest, pieces) + } + + // In all other cases we just keep iterating. + _ -> { + let #(char, rest) = pop_codeunit(uri_string) + // Inside `[...]` there can only be some characters, if we find a special + // one then we know that we're actually parsing the other format for the + // host and we switch to that! + case is_valid_host_within_brackets_char(char) { + True -> + parse_host_within_brackets_loop(original, rest, pieces, size + 1) + + False -> + parse_host_outside_of_brackets_loop(original, original, pieces, 0) + } + } + } +} + +fn is_valid_host_within_brackets_char(char: Int) -> Bool { + // [0-9] + { 48 >= char && char <= 57 } + // [A-Z] + || { 65 >= char && char <= 90 } + // [a-z] + || { 97 >= char && char <= 122 } + // : + || char == 58 + // . + || char == 46 +} + +fn parse_host_outside_of_brackets( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_host_outside_of_brackets_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_host_outside_of_brackets_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + "" -> Ok(Uri(..pieces, host: Some(original))) + + // `:` marks the beginning of the port. + ":" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_port(uri_string, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_path(uri_string, pieces) + } + + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let host = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, host: Some(host)) + parse_fragment(rest, pieces) + } + + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_host_outside_of_brackets_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_port(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + case uri_string { + ":0" <> rest -> parse_port_loop(rest, pieces, 0) + ":1" <> rest -> parse_port_loop(rest, pieces, 1) + ":2" <> rest -> parse_port_loop(rest, pieces, 2) + ":3" <> rest -> parse_port_loop(rest, pieces, 3) + ":4" <> rest -> parse_port_loop(rest, pieces, 4) + ":5" <> rest -> parse_port_loop(rest, pieces, 5) + ":6" <> rest -> parse_port_loop(rest, pieces, 6) + ":7" <> rest -> parse_port_loop(rest, pieces, 7) + ":8" <> rest -> parse_port_loop(rest, pieces, 8) + ":9" <> rest -> parse_port_loop(rest, pieces, 9) + + // The port could be empty and be followed by any of the next delimiters. + // Like `:#`, `:?` or `:/` + ":" | "" -> Ok(pieces) + + // `?` marks the beginning of the query with question mark. + "?" <> rest | ":?" <> rest -> parse_query_with_question_mark(rest, pieces) + + // `#` marks the beginning of the fragment part. + "#" <> rest | ":#" <> rest -> parse_fragment(rest, pieces) + + // `/` marks the beginning of a path. + "/" <> _ -> parse_path(uri_string, pieces) + ":" <> rest -> + case rest { + "/" <> _ -> parse_path(rest, pieces) + _ -> Error(Nil) + } + + _ -> Error(Nil) + } +} + +fn parse_port_loop( + uri_string: String, + pieces: Uri, + port: Int, +) -> Result(Uri, Nil) { + case uri_string { + // As long as we find port numbers we keep accumulating those. + "0" <> rest -> parse_port_loop(rest, pieces, port * 10) + "1" <> rest -> parse_port_loop(rest, pieces, port * 10 + 1) + "2" <> rest -> parse_port_loop(rest, pieces, port * 10 + 2) + "3" <> rest -> parse_port_loop(rest, pieces, port * 10 + 3) + "4" <> rest -> parse_port_loop(rest, pieces, port * 10 + 4) + "5" <> rest -> parse_port_loop(rest, pieces, port * 10 + 5) + "6" <> rest -> parse_port_loop(rest, pieces, port * 10 + 6) + "7" <> rest -> parse_port_loop(rest, pieces, port * 10 + 7) + "8" <> rest -> parse_port_loop(rest, pieces, port * 10 + 8) + "9" <> rest -> parse_port_loop(rest, pieces, port * 10 + 9) + + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_fragment(rest, pieces) + } + + // `/` marks the beginning of a path. + "/" <> _ -> { + let pieces = Uri(..pieces, port: Some(port)) + parse_path(uri_string, pieces) + } + + // The string (and so the port) is over, we return what we parsed so far. + "" -> Ok(Uri(..pieces, port: Some(port))) + + // In all other cases we've ran into some invalid character inside the port + // so the uri is invalid! + _ -> Error(Nil) + } +} + +fn parse_path(uri_string: String, pieces: Uri) -> Result(Uri, Nil) { + parse_path_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_path_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `?` marks the beginning of the query with question mark. + "?" <> rest -> { + let path = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, path: path) + parse_query_with_question_mark(rest, pieces) + } + + // `#` marks the beginning of the fragment part. + "#" <> rest -> { + let path = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, path: path) + parse_fragment(rest, pieces) + } + + // If the string is over that means the entirety of the string was the path + // and it has an empty query and fragment. + "" -> Ok(Uri(..pieces, path: original)) + + // In all other cases the character is allowed to be part of the path so we + // just keep munching until we reach to its end. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_path_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_query_with_question_mark( + uri_string: String, + pieces: Uri, +) -> Result(Uri, Nil) { + parse_query_with_question_mark_loop(uri_string, uri_string, pieces, 0) +} + +fn parse_query_with_question_mark_loop( + original: String, + uri_string: String, + pieces: Uri, + size: Int, +) -> Result(Uri, Nil) { + case uri_string { + // `#` marks the beginning of the fragment part. + "#" <> rest if size == 0 -> parse_fragment(rest, pieces) + "#" <> rest -> { + let query = codeunit_slice(original, at_index: 0, length: size) + let pieces = Uri(..pieces, query: Some(query)) + parse_fragment(rest, pieces) + } + + // If the string is over that means the entirety of the string was the query + // and it has an empty fragment. + "" -> Ok(Uri(..pieces, query: Some(original))) + + // In all other cases the character is allowed to be part of the query so we + // just keep munching until we reach to its end. + _ -> { + let #(_, rest) = pop_codeunit(uri_string) + parse_query_with_question_mark_loop(original, rest, pieces, size + 1) + } + } +} + +fn parse_fragment(rest: String, pieces: Uri) -> Result(Uri, Nil) { + Ok(Uri(..pieces, fragment: Some(rest))) +} + +// WARN: this function returns invalid strings! +// We need to return a String anyways to have this as the representation on the +// JavaScript target. +// Alternatively, we could rewrite the entire code to use a single +// `fold_codeunits`-style loop and a state machine. +@external(erlang, "gleam_stdlib", "string_pop_codeunit") +@external(javascript, "../gleam_stdlib.mjs", "pop_codeunit") +fn pop_codeunit(str: String) -> #(Int, String) + +@external(erlang, "binary", "part") +@external(javascript, "../gleam_stdlib.mjs", "string_codeunit_slice") +fn codeunit_slice(str: String, at_index from: Int, length length: Int) -> String + +/// Parses an URL-encoded query string into a list of key value pairs. +/// Returns an error for invalid encoding. +/// +/// The opposite operation is `uri.query_to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// assert parse_query("a=1&b=2") == Ok([#("a", "1"), #("b", "2")]) +/// ``` +/// +@external(erlang, "gleam_stdlib", "parse_query") +@external(javascript, "../gleam_stdlib.mjs", "parse_query") +pub fn parse_query(query: String) -> Result(List(#(String, String)), Nil) + +/// Encodes a list of key value pairs as a URI query string. +/// +/// The opposite operation is `uri.parse_query`. +/// +/// ## Examples +/// +/// ```gleam +/// assert query_to_string([#("a", "1"), #("b", "2")]) == "a=1&b=2" +/// ``` +/// +pub fn query_to_string(query: List(#(String, String))) -> String { + query + |> list.map(query_pair) + |> list.intersperse(string_tree.from_string("&")) + |> string_tree.concat + |> string_tree.to_string +} + +fn query_pair(pair: #(String, String)) -> StringTree { + [percent_encode_query(pair.0), "=", percent_encode_query(pair.1)] + |> string_tree.from_strings +} + +fn percent_encode_query(part: String) -> String { + percent_encode(part) + |> string.replace(each: "+", with: "%2B") +} + +/// Encodes a string into a percent encoded representation. +/// +/// ## Examples +/// +/// ```gleam +/// assert percent_encode("100% great") == "100%25%20great" +/// ``` +/// +@external(erlang, "gleam_stdlib", "percent_encode") +@external(javascript, "../gleam_stdlib.mjs", "percent_encode") +pub fn percent_encode(value: String) -> String + +/// Decodes a percent encoded string. +/// +/// ## Examples +/// +/// ```gleam +/// assert percent_decode("100%25%20great+fun") == Ok("100% great+fun") +/// ``` +/// +@external(erlang, "gleam_stdlib", "percent_decode") +@external(javascript, "../gleam_stdlib.mjs", "percent_decode") +pub fn percent_decode(value: String) -> Result(String, Nil) + +/// Splits the path section of a URI into its constituent segments. +/// +/// Removes empty segments and resolves dot-segments as specified in +/// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC. +/// +/// ## Examples +/// +/// ```gleam +/// assert path_segments("/users/1") == ["users" ,"1"] +/// ``` +/// +pub fn path_segments(path: String) -> List(String) { + remove_dot_segments(string.split(path, "/")) +} + +fn remove_dot_segments(input: List(String)) -> List(String) { + remove_dot_segments_loop(input, []) +} + +fn remove_dot_segments_loop( + input: List(String), + accumulator: List(String), +) -> List(String) { + case input { + [] -> list.reverse(accumulator) + [segment, ..rest] -> { + let accumulator = case segment, accumulator { + "", accumulator -> accumulator + ".", accumulator -> accumulator + "..", [] -> [] + "..", [_, ..accumulator] -> accumulator + segment, accumulator -> [segment, ..accumulator] + } + remove_dot_segments_loop(rest, accumulator) + } + } +} + +/// Encodes a `Uri` value as a URI string. +/// +/// The opposite operation is `uri.parse`. +/// +/// ## Examples +/// +/// ```gleam +/// let uri = Uri(..empty, scheme: Some("https"), host: Some("example.com")) +/// assert to_string(uri) == "https://example.com" +/// ``` +/// +pub fn to_string(uri: Uri) -> String { + let parts = case uri.fragment { + Some(fragment) -> ["#", fragment] + None -> [] + } + let parts = case uri.query { + Some(query) -> ["?", query, ..parts] + None -> parts + } + let parts = [uri.path, ..parts] + let parts = case uri.host, string.starts_with(uri.path, "/") { + Some(host), False if host != "" -> ["/", ..parts] + _, _ -> parts + } + let parts = case uri.host, uri.port { + Some(_), Some(port) -> [":", int.to_string(port), ..parts] + _, _ -> parts + } + let parts = case uri.scheme, uri.userinfo, uri.host { + Some(s), Some(u), Some(h) -> [s, "://", u, "@", h, ..parts] + Some(s), None, Some(h) -> [s, "://", h, ..parts] + Some(s), Some(_), None | Some(s), None, None -> [s, ":", ..parts] + None, None, Some(h) -> ["//", h, ..parts] + _, _, _ -> parts + } + string.concat(parts) +} + +/// Fetches the origin of a URI. +/// +/// Returns the origin of a uri as defined in +/// [RFC 6454](https://tools.ietf.org/html/rfc6454) +/// +/// The supported URI schemes are `http` and `https`. +/// URLs without a scheme will return `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(uri) = parse("https://example.com/path?foo#bar") +/// assert origin(uri) == Ok("https://example.com") +/// ``` +/// +pub fn origin(uri: Uri) -> Result(String, Nil) { + let Uri(scheme: scheme, host: host, port: port, ..) = uri + case host, scheme { + Some(h), Some("https") if port == Some(443) -> + Ok(string.concat(["https://", h])) + Some(h), Some("http") if port == Some(80) -> + Ok(string.concat(["http://", h])) + Some(h), Some(s) if s == "http" || s == "https" -> { + case port { + Some(p) -> Ok(string.concat([s, "://", h, ":", int.to_string(p)])) + None -> Ok(string.concat([s, "://", h])) + } + } + _, _ -> Error(Nil) + } +} + +/// Resolves a URI with respect to the given base URI. +/// +/// The base URI must be an absolute URI or this function will return an error. +/// The algorithm for merging URIs is described in +/// [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2). +/// +pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) { + case base { + Uri(scheme: Some(_), host: Some(_), ..) -> + case relative { + Uri(host: Some(_), ..) -> { + let path = + relative.path + |> string.split("/") + |> remove_dot_segments() + |> join_segments() + let resolved = + Uri( + option.or(relative.scheme, base.scheme), + None, + relative.host, + option.or(relative.port, base.port), + path, + relative.query, + relative.fragment, + ) + Ok(resolved) + } + _ -> { + let #(new_path, new_query) = case relative.path { + "" -> #(base.path, option.or(relative.query, base.query)) + _ -> { + let path_segments = case string.starts_with(relative.path, "/") { + True -> string.split(relative.path, "/") + False -> + base.path + |> string.split("/") + |> drop_last() + |> list.append(string.split(relative.path, "/")) + } + let path = + path_segments + |> remove_dot_segments() + |> join_segments() + #(path, relative.query) + } + } + let resolved = + Uri( + base.scheme, + None, + base.host, + base.port, + new_path, + new_query, + relative.fragment, + ) + Ok(resolved) + } + } + _ -> Error(Nil) + } +} + +fn drop_last(elements: List(a)) -> List(a) { + list.take(from: elements, up_to: list.length(elements) - 1) +} + +fn join_segments(segments: List(String)) -> String { + string.join(["", ..segments], "/") +} diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bit_array.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bit_array.erl new file mode 100644 index 0000000..0f4b57b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bit_array.erl @@ -0,0 +1,347 @@ +-module(gleam@bit_array). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bit_array.gleam"). +-export([from_string/1, bit_size/1, byte_size/1, pad_to_bytes/1, slice/3, is_utf8/1, to_string/1, concat/1, append/2, base64_encode/2, base64_decode/1, base64_url_encode/2, base64_url_decode/1, base16_encode/1, base16_decode/1, inspect/1, compare/2, starts_with/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(" BitArrays are a sequence of binary data of any length.\n"). + +-file("src/gleam/bit_array.gleam", 11). +?DOC(" Converts a UTF-8 `String` type into a `BitArray`.\n"). +-spec from_string(binary()) -> bitstring(). +from_string(X) -> + gleam_stdlib:identity(X). + +-file("src/gleam/bit_array.gleam", 17). +?DOC(" Returns an integer which is the number of bits in the bit array.\n"). +-spec bit_size(bitstring()) -> integer(). +bit_size(X) -> + erlang:bit_size(X). + +-file("src/gleam/bit_array.gleam", 23). +?DOC(" Returns an integer which is the number of bytes in the bit array.\n"). +-spec byte_size(bitstring()) -> integer(). +byte_size(X) -> + erlang:byte_size(X). + +-file("src/gleam/bit_array.gleam", 29). +?DOC(" Pads a bit array with zeros so that it is a whole number of bytes.\n"). +-spec pad_to_bytes(bitstring()) -> bitstring(). +pad_to_bytes(X) -> + gleam_stdlib:bit_array_pad_to_bytes(X). + +-file("src/gleam/bit_array.gleam", 54). +?DOC( + " Extracts a sub-section of a bit array.\n" + "\n" + " The slice will start at given position and continue up to specified\n" + " length.\n" + " A negative length can be used to extract bytes at the end of a bit array.\n" + "\n" + " This function runs in constant time.\n" +). +-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} | + {error, nil}. +slice(String, Position, Length) -> + gleam_stdlib:bit_array_slice(String, Position, Length). + +-file("src/gleam/bit_array.gleam", 67). +-spec is_utf8_loop(bitstring()) -> boolean(). +is_utf8_loop(Bits) -> + case Bits of + <<>> -> + true; + + <<_/utf8, Rest/binary>> -> + is_utf8_loop(Rest); + + _ -> + false + end. + +-file("src/gleam/bit_array.gleam", 62). +?DOC(" Tests to see whether a bit array is valid UTF-8.\n"). +-spec is_utf8(bitstring()) -> boolean(). +is_utf8(Bits) -> + is_utf8_loop(Bits). + +-file("src/gleam/bit_array.gleam", 88). +?DOC( + " Converts a bit array to a string.\n" + "\n" + " Returns an error if the bit array is invalid UTF-8 data.\n" +). +-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}. +to_string(Bits) -> + case is_utf8(Bits) of + true -> + {ok, gleam_stdlib:identity(Bits)}; + + false -> + {error, nil} + end. + +-file("src/gleam/bit_array.gleam", 109). +?DOC( + " Creates a new bit array by joining multiple binaries.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert concat([from_string(\"butter\"), from_string(\"fly\")])\n" + " == from_string(\"butterfly\")\n" + " ```\n" +). +-spec concat(list(bitstring())) -> bitstring(). +concat(Bit_arrays) -> + gleam_stdlib:bit_array_concat(Bit_arrays). + +-file("src/gleam/bit_array.gleam", 40). +?DOC( + " Creates a new bit array by joining two bit arrays.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append(to: from_string(\"butter\"), suffix: from_string(\"fly\"))\n" + " == from_string(\"butterfly\")\n" + " ```\n" +). +-spec append(bitstring(), bitstring()) -> bitstring(). +append(First, Second) -> + gleam_stdlib:bit_array_concat([First, Second]). + +-file("src/gleam/bit_array.gleam", 118). +?DOC( + " Encodes a BitArray into a base 64 encoded string.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base64_encode(bitstring(), boolean()) -> binary(). +base64_encode(Input, Padding) -> + gleam_stdlib:base64_encode(Input, Padding). + +-file("src/gleam/bit_array.gleam", 122). +?DOC(" Decodes a base 64 encoded string into a `BitArray`.\n"). +-spec base64_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_decode(Encoded) -> + Padded = case erlang:byte_size(gleam_stdlib:identity(Encoded)) rem 4 of + 0 -> + Encoded; + + N -> + gleam@string:append( + Encoded, + gleam@string:repeat(<<"="/utf8>>, 4 - N) + ) + end, + gleam_stdlib:base64_decode(Padded). + +-file("src/gleam/bit_array.gleam", 140). +?DOC( + " Encodes a `BitArray` into a base 64 encoded string with URL and filename\n" + " safe alphabet.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base64_url_encode(bitstring(), boolean()) -> binary(). +base64_url_encode(Input, Padding) -> + _pipe = Input, + _pipe@1 = gleam_stdlib:base64_encode(_pipe, Padding), + _pipe@2 = gleam@string:replace(_pipe@1, <<"+"/utf8>>, <<"-"/utf8>>), + gleam@string:replace(_pipe@2, <<"/"/utf8>>, <<"_"/utf8>>). + +-file("src/gleam/bit_array.gleam", 150). +?DOC( + " Decodes a base 64 encoded string with URL and filename safe alphabet into a\n" + " `BitArray`.\n" +). +-spec base64_url_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_url_decode(Encoded) -> + _pipe = Encoded, + _pipe@1 = gleam@string:replace(_pipe, <<"-"/utf8>>, <<"+"/utf8>>), + _pipe@2 = gleam@string:replace(_pipe@1, <<"_"/utf8>>, <<"/"/utf8>>), + base64_decode(_pipe@2). + +-file("src/gleam/bit_array.gleam", 164). +?DOC( + " Encodes a `BitArray` into a base 16 encoded string.\n" + "\n" + " If the bit array does not contain a whole number of bytes then it is padded\n" + " with zero bits prior to being encoded.\n" +). +-spec base16_encode(bitstring()) -> binary(). +base16_encode(Input) -> + gleam_stdlib:base16_encode(Input). + +-file("src/gleam/bit_array.gleam", 170). +?DOC(" Decodes a base 16 encoded string into a `BitArray`.\n"). +-spec base16_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base16_decode(Input) -> + gleam_stdlib:base16_decode(Input). + +-file("src/gleam/bit_array.gleam", 191). +-spec inspect_loop(bitstring(), binary()) -> binary(). +inspect_loop(Input, Accumulator) -> + case Input of + <<>> -> + Accumulator; + + <> -> + <<<>/binary, + ":size(1)"/utf8>>; + + <> -> + <<<>/binary, + ":size(2)"/utf8>>; + + <> -> + <<<>/binary, + ":size(3)"/utf8>>; + + <> -> + <<<>/binary, + ":size(4)"/utf8>>; + + <> -> + <<<>/binary, + ":size(5)"/utf8>>; + + <> -> + <<<>/binary, + ":size(6)"/utf8>>; + + <> -> + <<<>/binary, + ":size(7)"/utf8>>; + + <> -> + Suffix = case Rest of + <<>> -> + <<""/utf8>>; + + _ -> + <<", "/utf8>> + end, + Accumulator@1 = <<<>/binary, + Suffix/binary>>, + inspect_loop(Rest, Accumulator@1); + + _ -> + Accumulator + end. + +-file("src/gleam/bit_array.gleam", 187). +?DOC( + " Converts a bit array to a string containing the decimal value of each byte.\n" + "\n" + " Use this over `string.inspect` when you have a bit array you want printed\n" + " in the array syntax even if it is valid UTF-8.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert inspect(<<0, 20, 0x20, 255>>) == \"<<0, 20, 32, 255>>\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert inspect(<<100, 5:3>>) == \"<<100, 5:size(3)>>\"\n" + " ```\n" +). +-spec inspect(bitstring()) -> binary(). +inspect(Input) -> + <<(inspect_loop(Input, <<"<<"/utf8>>))/binary, ">>"/utf8>>. + +-file("src/gleam/bit_array.gleam", 233). +?DOC( + " Compare two bit arrays as sequences of bytes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(<<1>>, <<2>>) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(<<\"AB\":utf8>>, <<\"AA\":utf8>>) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>) == Eq\n" + " ```\n" +). +-spec compare(bitstring(), bitstring()) -> gleam@order:order(). +compare(A, B) -> + case {A, B} of + {<>, + <>} -> + case {First_byte, Second_byte} of + {F, S} when F > S -> + gt; + + {F@1, S@1} when F@1 < S@1 -> + lt; + + {_, _} -> + compare(First_rest, Second_rest) + end; + + {<<>>, <<>>} -> + eq; + + {_, <<>>} -> + gt; + + {<<>>, _} -> + lt; + + {First, Second} -> + case {gleam_stdlib:bit_array_to_int_and_size(First), + gleam_stdlib:bit_array_to_int_and_size(Second)} of + {{A@1, _}, {B@1, _}} when A@1 > B@1 -> + gt; + + {{A@2, _}, {B@2, _}} when A@2 < B@2 -> + lt; + + {{_, Size_a}, {_, Size_b}} when Size_a > Size_b -> + gt; + + {{_, Size_a@1}, {_, Size_b@1}} when Size_a@1 < Size_b@1 -> + lt; + + {_, _} -> + eq + end + end. + +-file("src/gleam/bit_array.gleam", 273). +?DOC( + " Checks whether the first `BitArray` starts with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert starts_with(<<1, 2, 3, 4>>, <<1, 2>>)\n" + " ```\n" +). +-spec starts_with(bitstring(), bitstring()) -> boolean(). +starts_with(Bits, Prefix) -> + Prefix_size = erlang:bit_size(Prefix), + case Bits of + <> when Pref =:= Prefix -> + true; + + _ -> + false + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bool.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bool.erl new file mode 100644 index 0000000..edb3be8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bool.erl @@ -0,0 +1,334 @@ +-module(gleam@bool). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bool.gleam"). +-export(['and'/2, 'or'/2, negate/1, nor/2, nand/2, exclusive_or/2, exclusive_nor/2, to_string/1, guard/3, lazy_guard/3]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " A type with two possible values, `True` and `False`. Used to indicate whether\n" + " things are... true or false!\n" + "\n" + " It is often clearer and offers more type safety to define a custom type\n" + " than to use `Bool`. For example, rather than having a `is_teacher: Bool`\n" + " field consider having a `role: SchoolRole` field where `SchoolRole` is a custom\n" + " type that can be either `Student` or `Teacher`.\n" +). + +-file("src/gleam/bool.gleam", 32). +?DOC( + " Returns the and of two bools, but it evaluates both arguments.\n" + "\n" + " It's the function equivalent of the `&&` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert and(True, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !and(False, False)\n" + " ```\n" +). +-spec 'and'(boolean(), boolean()) -> boolean(). +'and'(A, B) -> + A andalso B. + +-file("src/gleam/bool.gleam", 59). +?DOC( + " Returns the or of two bools, but it evaluates both arguments.\n" + "\n" + " It's the function equivalent of the `||` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(True, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !or(False, False)\n" + " ```\n" +). +-spec 'or'(boolean(), boolean()) -> boolean(). +'or'(A, B) -> + A orelse B. + +-file("src/gleam/bool.gleam", 77). +?DOC( + " Returns the opposite bool value.\n" + "\n" + " This is the same as the `!` or `not` operators in some other languages.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !negate(True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(False)\n" + " ```\n" +). +-spec negate(boolean()) -> boolean(). +negate(Bool) -> + not Bool. + +-file("src/gleam/bool.gleam", 101). +?DOC( + " Returns the nor of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert nor(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nor(True, True)\n" + " ```\n" +). +-spec nor(boolean(), boolean()) -> boolean(). +nor(A, B) -> + not (A orelse B). + +-file("src/gleam/bool.gleam", 125). +?DOC( + " Returns the nand of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert nand(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert nand(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert nand(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !nand(True, True)\n" + " ```\n" +). +-spec nand(boolean(), boolean()) -> boolean(). +nand(A, B) -> + not (A andalso B). + +-file("src/gleam/bool.gleam", 149). +?DOC( + " Returns the exclusive or of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !exclusive_or(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_or(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_or(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_or(True, True)\n" + " ```\n" +). +-spec exclusive_or(boolean(), boolean()) -> boolean(). +exclusive_or(A, B) -> + A /= B. + +-file("src/gleam/bool.gleam", 173). +?DOC( + " Returns the exclusive nor of two bools.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert exclusive_nor(False, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_nor(False, True)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !exclusive_nor(True, False)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exclusive_nor(True, True)\n" + " ```\n" +). +-spec exclusive_nor(boolean(), boolean()) -> boolean(). +exclusive_nor(A, B) -> + A =:= B. + +-file("src/gleam/bool.gleam", 189). +?DOC( + " Returns a string representation of the given bool.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(True) == \"True\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_string(False) == \"False\"\n" + " ```\n" +). +-spec to_string(boolean()) -> binary(). +to_string(Bool) -> + case Bool of + false -> + <<"False"/utf8>>; + + true -> + <<"True"/utf8>> + end. + +-file("src/gleam/bool.gleam", 248). +?DOC( + " Run a callback function if the given bool is `False`, otherwise return a\n" + " default value.\n" + "\n" + " With a `use` expression this function can simulate the early-return pattern\n" + " found in some other programming languages.\n" + "\n" + " In a procedural language:\n" + "\n" + " ```js\n" + " if (predicate) return value;\n" + " // ...\n" + " ```\n" + "\n" + " In Gleam with a `use` expression:\n" + "\n" + " ```gleam\n" + " use <- guard(when: predicate, return: value)\n" + " // ...\n" + " ```\n" + "\n" + " Like everything in Gleam `use` is an expression, so it short circuits the\n" + " current block, not the entire function. As a result you can assign the value\n" + " to a variable:\n" + "\n" + " ```gleam\n" + " let x = {\n" + " use <- guard(when: predicate, return: value)\n" + " // ...\n" + " }\n" + " ```\n" + "\n" + " Note that unlike in procedural languages the `return` value is evaluated\n" + " even when the predicate is `False`, so it is advisable not to perform\n" + " expensive computation nor side-effects there.\n" + "\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let name = \"\"\n" + " use <- guard(when: name == \"\", return: \"Welcome!\")\n" + " \"Hello, \" <> name\n" + " // -> \"Welcome!\"\n" + " ```\n" + "\n" + " ```gleam\n" + " let name = \"Kamaka\"\n" + " use <- guard(when: name == \"\", return: \"Welcome!\")\n" + " \"Hello, \" <> name\n" + " // -> \"Hello, Kamaka\"\n" + " ```\n" +). +-spec guard(boolean(), BTP, fun(() -> BTP)) -> BTP. +guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence; + + false -> + Alternative() + end. + +-file("src/gleam/bool.gleam", 289). +?DOC( + " Runs a callback function if the given bool is `True`, otherwise runs an\n" + " alternative callback function.\n" + "\n" + " Useful when further computation should be delayed regardless of the given\n" + " bool's value.\n" + "\n" + " See [`guard`](#guard) for more info.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let name = \"Kamaka\"\n" + " let inquiry = fn() { \"How may we address you?\" }\n" + " use <- lazy_guard(when: name == \"\", return: inquiry)\n" + " \"Hello, \" <> name\n" + " // -> \"Hello, Kamaka\"\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " let name = \"\"\n" + " let greeting = fn() { \"Hello, \" <> name }\n" + " use <- lazy_guard(when: name == \"\", otherwise: greeting)\n" + " let number = int.random(99)\n" + " let name = \"User \" <> int.to_string(number)\n" + " \"Welcome, \" <> name\n" + " // -> \"Welcome, User 54\"\n" + " ```\n" +). +-spec lazy_guard(boolean(), fun(() -> BTQ), fun(() -> BTQ)) -> BTQ. +lazy_guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence(); + + false -> + Alternative() + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl new file mode 100644 index 0000000..e9c906b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@bytes_tree.erl @@ -0,0 +1,211 @@ +-module(gleam@bytes_tree). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/bytes_tree.gleam"). +-export([append_tree/2, prepend_tree/2, concat/1, new/0, from_string/1, prepend_string/2, append_string/2, from_string_tree/1, from_bit_array/1, prepend/2, append/2, concat_bit_arrays/1, to_bit_array/1, byte_size/1]). +-export_type([bytes_tree/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " `BytesTree` is a type used for efficiently building binary content to be\n" + " written to a file or a socket. Internally it is represented as a tree so to\n" + " append or prepend to a bytes tree is a constant time operation that\n" + " allocates a new node in the tree without copying any of the content. When\n" + " writing to an output stream the tree is traversed and the content is sent\n" + " directly rather than copying it into a single buffer beforehand.\n" + "\n" + " If we append one bit array to another the bit arrays must be copied to a\n" + " new location in memory so that they can sit together. This behaviour\n" + " enables efficient reading of the data but copying can be expensive,\n" + " especially if we want to join many bit arrays together.\n" + "\n" + " BytesTree is different in that it can be joined together in constant\n" + " time using minimal memory, and then can be efficiently converted to a\n" + " bit array using the `to_bit_array` function.\n" + "\n" + " Byte trees are always byte aligned, so that a number of bits that is not\n" + " divisible by 8 will be padded with 0s.\n" + "\n" + " On Erlang this type is compatible with Erlang's iolists.\n" +). + +-opaque bytes_tree() :: {bytes, bitstring()} | + {text, gleam@string_tree:string_tree()} | + {many, list(bytes_tree())}. + +-file("src/gleam/bytes_tree.gleam", 68). +?DOC( + " Appends a bytes tree onto the end of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec append_tree(bytes_tree(), bytes_tree()) -> bytes_tree(). +append_tree(First, Second) -> + gleam_stdlib:iodata_append(First, Second). + +-file("src/gleam/bytes_tree.gleam", 59). +?DOC( + " Prepends a bytes tree onto the start of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend_tree(bytes_tree(), bytes_tree()) -> bytes_tree(). +prepend_tree(Second, First) -> + gleam_stdlib:iodata_append(First, Second). + +-file("src/gleam/bytes_tree.gleam", 98). +?DOC( + " Joins a list of bytes trees into a single one.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat(list(bytes_tree())) -> bytes_tree(). +concat(Trees) -> + gleam_stdlib:identity(Trees). + +-file("src/gleam/bytes_tree.gleam", 35). +?DOC( + " Create an empty `BytesTree`. Useful as the start of a pipe chaining many\n" + " trees together.\n" +). +-spec new() -> bytes_tree(). +new() -> + gleam_stdlib:identity([]). + +-file("src/gleam/bytes_tree.gleam", 118). +?DOC( + " Creates a new bytes tree from a string.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time otherwise.\n" +). +-spec from_string(binary()) -> bytes_tree(). +from_string(String) -> + gleam_stdlib:wrap_list(String). + +-file("src/gleam/bytes_tree.gleam", 80). +?DOC( + " Prepends a string onto the start of a bytes tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time with the length of the string otherwise.\n" +). +-spec prepend_string(bytes_tree(), binary()) -> bytes_tree(). +prepend_string(Second, First) -> + gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second). + +-file("src/gleam/bytes_tree.gleam", 89). +?DOC( + " Appends a string onto the end of a bytes tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time with the length of the string otherwise.\n" +). +-spec append_string(bytes_tree(), binary()) -> bytes_tree(). +append_string(First, Second) -> + gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)). + +-file("src/gleam/bytes_tree.gleam", 128). +?DOC( + " Creates a new bytes tree from a string tree.\n" + "\n" + " Runs in constant time when running on Erlang.\n" + " Runs in linear time otherwise.\n" +). +-spec from_string_tree(gleam@string_tree:string_tree()) -> bytes_tree(). +from_string_tree(Tree) -> + gleam_stdlib:wrap_list(Tree). + +-file("src/gleam/bytes_tree.gleam", 136). +?DOC( + " Creates a new bytes tree from a bit array.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_bit_array(bitstring()) -> bytes_tree(). +from_bit_array(Bits) -> + _pipe = Bits, + _pipe@1 = gleam_stdlib:bit_array_pad_to_bytes(_pipe), + gleam_stdlib:wrap_list(_pipe@1). + +-file("src/gleam/bytes_tree.gleam", 43). +?DOC( + " Prepends a bit array to the start of a bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend(bytes_tree(), bitstring()) -> bytes_tree(). +prepend(Second, First) -> + gleam_stdlib:iodata_append(from_bit_array(First), Second). + +-file("src/gleam/bytes_tree.gleam", 51). +?DOC( + " Appends a bit array to the end of a bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec append(bytes_tree(), bitstring()) -> bytes_tree(). +append(First, Second) -> + gleam_stdlib:iodata_append(First, from_bit_array(Second)). + +-file("src/gleam/bytes_tree.gleam", 106). +?DOC( + " Joins a list of bit arrays into a single bytes tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat_bit_arrays(list(bitstring())) -> bytes_tree(). +concat_bit_arrays(Bits) -> + _pipe = Bits, + _pipe@1 = gleam@list:map(_pipe, fun from_bit_array/1), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/bytes_tree.gleam", 162). +-spec to_list(list(list(bytes_tree())), list(bitstring())) -> list(bitstring()). +to_list(Stack, Acc) -> + case Stack of + [] -> + Acc; + + [[] | Remaining_stack] -> + to_list(Remaining_stack, Acc); + + [[{bytes, Bits} | Rest] | Remaining_stack@1] -> + to_list([Rest | Remaining_stack@1], [Bits | Acc]); + + [[{text, Tree} | Rest@1] | Remaining_stack@2] -> + Bits@1 = gleam_stdlib:identity(unicode:characters_to_binary(Tree)), + to_list([Rest@1 | Remaining_stack@2], [Bits@1 | Acc]); + + [[{many, Trees} | Rest@2] | Remaining_stack@3] -> + to_list([Trees, Rest@2 | Remaining_stack@3], Acc) + end. + +-file("src/gleam/bytes_tree.gleam", 155). +?DOC( + " Turns a bytes tree into a bit array.\n" + "\n" + " Runs in linear time.\n" + "\n" + " When running on Erlang this function is implemented natively by the\n" + " virtual machine and is highly optimised.\n" +). +-spec to_bit_array(bytes_tree()) -> bitstring(). +to_bit_array(Tree) -> + erlang:list_to_bitstring(Tree). + +-file("src/gleam/bytes_tree.gleam", 186). +?DOC( + " Returns the size of the bytes tree's content in bytes.\n" + "\n" + " Runs in linear time.\n" +). +-spec byte_size(bytes_tree()) -> integer(). +byte_size(Tree) -> + erlang:iolist_size(Tree). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dict.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dict.erl new file mode 100644 index 0000000..e3e818e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dict.erl @@ -0,0 +1,513 @@ +-module(gleam@dict). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dict.gleam"). +-export([size/1, is_empty/1, has_key/2, new/0, get/2, insert/3, from_list/1, map_values/2, take/2, delete/2, drop/2, upsert/3, fold/3, to_list/1, keys/1, values/1, filter/2, each/2, combine/3, merge/2, group/2]). +-export_type([dict/2, transient_dict/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type dict(JL, JM) :: any() | {gleam_phantom, JL, JM}. + +-type transient_dict(JN, JO) :: any() | {gleam_phantom, JN, JO}. + +-file("src/gleam/dict.gleam", 53). +?DOC( + " Determines the number of key-value pairs in the dict.\n" + " This function runs in constant time and does not need to iterate the dict.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> size == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"key\", \"value\") |> size == 1\n" + " ```\n" +). +-spec size(dict(any(), any())) -> integer(). +size(Dict) -> + maps:size(Dict). + +-file("src/gleam/dict.gleam", 67). +?DOC( + " Determines whether or not the dict is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(\"b\", 1) |> is_empty }\n" + " ```\n" +). +-spec is_empty(dict(any(), any())) -> boolean(). +is_empty(Dict) -> + maps:size(Dict) =:= 0. + +-file("src/gleam/dict.gleam", 135). +?DOC( + " Determines whether or not a value is present in the dict for a given key.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> has_key(\"a\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(\"a\", 0) |> has_key(\"b\") }\n" + " ```\n" +). +-spec has_key(dict(LA, any()), LA) -> boolean(). +has_key(Dict, Key) -> + maps:is_key(Key, Dict). + +-file("src/gleam/dict.gleam", 146). +?DOC(" Creates a fresh dict that contains no values.\n"). +-spec new() -> dict(any(), any()). +new() -> + maps:new(). + +-file("src/gleam/dict.gleam", 165). +?DOC( + " Fetches a value from a dict for a given key.\n" + "\n" + " The dict may not have a value for the key, so the value is wrapped in a\n" + " `Result`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> get(\"a\") == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> get(\"b\") == Error(Nil)\n" + " ```\n" +). +-spec get(dict(LM, LN), LM) -> {ok, LN} | {error, nil}. +get(From, Get) -> + gleam_stdlib:map_get(From, Get). + +-file("src/gleam/dict.gleam", 183). +?DOC( + " Inserts a value into the dict with the given key.\n" + "\n" + " If the dict already has a value for the given key then the value is\n" + " replaced with the new value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) == from_list([#(\"a\", 0)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert new() |> insert(\"a\", 0) |> insert(\"a\", 5) == from_list([#(\"a\", 5)])\n" + " ```\n" +). +-spec insert(dict(LS, LT), LS, LT) -> dict(LS, LT). +insert(Dict, Key, Value) -> + maps:put(Key, Value, Dict). + +-file("src/gleam/dict.gleam", 111). +-spec from_list_loop(transient_dict(KT, KU), list({KT, KU})) -> dict(KT, KU). +from_list_loop(Transient, List) -> + case List of + [] -> + gleam_stdlib:identity(Transient); + + [{Key, Value} | Rest] -> + from_list_loop(maps:put(Key, Value, Transient), Rest) + end. + +-file("src/gleam/dict.gleam", 107). +?DOC( + " Converts a list of 2-element tuples `#(key, value)` to a dict.\n" + "\n" + " If two tuples have the same key the last one in the list will be the one\n" + " that is present in the dict.\n" +). +-spec from_list(list({KO, KP})) -> dict(KO, KP). +from_list(List) -> + maps:from_list(List). + +-file("src/gleam/dict.gleam", 210). +?DOC( + " Updates all values in a given dict by calling a given function on each key\n" + " and value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(3, 3), #(2, 4)])\n" + " |> map_values(fn(key, value) { key * value })\n" + " == from_list([#(3, 9), #(2, 8)])\n" + " ```\n" +). +-spec map_values(dict(MK, ML), fun((MK, ML) -> MO)) -> dict(MK, MO). +map_values(Dict, Fun) -> + maps:map(Fun, Dict). + +-file("src/gleam/dict.gleam", 313). +-spec do_take_loop(dict(OI, OJ), list(OI), transient_dict(OI, OJ)) -> dict(OI, OJ). +do_take_loop(Dict, Desired_keys, Acc) -> + case Desired_keys of + [] -> + gleam_stdlib:identity(Acc); + + [Key | Rest] -> + case gleam_stdlib:map_get(Dict, Key) of + {ok, Value} -> + do_take_loop(Dict, Rest, maps:put(Key, Value, Acc)); + + {error, _} -> + do_take_loop(Dict, Rest, Acc) + end + end. + +-file("src/gleam/dict.gleam", 304). +?DOC( + " Creates a new dict from a given dict, only including any entries for which the\n" + " keys are in a given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> take([\"b\"])\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> take([\"a\", \"b\", \"c\"])\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec take(dict(NU, NV), list(NU)) -> dict(NU, NV). +take(Dict, Desired_keys) -> + maps:with(Desired_keys, Dict). + +-file("src/gleam/dict.gleam", 361). +?DOC( + " Creates a new dict from a given dict with all the same entries except for the\n" + " one with a given key, if it exists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"a\")\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"c\")\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec delete(dict(OZ, PA), OZ) -> dict(OZ, PA). +delete(Dict, Key) -> + _pipe = gleam_stdlib:identity(Dict), + _pipe@1 = maps:remove(Key, _pipe), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/dict.gleam", 398). +-spec drop_loop(transient_dict(PZ, QA), list(PZ)) -> dict(PZ, QA). +drop_loop(Transient, Disallowed_keys) -> + case Disallowed_keys of + [] -> + gleam_stdlib:identity(Transient); + + [Key | Rest] -> + drop_loop(maps:remove(Key, Transient), Rest) + end. + +-file("src/gleam/dict.gleam", 389). +?DOC( + " Creates a new dict from a given dict with all the same entries except any with\n" + " keys found in a given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\"])\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"c\"])\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\", \"b\", \"c\"])\n" + " == from_list([])\n" + " ```\n" +). +-spec drop(dict(PL, PM), list(PL)) -> dict(PL, PM). +drop(Dict, Disallowed_keys) -> + maps:without(Disallowed_keys, Dict). + +-file("src/gleam/dict.gleam", 431). +?DOC( + " Creates a new dict with one entry inserted or updated using a given function.\n" + "\n" + " If there was not an entry in the dict for the given key then the function\n" + " gets `None` as its argument, otherwise it gets `Some(value)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let dict = from_list([#(\"a\", 0)])\n" + " let increment = fn(x) {\n" + " case x {\n" + " Some(i) -> i + 1\n" + " None -> 0\n" + " }\n" + " }\n" + "\n" + " assert upsert(dict, \"a\", increment) == from_list([#(\"a\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert upsert(dict, \"b\", increment) == from_list([#(\"a\", 0), #(\"b\", 0)])\n" + " ```\n" +). +-spec upsert(dict(QG, QH), QG, fun((gleam@option:option(QH)) -> QH)) -> dict(QG, QH). +upsert(Dict, Key, Fun) -> + case gleam_stdlib:map_get(Dict, Key) of + {ok, Value} -> + insert(Dict, Key, Fun({some, Value})); + + {error, _} -> + insert(Dict, Key, Fun(none)) + end. + +-file("src/gleam/dict.gleam", 469). +?DOC( + " Combines all entries into a single value by calling a given function on each\n" + " one.\n" + "\n" + " Dicts are not ordered so the values are not returned in any specific order. Do\n" + " not write code that relies on the order entries are used by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n" + " assert fold(dict, 0, fn(accumulator, key, value) { accumulator + value })\n" + " == 13\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/string\n" + "\n" + " let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n" + " assert\n" + " fold(dict, \"\", fn(accumulator, key, value) {\n" + " string.append(accumulator, key)\n" + " })\n" + " == \"abc\"\n" + " ```\n" +). +-spec fold(dict(QN, QO), QR, fun((QR, QN, QO) -> QR)) -> QR. +fold(Dict, Initial, Fun) -> + Fun@1 = fun(Key, Value, Acc) -> Fun(Acc, Key, Value) end, + maps:fold(Fun@1, Initial, Dict). + +-file("src/gleam/dict.gleam", 97). +?DOC( + " Converts the dict to a list of 2-element tuples `#(key, value)`, one for\n" + " each key-value pair in the dict.\n" + "\n" + " The tuples in the list have no specific order.\n" + "\n" + " ## Examples\n" + "\n" + " Calling `to_list` on an empty `dict` returns an empty list.\n" + "\n" + " ```gleam\n" + " assert new() |> to_list == []\n" + " ```\n" + "\n" + " The ordering of elements in the resulting list is an implementation detail\n" + " that should not be relied upon.\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(\"b\", 1)\n" + " |> insert(\"a\", 0)\n" + " |> insert(\"c\", 2)\n" + " |> to_list\n" + " == [#(\"a\", 0), #(\"b\", 1), #(\"c\", 2)]\n" + " ```\n" +). +-spec to_list(dict(KJ, KK)) -> list({KJ, KK}). +to_list(Dict) -> + maps:to_list(Dict). + +-file("src/gleam/dict.gleam", 230). +?DOC( + " Gets a list of all keys in a given dict.\n" + "\n" + " Dicts are not ordered so the keys are not returned in any specific order. Do\n" + " not write code that relies on the order keys are returned by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> keys == [\"a\", \"b\"]\n" + " ```\n" +). +-spec keys(dict(MY, any())) -> list(MY). +keys(Dict) -> + maps:keys(Dict). + +-file("src/gleam/dict.gleam", 247). +?DOC( + " Gets a list of all values in a given dict.\n" + "\n" + " Dicts are not ordered so the values are not returned in any specific order. Do\n" + " not write code that relies on the order values are returned by this function\n" + " as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)]) |> values == [0, 1]\n" + " ```\n" +). +-spec values(dict(any(), NE)) -> list(NE). +values(Dict) -> + maps:values(Dict). + +-file("src/gleam/dict.gleam", 268). +?DOC( + " Creates a new dict from a given dict, minus any entries that a given function\n" + " returns `False` for.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> filter(fn(key, value) { value != 0 })\n" + " == from_list([#(\"b\", 1)])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " |> filter(fn(key, value) { True })\n" + " == from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " ```\n" +). +-spec filter(dict(NI, NJ), fun((NI, NJ) -> boolean())) -> dict(NI, NJ). +filter(Dict, Predicate) -> + maps:filter(Predicate, Dict). + +-file("src/gleam/dict.gleam", 504). +?DOC( + " Calls a function for each key and value in a dict, discarding the return\n" + " value.\n" + "\n" + " Useful for producing a side effect for every item of a dict.\n" + "\n" + " ```gleam\n" + " import gleam/io\n" + "\n" + " let dict = from_list([#(\"a\", \"apple\"), #(\"b\", \"banana\"), #(\"c\", \"cherry\")])\n" + "\n" + " assert\n" + " each(dict, fn(k, v) {\n" + " io.println(k <> \" => \" <> v)\n" + " })\n" + " == Nil\n" + " // a => apple\n" + " // b => banana\n" + " // c => cherry\n" + " ```\n" + "\n" + " The order of elements in the iteration is an implementation detail that\n" + " should not be relied upon.\n" +). +-spec each(dict(QX, QY), fun((QX, QY) -> any())) -> nil. +each(Dict, Fun) -> + fold( + Dict, + nil, + fun(Nil, K, V) -> + Fun(K, V), + Nil + end + ). + +-file("src/gleam/dict.gleam", 525). +?DOC( + " Creates a new dict from a pair of given dicts by combining their entries.\n" + "\n" + " If there are entries with the same keys in both dicts the given function is\n" + " used to determine the new value to use in the resulting dict.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " let b = from_list([#(\"a\", 2), #(\"c\", 3)])\n" + " assert combine(a, b, fn(one, other) { one + other })\n" + " == from_list([#(\"a\", 2), #(\"b\", 1), #(\"c\", 3)])\n" + " ```\n" +). +-spec combine(dict(RC, RD), dict(RC, RD), fun((RD, RD) -> RD)) -> dict(RC, RD). +combine(Dict, Other, Fun) -> + maps:merge_with(fun(_, L, R) -> Fun(L, R) end, Dict, Other). + +-file("src/gleam/dict.gleam", 342). +?DOC( + " Creates a new dict from a pair of given dicts by combining their entries.\n" + "\n" + " If there are entries with the same keys in both dicts the entry from the\n" + " second dict takes precedence.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n" + " let b = from_list([#(\"b\", 2), #(\"c\", 3)])\n" + " assert merge(a, b) == from_list([#(\"a\", 0), #(\"b\", 2), #(\"c\", 3)])\n" + " ```\n" +). +-spec merge(dict(OR, OS), dict(OR, OS)) -> dict(OR, OS). +merge(Dict, New_entries) -> + maps:merge(Dict, New_entries). + +-file("src/gleam/dict.gleam", 566). +-spec group_loop(transient_dict(SE, list(SF)), fun((SF) -> SE), list(SF)) -> dict(SE, list(SF)). +group_loop(Transient, To_key, List) -> + case List of + [] -> + gleam_stdlib:identity(Transient); + + [Value | Rest] -> + Key = To_key(Value), + Update = fun(Existing) -> [Value | Existing] end, + _pipe = Transient, + _pipe@1 = maps:update_with(Key, Update, [Value], _pipe), + group_loop(_pipe@1, To_key, Rest) + end. + +-file("src/gleam/dict.gleam", 562). +?DOC(false). +-spec group(fun((RY) -> RZ), list(RY)) -> dict(RZ, list(RY)). +group(Key, List) -> + group_loop(gleam_stdlib:identity(maps:new()), Key, List). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic.erl new file mode 100644 index 0000000..5c260ef --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic.erl @@ -0,0 +1,105 @@ +-module(gleam@dynamic). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dynamic.gleam"). +-export([classify/1, bool/1, string/1, float/1, int/1, bit_array/1, list/1, array/1, properties/1, nil/0]). +-export_type([dynamic_/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type dynamic_() :: any(). + +-file("src/gleam/dynamic.gleam", 29). +?DOC( + " Return a string indicating the type of the dynamic value.\n" + "\n" + " This function may be useful for constructing error messages or logs. If you\n" + " want to turn dynamic data into well typed data then you want the\n" + " `gleam/dynamic/decode` module.\n" + "\n" + " ```gleam\n" + " assert classify(string(\"Hello\")) == \"String\"\n" + " ```\n" +). +-spec classify(dynamic_()) -> binary(). +classify(Data) -> + gleam_stdlib:classify_dynamic(Data). + +-file("src/gleam/dynamic.gleam", 35). +?DOC(" Create a dynamic value from a bool.\n"). +-spec bool(boolean()) -> dynamic_(). +bool(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 43). +?DOC( + " Create a dynamic value from a string.\n" + "\n" + " On Erlang this will be a binary string rather than a character list.\n" +). +-spec string(binary()) -> dynamic_(). +string(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 49). +?DOC(" Create a dynamic value from a float.\n"). +-spec float(float()) -> dynamic_(). +float(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 55). +?DOC(" Create a dynamic value from an int.\n"). +-spec int(integer()) -> dynamic_(). +int(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 61). +?DOC(" Create a dynamic value from a bit array.\n"). +-spec bit_array(bitstring()) -> dynamic_(). +bit_array(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 67). +?DOC(" Create a dynamic value from a list.\n"). +-spec list(list(dynamic_())) -> dynamic_(). +list(A) -> + gleam_stdlib:identity(A). + +-file("src/gleam/dynamic.gleam", 76). +?DOC( + " Create a dynamic value from a list, converting it to a sequential runtime\n" + " format rather than the regular list format.\n" + "\n" + " On Erlang this will be a tuple, on JavaScript this will be an array.\n" +). +-spec array(list(dynamic_())) -> dynamic_(). +array(A) -> + erlang:list_to_tuple(A). + +-file("src/gleam/dynamic.gleam", 84). +?DOC( + " Create a dynamic value made of an unordered series of keys and values, where\n" + " the keys are unique.\n" + "\n" + " On Erlang this will be a map, on JavaScript this will be a Gleam dict\n" + " object.\n" +). +-spec properties(list({dynamic_(), dynamic_()})) -> dynamic_(). +properties(Entries) -> + gleam_stdlib:identity(maps:from_list(Entries)). + +-file("src/gleam/dynamic.gleam", 93). +?DOC( + " A dynamic value representing nothing.\n" + "\n" + " On Erlang this will be the atom `nil`, on JavaScript this will be\n" + " `undefined`.\n" +). +-spec nil() -> dynamic_(). +nil() -> + gleam_stdlib:identity(nil). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl new file mode 100644 index 0000000..e881579 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl @@ -0,0 +1,1121 @@ +-module(gleam@dynamic@decode). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/dynamic/decode.gleam"). +-export([run/2, success/1, decode_dynamic/1, map/2, map_errors/2, then/2, one_of/2, recursive/1, optional/1, decode_error/2, decode_bool/1, decode_int/1, decode_float/1, decode_bit_array/1, collapse_errors/2, failure/2, new_primitive_decoder/2, decode_string/1, dict/2, list/1, subfield/3, at/2, field/3, optional_field/4, optionally_at/3]). +-export_type([decode_error/0, decoder/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " The `Dynamic` type is used to represent dynamically typed data. That is, data\n" + " that we don't know the precise type of yet, so we need to introspect the data to\n" + " see if it is of the desired type before we can use it. Typically data like this\n" + " would come from user input or from untyped languages such as Erlang or JavaScript.\n" + "\n" + " This module provides the `Decoder` type and associated functions, which provides\n" + " a type-safe and composable way to convert dynamic data into some desired type,\n" + " or into errors if the data doesn't have the desired structure.\n" + "\n" + " The `Decoder` type is generic and has 1 type parameter, which is the type that\n" + " it attempts to decode. A `Decoder(String)` can be used to decode strings, and a\n" + " `Decoder(Option(Int))` can be used to decode `Option(Int)`s\n" + "\n" + " Decoders work using _runtime reflection_ and the data structures of the target\n" + " platform. Differences between Erlang and JavaScript data structures may impact\n" + " your decoders, so it is important to test your decoders on all supported\n" + " platforms.\n" + "\n" + " The decoding technique used by this module was inspired by Juraj Petráš'\n" + " [Toy](https://github.com/Hackder/toy), Go's `encoding/json`, and Elm's\n" + " `Json.Decode`. Thank you to them!\n" + "\n" + " # Generating decoders\n" + "\n" + " The language server has the \"generate dynamic decoder\" code action, which\n" + " will generate a decoder function when run on a custom type definition.\n" + " This generated decoder function can be a convenient shortcut when creating\n" + " your own decoders, and you can edit the generated function to suit your needs.\n" + "\n" + " # Examples\n" + "\n" + " Dynamic data may come from various sources and so many different syntaxes could\n" + " be used to describe or construct them. In these examples a pseudocode\n" + " syntax is used to describe the data.\n" + "\n" + " ## Simple types\n" + "\n" + " This module defines decoders for simple data types such as [`string`](#string),\n" + " [`int`](#int), [`float`](#float), [`bit_array`](#bit_array), and [`bool`](#bool).\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // \"Hello, Joe!\"\n" + "\n" + " let result = decode.run(data, decode.string)\n" + " assert result == Ok(\"Hello, Joe!\")\n" + " ```\n" + "\n" + " ## Lists\n" + "\n" + " The [`list`](#list) decoder decodes `List`s. To use it you must construct it by\n" + " passing in another decoder into the `list` function, which is the decoder that\n" + " is to be used for the elements of the list, type checking both the list and its\n" + " elements.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // [1, 2, 3, 4]\n" + "\n" + " let result = decode.run(data, decode.list(decode.int))\n" + " assert result == Ok([1, 2, 3, 4])\n" + " ```\n" + "\n" + " On Erlang this decoder can decode from lists, and on JavaScript it can\n" + " decode from lists as well as JavaScript arrays.\n" + "\n" + " ## Options\n" + "\n" + " The [`optional`](#optional) decoder is used to decode values that may or may not\n" + " be present. In other environments these might be called \"nullable\" values.\n" + "\n" + " Like the `list` decoder, the `optional` decoder takes another decoder,\n" + " which is used to decode the value if it is present.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // 12.45\n" + "\n" + " let result = decode.run(data, decode.optional(decode.float))\n" + " assert result == Ok(option.Some(12.45))\n" + " ```\n" + " ```gleam\n" + " // Data:\n" + " // null\n" + "\n" + " let result = decode.run(data, decode.optional(decode.int))\n" + " assert result == Ok(option.None)\n" + " ```\n" + "\n" + " This decoder knows how to handle multiple different runtime representations of\n" + " absent values, including `Nil`, `None`, `null`, and `undefined`.\n" + "\n" + " ## Dicts\n" + "\n" + " The [`dict`](#dict) decoder decodes `Dicts` and contains two other decoders, one\n" + " for the keys, one for the values.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // { \"Lucy\" -> 10, \"Nubi\" -> 20 }\n" + "\n" + " let result = decode.run(data, decode.dict(decode.string, decode.int))\n" + " assert result == Ok(dict.from_list([\n" + " #(\"Lucy\", 10),\n" + " #(\"Nubi\", 20),\n" + " ]))\n" + " ```\n" + "\n" + " ## Indexing objects\n" + "\n" + " The [`at`](#at) decoder can be used to decode a value that is nested within\n" + " key-value containers such as Gleam dicts, Erlang maps, or JavaScript objects.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // { \"one\" -> { \"two\" -> 123 } }\n" + "\n" + " let result = decode.run(data, decode.at([\"one\", \"two\"], decode.int))\n" + " assert result == Ok(123)\n" + " ```\n" + "\n" + " ## Indexing arrays\n" + "\n" + " If you use ints as keys then the [`at`](#at) decoder can be used to index into\n" + " array-like containers such as Gleam or Erlang tuples, or JavaScript arrays.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // [\"one\", \"two\", \"three\"]\n" + "\n" + " let result = decode.run(data, decode.at([1], decode.string))\n" + " assert result == Ok(\"two\")\n" + " ```\n" + "\n" + " ## Records\n" + "\n" + " Decoding records from dynamic data is more complex and requires combining a\n" + " decoder for each field and a special constructor that builds your records with\n" + " the decoded field values.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // {\n" + " // \"score\" -> 180,\n" + " // \"name\" -> \"Mel Smith\",\n" + " // \"is-admin\" -> false,\n" + " // \"enrolled\" -> true,\n" + " // \"colour\" -> \"Red\",\n" + " // }\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use score <- decode.field(\"score\", decode.int)\n" + " use colour <- decode.field(\"colour\", decode.string)\n" + " use enrolled <- decode.field(\"enrolled\", decode.bool)\n" + " decode.success(Player(name:, score:, colour:, enrolled:))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(Player(\"Mel Smith\", 180, \"Red\", True))\n" + " ```\n" + "\n" + " ## Enum variants\n" + "\n" + " Imagine you have a custom type where all the variants do not contain any values.\n" + "\n" + " ```gleam\n" + " pub type PocketMonsterType {\n" + " Fire\n" + " Water\n" + " Grass\n" + " Electric\n" + " }\n" + " ```\n" + "\n" + " You might choose to encode these variants as strings, `\"fire\"` for `Fire`,\n" + " `\"water\"` for `Water`, and so on. To decode them you'll need to decode the dynamic\n" + " data as a string, but then you'll need to decode it further still as not all\n" + " strings are valid values for the enum. This can be done with the `then`\n" + " function, which enables running a second decoder after the first one\n" + " succeeds.\n" + "\n" + " ```gleam\n" + " let decoder = {\n" + " use decoded_string <- decode.then(decode.string)\n" + " case decoded_string {\n" + " // Return succeeding decoders for valid strings\n" + " \"fire\" -> decode.success(Fire)\n" + " \"water\" -> decode.success(Water)\n" + " \"grass\" -> decode.success(Grass)\n" + " \"electric\" -> decode.success(Electric)\n" + " // Return a failing decoder for any other strings\n" + " _ -> decode.failure(Fire, expected: \"PocketMonsterType\")\n" + " }\n" + " }\n" + "\n" + " let result = decode.run(dynamic.string(\"water\"), decoder)\n" + " assert result == Ok(Water)\n" + "\n" + " let result = decode.run(dynamic.string(\"wobble\"), decoder)\n" + " assert result == Error([DecodeError(\"PocketMonsterType\", \"String\", [])])\n" + " ```\n" + "\n" + " ## Record variants\n" + "\n" + " Decoding type variants that contain other values is done by combining the\n" + " techniques from the \"enum variants\" and \"records\" examples. Imagine you have\n" + " this custom type that you want to decode:\n" + "\n" + " ```gleam\n" + " pub type PocketMonsterPerson {\n" + " Trainer(name: String, badge_count: Int)\n" + " GymLeader(name: String, speciality: PocketMonsterType)\n" + " }\n" + " ```\n" + " And you would like to be able to decode these from dynamic data like this:\n" + " ```erlang\n" + " {\n" + " \"type\" -> \"trainer\",\n" + " \"name\" -> \"Ash\",\n" + " \"badge-count\" -> 1,\n" + " }\n" + " ```\n" + " ```erlang\n" + " {\n" + " \"type\" -> \"gym-leader\",\n" + " \"name\" -> \"Misty\",\n" + " \"speciality\" -> \"water\",\n" + " }\n" + " ```\n" + "\n" + " Notice how both documents have a `\"type\"` field, which is used to indicate which\n" + " variant the data is for.\n" + "\n" + " First, define decoders for each of the variants:\n" + "\n" + " ```gleam\n" + " let trainer_decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use badge_count <- decode.field(\"badge-count\", decode.int)\n" + " decode.success(Trainer(name, badge_count))\n" + " }\n" + "\n" + " let gym_leader_decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use speciality <- decode.field(\"speciality\", pocket_monster_type_decoder)\n" + " decode.success(GymLeader(name, speciality))\n" + " }\n" + " ```\n" + "\n" + " A third decoder can be used to extract and decode the `\"type\"` field, and the\n" + " expression can evaluate to whichever decoder is suitable for the document.\n" + "\n" + " ```gleam\n" + " // Data:\n" + " // {\n" + " // \"type\" -> \"gym-leader\",\n" + " // \"name\" -> \"Misty\",\n" + " // \"speciality\" -> \"water\",\n" + " // }\n" + "\n" + " let decoder = {\n" + " use tag <- decode.field(\"type\", decode.string)\n" + " case tag {\n" + " \"gym-leader\" -> gym_leader_decoder\n" + " _ -> trainer_decoder\n" + " }\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(GymLeader(\"Misty\", Water))\n" + " ```\n" +). + +-type decode_error() :: {decode_error, binary(), binary(), list(binary())}. + +-opaque decoder(BVN) :: {decoder, + fun((gleam@dynamic:dynamic_()) -> {BVN, list(decode_error())})}. + +-file("src/gleam/dynamic/decode.gleam", 364). +?DOC( + " Run a decoder on a `Dynamic` value, decoding the value if it is of the\n" + " desired type, or returning errors.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", decode.string)\n" + " use email <- decode.field(\"email\", decode.string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " decode.run(data, decoder)\n" + " ```\n" +). +-spec run(gleam@dynamic:dynamic_(), decoder(BVV)) -> {ok, BVV} | + {error, list(decode_error())}. +run(Data, Decoder) -> + {Maybe_invalid_data, Errors} = (erlang:element(2, Decoder))(Data), + case Errors of + [] -> + {ok, Maybe_invalid_data}; + + [_ | _] -> + {error, Errors} + end. + +-file("src/gleam/dynamic/decode.gleam", 489). +?DOC( + " Finalise a decoder having successfully extracted a value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.field(\"email\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" +). +-spec success(BWW) -> decoder(BWW). +success(Data) -> + {decoder, fun(_) -> {Data, []} end}. + +-file("src/gleam/dynamic/decode.gleam", 742). +-spec decode_dynamic(gleam@dynamic:dynamic_()) -> {gleam@dynamic:dynamic_(), + list(decode_error())}. +decode_dynamic(Data) -> + {Data, []}. + +-file("src/gleam/dynamic/decode.gleam", 902). +?DOC( + " Apply a transformation function to any value decoded by the decoder.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.int |> decode.map(int.to_string)\n" + " let result = decode.run(dynamic.int(1000), decoder)\n" + " assert result == Ok(\"1000\")\n" + " ```\n" +). +-spec map(decoder(BZT), fun((BZT) -> BZV)) -> decoder(BZV). +map(Decoder, Transformer) -> + {decoder, + fun(D) -> + {Data, Errors} = (erlang:element(2, Decoder))(D), + {Transformer(Data), Errors} + end}. + +-file("src/gleam/dynamic/decode.gleam", 911). +?DOC(" Apply a transformation function to any errors returned by the decoder.\n"). +-spec map_errors( + decoder(BZX), + fun((list(decode_error())) -> list(decode_error())) +) -> decoder(BZX). +map_errors(Decoder, Transformer) -> + {decoder, + fun(D) -> + {Data, Errors} = (erlang:element(2, Decoder))(D), + {Data, Transformer(Errors)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 949). +?DOC( + " Create a new decoder based upon the value of a previous decoder.\n" + "\n" + " This may be useful to run one previous decoder to use in further decoding.\n" +). +-spec then(decoder(CAF), fun((CAF) -> decoder(CAH))) -> decoder(CAH). +then(Decoder, Next) -> + {decoder, + fun(Dynamic_data) -> + {Data, Errors} = (erlang:element(2, Decoder))(Dynamic_data), + Decoder@1 = Next(Data), + {Data@1, _} = Layer = (erlang:element(2, Decoder@1))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + {Data@1, Errors} + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 991). +-spec run_decoders( + gleam@dynamic:dynamic_(), + {CAP, list(decode_error())}, + list(decoder(CAP)) +) -> {CAP, list(decode_error())}. +run_decoders(Data, Failure, Decoders) -> + case Decoders of + [] -> + Failure; + + [Decoder | Decoders@1] -> + {_, Errors} = Layer = (erlang:element(2, Decoder))(Data), + case Errors of + [] -> + Layer; + + [_ | _] -> + run_decoders(Data, Failure, Decoders@1) + end + end. + +-file("src/gleam/dynamic/decode.gleam", 978). +?DOC( + " Create a new decoder from several other decoders. Each of the inner\n" + " decoders is run in turn, and the value from the first to succeed is used.\n" + "\n" + " If no decoder succeeds then the errors from the first decoder are used.\n" + " If you wish for different errors then you may wish to use the\n" + " `collapse_errors` or `map_errors` functions.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.one_of(decode.string, or: [\n" + " decode.int |> decode.map(int.to_string),\n" + " decode.float |> decode.map(float.to_string),\n" + " ])\n" + " assert decode.run(dynamic.int(1000), decoder) == Ok(\"1000\")\n" + " ```\n" +). +-spec one_of(decoder(CAK), list(decoder(CAK))) -> decoder(CAK). +one_of(First, Alternatives) -> + {decoder, + fun(Dynamic_data) -> + {_, Errors} = Layer = (erlang:element(2, First))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + run_decoders(Dynamic_data, Layer, Alternatives) + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 1102). +?DOC( + " Create a decoder that can refer to itself, useful for decoding deeply\n" + " nested data.\n" + "\n" + " Attempting to create a recursive decoder without this function could result\n" + " in an infinite loop. If you are using `field` or other `use`able functions\n" + " then you may not need to use this function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " type Nested {\n" + " Nested(List(Nested))\n" + " Value(String)\n" + " }\n" + "\n" + " fn nested_decoder() -> decode.Decoder(Nested) {\n" + " use <- decode.recursive\n" + " decode.one_of(decode.string |> decode.map(Value), [\n" + " decode.list(nested_decoder()) |> decode.map(Nested),\n" + " ])\n" + " }\n" + " ```\n" +). +-spec recursive(fun(() -> decoder(CBA))) -> decoder(CBA). +recursive(Inner) -> + {decoder, + fun(Data) -> + Decoder = Inner(), + (erlang:element(2, Decoder))(Data) + end}. + +-file("src/gleam/dynamic/decode.gleam", 880). +?DOC( + " A decoder that decodes nullable values of a type decoded by with a given\n" + " decoder.\n" + "\n" + " This function can handle common representations of null on all runtimes, such as\n" + " `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on\n" + " JavaScript.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let result = decode.run(dynamic.int(100), decode.optional(decode.int))\n" + " assert result == Ok(option.Some(100))\n" + " ```\n" + "\n" + " ```gleam\n" + " let result = decode.run(dynamic.nil(), decode.optional(decode.int))\n" + " assert result == Ok(option.None)\n" + " ```\n" +). +-spec optional(decoder(BZP)) -> decoder(gleam@option:option(BZP)). +optional(Inner) -> + {decoder, fun(Data) -> case gleam_stdlib:is_null(Data) of + true -> + {none, []}; + + false -> + {Data@1, Errors} = (erlang:element(2, Inner))(Data), + {{some, Data@1}, Errors} + end end}. + +-file("src/gleam/dynamic/decode.gleam", 495). +?DOC(" Construct a decode error for some unexpected dynamic data.\n"). +-spec decode_error(binary(), gleam@dynamic:dynamic_()) -> list(decode_error()). +decode_error(Expected, Found) -> + [{decode_error, Expected, gleam_stdlib:classify_dynamic(Found), []}]. + +-file("src/gleam/dynamic/decode.gleam", 617). +-spec run_dynamic_function( + gleam@dynamic:dynamic_(), + binary(), + fun((gleam@dynamic:dynamic_()) -> {ok, BXQ} | {error, BXQ}) +) -> {BXQ, list(decode_error())}. +run_dynamic_function(Data, Name, F) -> + case F(Data) of + {ok, Data@1} -> + {Data@1, []}; + + {error, Placeholder} -> + {Placeholder, + [{decode_error, Name, gleam_stdlib:classify_dynamic(Data), []}]} + end. + +-file("src/gleam/dynamic/decode.gleam", 668). +-spec decode_bool(gleam@dynamic:dynamic_()) -> {boolean(), list(decode_error())}. +decode_bool(Data) -> + case gleam_stdlib:identity(true) =:= Data of + true -> + {true, []}; + + false -> + case gleam_stdlib:identity(false) =:= Data of + true -> + {false, []}; + + false -> + {false, decode_error(<<"Bool"/utf8>>, Data)} + end + end. + +-file("src/gleam/dynamic/decode.gleam", 697). +-spec decode_int(gleam@dynamic:dynamic_()) -> {integer(), list(decode_error())}. +decode_int(Data) -> + run_dynamic_function(Data, <<"Int"/utf8>>, fun gleam_stdlib:int/1). + +-file("src/gleam/dynamic/decode.gleam", 723). +-spec decode_float(gleam@dynamic:dynamic_()) -> {float(), list(decode_error())}. +decode_float(Data) -> + run_dynamic_function(Data, <<"Float"/utf8>>, fun gleam_stdlib:float/1). + +-file("src/gleam/dynamic/decode.gleam", 757). +-spec decode_bit_array(gleam@dynamic:dynamic_()) -> {bitstring(), + list(decode_error())}. +decode_bit_array(Data) -> + run_dynamic_function( + Data, + <<"BitArray"/utf8>>, + fun gleam_stdlib:bit_array/1 + ). + +-file("src/gleam/dynamic/decode.gleam", 935). +?DOC( + " Replace all errors produced by a decoder with one single error for a named\n" + " expected type.\n" + "\n" + " This function may be useful if you wish to simplify errors before\n" + " presenting them to a user, particularly when using the `one_of` function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.string |> decode.collapse_errors(\"MyThing\")\n" + " let result = decode.run(dynamic.int(1000), decoder)\n" + " assert result == Error([DecodeError(\"MyThing\", \"Int\", [])])\n" + " ```\n" +). +-spec collapse_errors(decoder(CAC), binary()) -> decoder(CAC). +collapse_errors(Decoder, Name) -> + {decoder, + fun(Dynamic_data) -> + {Data, Errors} = Layer = (erlang:element(2, Decoder))(Dynamic_data), + case Errors of + [] -> + Layer; + + [_ | _] -> + {Data, decode_error(Name, Dynamic_data)} + end + end}. + +-file("src/gleam/dynamic/decode.gleam", 1024). +?DOC( + " Define a decoder that always fails.\n" + "\n" + " The first parameter is a \"placeholder\" value, which is some default value that the\n" + " decoder uses internally in place of the value that would have been produced\n" + " if the decoder was successful. It doesn't matter what this value is, it is\n" + " never returned by the decoder or shown to the user, so pick some arbitrary\n" + " value. If it is an int you might pick `0`, if it is a list you might pick\n" + " `[]`.\n" + "\n" + " The second parameter is the name of the type that has failed to decode.\n" + "\n" + " ```gleam\n" + " decode.failure(User(name: \"\", score: 0, tags: []), expected: \"User\")\n" + " ```\n" +). +-spec failure(CAU, binary()) -> decoder(CAU). +failure(Placeholder, Name) -> + {decoder, fun(D) -> {Placeholder, decode_error(Name, D)} end}. + +-file("src/gleam/dynamic/decode.gleam", 1065). +?DOC( + " Create a decoder for a new data type from a decoding function.\n" + "\n" + " This function is used for new primitive types. For example, you might\n" + " define a decoder for Erlang's pid type.\n" + "\n" + " A default \"placeholder\" value is also required to make a decoder. When this\n" + " decoder is used as part of a larger decoder this placeholder value is used\n" + " so that the rest of the decoder can continue to run and\n" + " collect all decoding errors. It doesn't matter what this value is, it is\n" + " never returned by the decoder or shown to the user, so pick some arbitrary\n" + " value. If it is an int you might pick `0`, if it is a list you might pick\n" + " `[]`.\n" + "\n" + " If you were to make a decoder for the `Int` type (rather than using the\n" + " build-in `Int` decoder) you would define it like so:\n" + "\n" + " ```gleam\n" + " pub fn int_decoder() -> decode.Decoder(Int) {\n" + " let default = \"\"\n" + " decode.new_primitive_decoder(\"Int\", int_from_dynamic)\n" + " }\n" + "\n" + " @external(erlang, \"my_module\", \"int_from_dynamic\")\n" + " fn int_from_dynamic(data: Int) -> Result(Int, Int)\n" + " ```\n" + "\n" + " ```erlang\n" + " -module(my_module).\n" + " -export([int_from_dynamic/1]).\n" + "\n" + " int_from_dynamic(Data) ->\n" + " case is_integer(Data) of\n" + " true -> {ok, Data};\n" + " false -> {error, 0}\n" + " end.\n" + " ```\n" +). +-spec new_primitive_decoder( + binary(), + fun((gleam@dynamic:dynamic_()) -> {ok, CAW} | {error, CAW}) +) -> decoder(CAW). +new_primitive_decoder(Name, Decoding_function) -> + {decoder, fun(D) -> case Decoding_function(D) of + {ok, T} -> + {T, []}; + + {error, Placeholder} -> + {Placeholder, + [{decode_error, + Name, + gleam_stdlib:classify_dynamic(D), + []}]} + end end}. + +-file("src/gleam/dynamic/decode.gleam", 646). +-spec dynamic_string(gleam@dynamic:dynamic_()) -> {ok, binary()} | + {error, binary()}. +dynamic_string(Data) -> + case gleam_stdlib:bit_array(Data) of + {ok, Data@1} -> + case gleam@bit_array:to_string(Data@1) of + {ok, String} -> + {ok, String}; + + {error, _} -> + {error, <<""/utf8>>} + end; + + {error, _} -> + {error, <<""/utf8>>} + end. + +-file("src/gleam/dynamic/decode.gleam", 641). +-spec decode_string(gleam@dynamic:dynamic_()) -> {binary(), + list(decode_error())}. +decode_string(Data) -> + run_dynamic_function(Data, <<"String"/utf8>>, fun dynamic_string/1). + +-file("src/gleam/dynamic/decode.gleam", 457). +-spec path_segment_to_string(gleam@dynamic:dynamic_()) -> binary(). +path_segment_to_string(Key) -> + Decoder = one_of( + {decoder, fun decode_string/1}, + [begin + _pipe = {decoder, fun decode_int/1}, + map(_pipe, fun erlang:integer_to_binary/1) + end, + begin + _pipe@1 = {decoder, fun decode_float/1}, + map(_pipe@1, fun gleam_stdlib:float_to_string/1) + end] + ), + case run(Key, Decoder) of + {ok, Key@1} -> + Key@1; + + {error, _} -> + <<<<"<"/utf8, (gleam_stdlib:classify_dynamic(Key))/binary>>/binary, + ">"/utf8>> + end. + +-file("src/gleam/dynamic/decode.gleam", 831). +-spec fold_dict( + {gleam@dict:dict(BZB, BZC), list(decode_error())}, + gleam@dynamic:dynamic_(), + gleam@dynamic:dynamic_(), + fun((gleam@dynamic:dynamic_()) -> {BZB, list(decode_error())}), + fun((gleam@dynamic:dynamic_()) -> {BZC, list(decode_error())}) +) -> {gleam@dict:dict(BZB, BZC), list(decode_error())}. +fold_dict(Acc, Key, Value, Key_decoder, Value_decoder) -> + case Key_decoder(Key) of + {Key_decoded, []} -> + case Value_decoder(Value) of + {Value@1, []} -> + Dict = gleam@dict:insert( + erlang:element(1, Acc), + Key_decoded, + Value@1 + ), + {Dict, erlang:element(2, Acc)}; + + {_, Errors} -> + Key_identifier = path_segment_to_string(Key), + push_path({maps:new(), Errors}, [Key_identifier]) + end; + + {_, Errors@1} -> + push_path({maps:new(), Errors@1}, [<<"keys"/utf8>>]) + end. + +-file("src/gleam/dynamic/decode.gleam", 811). +?DOC( + " A decoder that decodes dicts where all keys and values are decoded with\n" + " given decoders.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let values = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.int(1)),\n" + " #(dynamic.string(\"two\"), dynamic.int(2)),\n" + " ])\n" + "\n" + " let result =\n" + " decode.run(values, decode.dict(decode.string, decode.int))\n" + " assert result == Ok(values)\n" + " ```\n" +). +-spec dict(decoder(BYU), decoder(BYW)) -> decoder(gleam@dict:dict(BYU, BYW)). +dict(Key, Value) -> + {decoder, fun(Data) -> case gleam_stdlib:dict(Data) of + {error, _} -> + {maps:new(), decode_error(<<"Dict"/utf8>>, Data)}; + + {ok, Dict} -> + gleam@dict:fold( + Dict, + {maps:new(), []}, + fun(A, K, V) -> case erlang:element(2, A) of + [] -> + fold_dict( + A, + K, + V, + erlang:element(2, Key), + erlang:element(2, Value) + ); + + [_ | _] -> + A + end end + ) + end end}. + +-file("src/gleam/dynamic/decode.gleam", 779). +?DOC( + " A decoder that decodes lists where all elements are decoded with a given\n" + " decoder.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let result =\n" + " [1, 2, 3]\n" + " |> list.map(dynamic.int)\n" + " |> dynamic.list\n" + " |> decode.run(decode.list(of: decode.int))\n" + " assert result == Ok([1, 2, 3])\n" + " ```\n" +). +-spec list(decoder(BYI)) -> decoder(list(BYI)). +list(Inner) -> + {decoder, + fun(Data) -> + gleam_stdlib:list( + Data, + erlang:element(2, Inner), + fun(P, K) -> push_path(P, [K]) end, + 0, + [] + ) + end}. + +-file("src/gleam/dynamic/decode.gleam", 445). +-spec push_path({BWR, list(decode_error())}, list(any())) -> {BWR, + list(decode_error())}. +push_path(Layer, Path) -> + Path@1 = gleam@list:map(Path, fun(Key) -> _pipe = Key, + _pipe@1 = gleam_stdlib:identity(_pipe), + path_segment_to_string(_pipe@1) end), + Errors = gleam@list:map( + erlang:element(2, Layer), + fun(Error) -> + {decode_error, + erlang:element(2, Error), + erlang:element(3, Error), + lists:append(Path@1, erlang:element(4, Error))} + end + ), + {erlang:element(1, Layer), Errors}. + +-file("src/gleam/dynamic/decode.gleam", 409). +-spec index( + list(BWF), + list(BWF), + fun((gleam@dynamic:dynamic_()) -> {BWI, list(decode_error())}), + gleam@dynamic:dynamic_(), + fun((gleam@dynamic:dynamic_(), list(BWF)) -> {BWI, list(decode_error())}) +) -> {BWI, list(decode_error())}. +index(Path, Position, Inner, Data, Handle_miss) -> + case Path of + [] -> + _pipe = Data, + _pipe@1 = Inner(_pipe), + push_path(_pipe@1, lists:reverse(Position)); + + [Key | Path@1] -> + case gleam_stdlib:index(Data, Key) of + {ok, {some, Data@1}} -> + index(Path@1, [Key | Position], Inner, Data@1, Handle_miss); + + {ok, none} -> + Handle_miss(Data, [Key | Position]); + + {error, Kind} -> + {Default, _} = Inner(Data), + _pipe@2 = {Default, + [{decode_error, + Kind, + gleam_stdlib:classify_dynamic(Data), + []}]}, + push_path(_pipe@2, lists:reverse(Position)) + end + end. + +-file("src/gleam/dynamic/decode.gleam", 332). +?DOC( + " The same as [`field`](#field), except taking a path to the value rather\n" + " than a field name.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"data\"), dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.subfield([\"data\", \"name\"], decode.string)\n" + " use email <- decode.subfield([\"data\", \"email\"], decode.string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" +). +-spec subfield(list(any()), decoder(BVQ), fun((BVQ) -> decoder(BVS))) -> decoder(BVS). +subfield(Field_path, Field_decoder, Next) -> + {decoder, + fun(Data) -> + {Out, Errors1} = index( + Field_path, + [], + erlang:element(2, Field_decoder), + Data, + fun(Data@1, Position) -> + {Default, _} = (erlang:element(2, Field_decoder))(Data@1), + _pipe = {Default, + [{decode_error, + <<"Field"/utf8>>, + <<"Nothing"/utf8>>, + []}]}, + push_path(_pipe, lists:reverse(Position)) + end + ), + {Out@1, Errors2} = (erlang:element(2, Next(Out)))(Data), + {Out@1, lists:append(Errors1, Errors2)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 399). +?DOC( + " A decoder that decodes a value that is nested within other values. For\n" + " example, decoding a value that is within some deeply nested JSON objects.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.at([\"one\", \"two\"], decode.int)\n" + "\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.properties([\n" + " #(dynamic.string(\"two\"), dynamic.int(1000)),\n" + " ]),\n" + " ])\n" + "\n" + " assert decode.run(data, decoder) == Ok(1000)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert dynamic.nil()\n" + " |> decode.run(decode.optional(decode.int))\n" + " == Ok(option.None)\n" + " ```\n" +). +-spec at(list(any()), decoder(BWC)) -> decoder(BWC). +at(Path, Inner) -> + {decoder, + fun(Data) -> + index( + Path, + [], + erlang:element(2, Inner), + Data, + fun(Data@1, Position) -> + {Default, _} = (erlang:element(2, Inner))(Data@1), + _pipe = {Default, + [{decode_error, + <<"Field"/utf8>>, + <<"Nothing"/utf8>>, + []}]}, + push_path(_pipe, lists:reverse(Position)) + end + ) + end}. + +-file("src/gleam/dynamic/decode.gleam", 534). +?DOC( + " Run a decoder on a field of a `Dynamic` value, decoding the value if it is\n" + " of the desired type, or returning errors. An error is returned if there is\n" + " no field for the specified key.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"email\"), dynamic.string(\"lucy@example.com\")),\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.field(\"email\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"lucy@example.com\"))\n" + " ```\n" + "\n" + " If you wish to decode a value that is more deeply nested within the dynamic\n" + " data, see [`subfield`](#subfield) and [`at`](#at).\n" + "\n" + " If you wish to return a default in the event that a field is not present,\n" + " see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at).\n" +). +-spec field(any(), decoder(BXA), fun((BXA) -> decoder(BXC))) -> decoder(BXC). +field(Field_name, Field_decoder, Next) -> + subfield([Field_name], Field_decoder, Next). + +-file("src/gleam/dynamic/decode.gleam", 567). +?DOC( + " Run a decoder on a field of a `Dynamic` value, decoding the value if it is\n" + " of the desired type, or returning errors. The given default value is\n" + " returned if there is no field for the specified key.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"name\"), dynamic.string(\"Lucy\")),\n" + " ])\n" + "\n" + " let decoder = {\n" + " use name <- decode.field(\"name\", string)\n" + " use email <- decode.optional_field(\"email\", \"n/a\", string)\n" + " decode.success(SignUp(name: name, email: email))\n" + " }\n" + "\n" + " let result = decode.run(data, decoder)\n" + " assert result == Ok(SignUp(name: \"Lucy\", email: \"n/a\"))\n" + " ```\n" +). +-spec optional_field(any(), BXG, decoder(BXG), fun((BXG) -> decoder(BXI))) -> decoder(BXI). +optional_field(Key, Default, Field_decoder, Next) -> + {decoder, + fun(Data) -> + {Out, Errors1} = begin + _pipe = case gleam_stdlib:index(Data, Key) of + {ok, {some, Data@1}} -> + (erlang:element(2, Field_decoder))(Data@1); + + {ok, none} -> + {Default, []}; + + {error, Kind} -> + {Default, + [{decode_error, + Kind, + gleam_stdlib:classify_dynamic(Data), + []}]} + end, + push_path(_pipe, [Key]) + end, + {Out@1, Errors2} = (erlang:element(2, Next(Out)))(Data), + {Out@1, lists:append(Errors1, Errors2)} + end}. + +-file("src/gleam/dynamic/decode.gleam", 607). +?DOC( + " A decoder that decodes a value that is nested within other values. For\n" + " example, decoding a value that is within some deeply nested JSON objects.\n" + "\n" + " This function will index into dictionaries with any key type, and if the key is\n" + " an int then it'll also index into Erlang tuples and JavaScript arrays, and\n" + " the first eight elements of Gleam lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let decoder = decode.optionally_at([\"one\", \"two\"], 100, decode.int)\n" + "\n" + " let data = dynamic.properties([\n" + " #(dynamic.string(\"one\"), dynamic.properties([])),\n" + " ])\n" + "\n" + " assert decode.run(data, decoder) == Ok(100)\n" + " ```\n" +). +-spec optionally_at(list(any()), BXN, decoder(BXN)) -> decoder(BXN). +optionally_at(Path, Default, Inner) -> + {decoder, + fun(Data) -> + index( + Path, + [], + erlang:element(2, Inner), + Data, + fun(_, _) -> {Default, []} end + ) + end}. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@float.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@float.erl new file mode 100644 index 0000000..33256a2 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@float.erl @@ -0,0 +1,711 @@ +-module(gleam@float). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/float.gleam"). +-export([parse/1, to_string/1, compare/2, min/2, max/2, clamp/3, ceiling/1, floor/1, truncate/1, absolute_value/1, loosely_compare/3, loosely_equals/3, power/2, square_root/1, negate/1, round/1, to_precision/2, sum/1, product/1, random/0, modulo/2, divide/2, add/2, multiply/2, subtract/2, logarithm/1, exponential/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Functions for working with floats.\n" + "\n" + " ## Float representation\n" + "\n" + " Floats are represented as 64 bit floating point numbers on both the Erlang\n" + " and JavaScript runtimes. The floating point behaviour is native to their\n" + " respective runtimes, so their exact behaviour will be slightly different on\n" + " the two runtimes.\n" + "\n" + " ### Infinity and NaN\n" + "\n" + " Under the JavaScript runtime, exceeding the maximum (or minimum)\n" + " representable value for a floating point value will result in Infinity (or\n" + " -Infinity). Should you try to divide two infinities you will get NaN as a\n" + " result.\n" + "\n" + " When running on BEAM, exceeding the maximum (or minimum) representable\n" + " value for a floating point value will raise an error.\n" + "\n" + " ## Division by zero\n" + "\n" + " Gleam runs on the Erlang virtual machine, which does not follow the IEEE\n" + " 754 standard for floating point arithmetic and does not have an `Infinity`\n" + " value. In Erlang division by zero results in a crash, however Gleam does\n" + " not have partial functions and operators in core so instead division by zero\n" + " returns zero, a behaviour taken from Pony, Coq, and Lean.\n" + "\n" + " This may seem unexpected at first, but it is no less mathematically valid\n" + " than crashing or returning a special value. Division by zero is undefined\n" + " in mathematics.\n" +). + +-file("src/gleam/float.gleam", 49). +?DOC( + " Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was\n" + " not possible.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"2.3\") == Ok(2.3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert parse(\"ABC\") == Error(Nil)\n" + " ```\n" +). +-spec parse(binary()) -> {ok, float()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_float(String). + +-file("src/gleam/float.gleam", 61). +?DOC( + " Returns the string representation of the provided `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(2.3) == \"2.3\"\n" + " ```\n" +). +-spec to_string(float()) -> binary(). +to_string(X) -> + gleam_stdlib:float_to_string(X). + +-file("src/gleam/float.gleam", 100). +?DOC( + " Compares two `Float`s, returning an `Order`:\n" + " `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(2.0, 2.3) == Lt\n" + " ```\n" + "\n" + " To handle\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems)\n" + " you may use [`loosely_compare`](#loosely_compare) instead.\n" +). +-spec compare(float(), float()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/float.gleam", 177). +?DOC( + " Compares two `Float`s, returning the smaller of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert min(2.0, 2.3) == 2.0\n" + " ```\n" +). +-spec min(float(), float()) -> float(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/float.gleam", 192). +?DOC( + " Compares two `Float`s, returning the larger of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert max(2.0, 2.3) == 2.3\n" + " ```\n" +). +-spec max(float(), float()) -> float(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/float.gleam", 80). +?DOC( + " Restricts a float between two bounds.\n" + "\n" + " Note: If the `min` argument is larger than the `max` argument then they\n" + " will be swapped, so the minimum bound is always lower than the maximum\n" + " bound.\n" + "\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert clamp(1.2, min: 1.4, max: 1.6) == 1.4\n" + " ```\n" + "\n" + " ```gleam\n" + " assert clamp(1.2, min: 1.4, max: 0.6) == 1.2\n" + " ```\n" +). +-spec clamp(float(), float(), float()) -> float(). +clamp(X, Min_bound, Max_bound) -> + case Min_bound >= Max_bound of + true -> + _pipe = X, + _pipe@1 = min(_pipe, Min_bound), + max(_pipe@1, Max_bound); + + false -> + _pipe@2 = X, + _pipe@3 = min(_pipe@2, Max_bound), + max(_pipe@3, Min_bound) + end. + +-file("src/gleam/float.gleam", 209). +?DOC( + " Rounds the value to the next highest whole number as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert ceiling(2.3) == 3.0\n" + " ```\n" +). +-spec ceiling(float()) -> float(). +ceiling(X) -> + math:ceil(X). + +-file("src/gleam/float.gleam", 221). +?DOC( + " Rounds the value to the next lowest whole number as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert floor(2.3) == 2.0\n" + " ```\n" +). +-spec floor(float()) -> float(). +floor(X) -> + math:floor(X). + +-file("src/gleam/float.gleam", 256). +?DOC( + " Returns the value as an `Int`, truncating all decimal digits.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert truncate(2.4343434847383438) == 2\n" + " ```\n" +). +-spec truncate(float()) -> integer(). +truncate(X) -> + erlang:trunc(X). + +-file("src/gleam/float.gleam", 302). +?DOC( + " Returns the absolute value of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert absolute_value(-12.5) == 12.5\n" + " ```\n" + "\n" + " ```gleam\n" + " assert absolute_value(10.2) == 10.2\n" + " ```\n" +). +-spec absolute_value(float()) -> float(). +absolute_value(X) -> + case X >= +0.0 of + true -> + X; + + false -> + +0.0 - X + end. + +-file("src/gleam/float.gleam", 129). +?DOC( + " Compares two `Float`s within a tolerance, returning an `Order`:\n" + " `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n" + "\n" + " This function allows Float comparison while handling\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n" + "\n" + " Notice: For `Float`s the tolerance won't be exact:\n" + " `5.3 - 5.0` is not exactly `0.3`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert loosely_compare(5.0, with: 5.3, tolerating: 0.5) == Eq\n" + " ```\n" + "\n" + " If you want to check only for equality you may use\n" + " [`loosely_equals`](#loosely_equals) instead.\n" +). +-spec loosely_compare(float(), float(), float()) -> gleam@order:order(). +loosely_compare(A, B, Tolerance) -> + Difference = absolute_value(A - B), + case Difference =< Tolerance of + true -> + eq; + + false -> + compare(A, B) + end. + +-file("src/gleam/float.gleam", 160). +?DOC( + " Checks for equality of two `Float`s within a tolerance,\n" + " returning a `Bool`.\n" + "\n" + " This function allows Float comparison while handling\n" + " [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n" + "\n" + " Notice: For `Float`s the tolerance won't be exact:\n" + " `5.3 - 5.0` is not exactly `0.3`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert loosely_equals(5.0, with: 5.3, tolerating: 0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !loosely_equals(5.0, with: 5.1, tolerating: 0.1)\n" + " ```\n" +). +-spec loosely_equals(float(), float(), float()) -> boolean(). +loosely_equals(A, B, Tolerance) -> + Difference = absolute_value(A - B), + Difference =< Tolerance. + +-file("src/gleam/float.gleam", 334). +?DOC( + " Returns the result of the base being raised to the power of the\n" + " exponent, as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert power(2.0, -1.0) == Ok(0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(2.0, 2.0) == Ok(4.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(8.0, 1.5) == Ok(22.627416997969522)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 4.0 |> power(of: 2.0) == Ok(16.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(-1.0, 0.5) == Error(Nil)\n" + " ```\n" +). +-spec power(float(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + Fractional = (math:ceil(Exponent) - Exponent) > +0.0, + case ((Base < +0.0) andalso Fractional) orelse ((Base =:= +0.0) andalso (Exponent + < +0.0)) of + true -> + {error, nil}; + + false -> + {ok, math:pow(Base, Exponent)} + end. + +-file("src/gleam/float.gleam", 364). +?DOC( + " Returns the square root of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert square_root(4.0) == Ok(2.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert square_root(-16.0) == Error(Nil)\n" + " ```\n" +). +-spec square_root(float()) -> {ok, float()} | {error, nil}. +square_root(X) -> + power(X, 0.5). + +-file("src/gleam/float.gleam", 376). +?DOC( + " Returns the negative of the value provided.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(1.0) == -1.0\n" + " ```\n" +). +-spec negate(float()) -> float(). +negate(X) -> + -1.0 * X. + +-file("src/gleam/float.gleam", 236). +?DOC( + " Rounds the value to the nearest whole number as an `Int`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert round(2.3) == 2\n" + " ```\n" + "\n" + " ```gleam\n" + " assert round(2.5) == 3\n" + " ```\n" +). +-spec round(float()) -> integer(). +round(X) -> + erlang:round(X). + +-file("src/gleam/float.gleam", 273). +?DOC( + " Converts the value to a given precision as a `Float`.\n" + " The precision is the number of allowed decimal places.\n" + " Negative precisions are allowed and force rounding\n" + " to the nearest tenth, hundredth, thousandth etc.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_precision(2.43434348473, 2) == 2.43\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_precision(547890.453444, -3) == 548000.0\n" + " ```\n" +). +-spec to_precision(float(), integer()) -> float(). +to_precision(X, Precision) -> + case Precision =< 0 of + true -> + Factor = math:pow(10.0, erlang:float(- Precision)), + erlang:float(erlang:round(case Factor of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> X / Gleam@denominator + end)) * Factor; + + false -> + Factor@1 = math:pow(10.0, erlang:float(Precision)), + case Factor@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator@1 -> erlang:float(erlang:round(X * Factor@1)) + / Gleam@denominator@1 + end + end. + +-file("src/gleam/float.gleam", 392). +-spec sum_loop(list(float()), float()) -> float(). +sum_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + sum_loop(Rest, First + Initial); + + [] -> + Initial + end. + +-file("src/gleam/float.gleam", 388). +?DOC( + " Sums a list of `Float`s.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert sum([1.0, 2.2, 3.3]) == 6.5\n" + " ```\n" +). +-spec sum(list(float())) -> float(). +sum(Numbers) -> + sum_loop(Numbers, +0.0). + +-file("src/gleam/float.gleam", 411). +-spec product_loop(list(float()), float()) -> float(). +product_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + product_loop(Rest, First * Initial); + + [] -> + Initial + end. + +-file("src/gleam/float.gleam", 407). +?DOC( + " Multiplies a list of `Float`s and returns the product.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert product([2.5, 3.2, 4.2]) == 33.6\n" + " ```\n" +). +-spec product(list(float())) -> float(). +product(Numbers) -> + product_loop(Numbers, 1.0). + +-file("src/gleam/float.gleam", 433). +?DOC( + " Generates a random float between the given zero (inclusive) and one\n" + " (exclusive).\n" + "\n" + " On Erlang this updates the random state in the process dictionary.\n" + " See: \n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " random()\n" + " // -> 0.646355926896028\n" + " ```\n" +). +-spec random() -> float(). +random() -> + rand:uniform(). + +-file("src/gleam/float.gleam", 460). +?DOC( + " Computes the modulo of a float division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " The computed value will always have the same sign as the `divisor`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert modulo(13.3, by: 3.3) == Ok(0.1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13.3, by: 3.3) == Ok(3.2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13.3, by: -3.3) == Ok(-3.2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13.3, by: -3.3) == Ok(-0.1)\n" + " ```\n" +). +-spec modulo(float(), float()) -> {ok, float()} | {error, nil}. +modulo(Dividend, Divisor) -> + case Divisor of + +0.0 -> + {error, nil}; + + _ -> + {ok, Dividend - (math:floor(case Divisor of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> Dividend / Gleam@denominator + end) * Divisor)} + end. + +-file("src/gleam/float.gleam", 479). +?DOC( + " Returns division of the inputs as a `Result`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert divide(0.0, 1.0) == Ok(0.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(1.0, 0.0) == Error(Nil)\n" + " ```\n" +). +-spec divide(float(), float()) -> {ok, float()} | {error, nil}. +divide(A, B) -> + case B of + +0.0 -> + {error, nil}; + + B@1 -> + {ok, case B@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> A / Gleam@denominator + end} + end. + +-file("src/gleam/float.gleam", 507). +?DOC( + " Adds two floats together.\n" + "\n" + " It's the function equivalent of the `+.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert add(1.0, 2.0) == 3.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1.0, 2.0, 3.0], 0.0, add) == 6.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> add(2.0) == 5.0\n" + " ```\n" +). +-spec add(float(), float()) -> float(). +add(A, B) -> + A + B. + +-file("src/gleam/float.gleam", 532). +?DOC( + " Multiplies two floats together.\n" + "\n" + " It's the function equivalent of the `*.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert multiply(2.0, 4.0) == 8.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([2.0, 3.0, 4.0], 1.0, multiply) == 24.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> multiply(2.0) == 6.0\n" + " ```\n" +). +-spec multiply(float(), float()) -> float(). +multiply(A, B) -> + A * B. + +-file("src/gleam/float.gleam", 561). +?DOC( + " Subtracts one float from another.\n" + "\n" + " It's the function equivalent of the `-.` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert subtract(3.0, 1.0) == 2.0\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1.0, 2.0, 3.0], 10.0, subtract) == 4.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> subtract(_, 2.0) == 1.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3.0 |> subtract(2.0, _) == -1.0\n" + " ```\n" +). +-spec subtract(float(), float()) -> float(). +subtract(A, B) -> + A - B. + +-file("src/gleam/float.gleam", 586). +?DOC( + " Returns the natural logarithm (base e) of the given `Float` as a `Result`. If the\n" + " input is less than or equal to 0, returns `Error(Nil)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert logarithm(1.0) == Ok(0.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(2.718281828459045) == Ok(1.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(0.0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert logarithm(-1.0) == Error(Nil)\n" + " ```\n" +). +-spec logarithm(float()) -> {ok, float()} | {error, nil}. +logarithm(X) -> + case X =< +0.0 of + true -> + {error, nil}; + + false -> + {ok, math:log(X)} + end. + +-file("src/gleam/float.gleam", 621). +?DOC( + " Returns e (Euler's number) raised to the power of the given exponent, as\n" + " a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert exponential(0.0) == Ok(1.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exponential(1.0) == Ok(2.718281828459045)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert exponential(-1.0) == Ok(0.36787944117144233)\n" + " ```\n" +). +-spec exponential(float()) -> float(). +exponential(X) -> + math:exp(X). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@function.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@function.erl new file mode 100644 index 0000000..63fff58 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@function.erl @@ -0,0 +1,18 @@ +-module(gleam@function). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/function.gleam"). +-export([identity/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/function.gleam", 3). +?DOC(" Takes a single argument and always returns its input value.\n"). +-spec identity(CLV) -> CLV. +identity(X) -> + X. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@int.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@int.erl new file mode 100644 index 0000000..1d5306a --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@int.erl @@ -0,0 +1,972 @@ +-module(gleam@int). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/int.gleam"). +-export([absolute_value/1, parse/1, base_parse/2, to_string/1, to_base_string/2, to_base2/1, to_base8/1, to_base16/1, to_base36/1, to_float/1, power/2, square_root/1, compare/2, min/2, max/2, clamp/3, is_even/1, is_odd/1, negate/1, sum/1, product/1, random/1, divide/2, remainder/2, modulo/2, floor_divide/2, add/2, multiply/2, subtract/2, bitwise_and/2, bitwise_not/1, bitwise_or/2, bitwise_exclusive_or/2, bitwise_shift_left/2, bitwise_shift_right/2, range/4]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Functions for working with integers.\n" + "\n" + " ## Division by zero\n" + "\n" + " In Erlang division by zero results in a crash, however Gleam does not have\n" + " partial functions and operators in core so instead division by zero returns\n" + " zero, a behaviour taken from Pony, Coq, and Lean.\n" + "\n" + " This may seem unexpected at first, but it is no less mathematically valid\n" + " than crashing or returning a special value. Division by zero is undefined\n" + " in mathematics.\n" +). + +-file("src/gleam/int.gleam", 28). +?DOC( + " Returns the absolute value of the input.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert absolute_value(-12) == 12\n" + " ```\n" + "\n" + " ```gleam\n" + " assert absolute_value(10) == 10\n" + " ```\n" +). +-spec absolute_value(integer()) -> integer(). +absolute_value(X) -> + case X >= 0 of + true -> + X; + + false -> + X * -1 + end. + +-file("src/gleam/int.gleam", 98). +?DOC( + " Parses a given string as an int if possible.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"2\") == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert parse(\"ABC\") == Error(Nil)\n" + " ```\n" +). +-spec parse(binary()) -> {ok, integer()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_int(String). + +-file("src/gleam/int.gleam", 125). +?DOC( + " Parses a given string as an int in a given base if possible.\n" + " Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert base_parse(\"10\", 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"30\", 16) == Ok(48)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"1C\", 36) == Ok(48)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"48\", 1) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert base_parse(\"48\", 37) == Error(Nil)\n" + " ```\n" +). +-spec base_parse(binary(), integer()) -> {ok, integer()} | {error, nil}. +base_parse(String, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + gleam_stdlib:int_from_base_string(String, Base); + + false -> + {error, nil} + end. + +-file("src/gleam/int.gleam", 146). +?DOC( + " Prints a given int to a string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_string(2) == \"2\"\n" + " ```\n" +). +-spec to_string(integer()) -> binary(). +to_string(X) -> + erlang:integer_to_binary(X). + +-file("src/gleam/int.gleam", 174). +?DOC( + " Prints a given int to a string using the base number provided.\n" + " Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n" + " For common bases (2, 8, 16, 36), use the `to_baseN` functions.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base_string(2, 2) == Ok(\"10\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 16) == Ok(\"30\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 36) == Ok(\"1C\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 1) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_base_string(48, 37) == Error(Nil)\n" + " ```\n" +). +-spec to_base_string(integer(), integer()) -> {ok, binary()} | {error, nil}. +to_base_string(X, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + {ok, erlang:integer_to_binary(X, Base)}; + + false -> + {error, nil} + end. + +-file("src/gleam/int.gleam", 193). +?DOC( + " Prints a given int to a string using base-2.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base2(2) == \"10\"\n" + " ```\n" +). +-spec to_base2(integer()) -> binary(). +to_base2(X) -> + erlang:integer_to_binary(X, 2). + +-file("src/gleam/int.gleam", 205). +?DOC( + " Prints a given int to a string using base-8.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base8(15) == \"17\"\n" + " ```\n" +). +-spec to_base8(integer()) -> binary(). +to_base8(X) -> + erlang:integer_to_binary(X, 8). + +-file("src/gleam/int.gleam", 217). +?DOC( + " Prints a given int to a string using base-16.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base16(48) == \"30\"\n" + " ```\n" +). +-spec to_base16(integer()) -> binary(). +to_base16(X) -> + erlang:integer_to_binary(X, 16). + +-file("src/gleam/int.gleam", 229). +?DOC( + " Prints a given int to a string using base-36.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_base36(48) == \"1C\"\n" + " ```\n" +). +-spec to_base36(integer()) -> binary(). +to_base36(X) -> + erlang:integer_to_binary(X, 36). + +-file("src/gleam/int.gleam", 251). +?DOC( + " Takes an int and returns its value as a float.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_float(5) == 5.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_float(0) == 0.0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_float(-3) == -3.0\n" + " ```\n" +). +-spec to_float(integer()) -> float(). +to_float(X) -> + erlang:float(X). + +-file("src/gleam/int.gleam", 60). +?DOC( + " Returns the result of the base being raised to the power of the\n" + " exponent, as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert power(2, -1.0) == Ok(0.5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(2, 2.0) == Ok(4.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(8, 1.5) == Ok(22.627416997969522)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 4 |> power(of: 2.0) == Ok(16.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert power(-1, 0.5) == Error(Nil)\n" + " ```\n" +). +-spec power(integer(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + _pipe = Base, + _pipe@1 = erlang:float(_pipe), + gleam@float:power(_pipe@1, Exponent). + +-file("src/gleam/int.gleam", 78). +?DOC( + " Returns the square root of the input as a `Float`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert square_root(4) == Ok(2.0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert square_root(-16) == Error(Nil)\n" + " ```\n" +). +-spec square_root(integer()) -> {ok, float()} | {error, nil}. +square_root(X) -> + _pipe = X, + _pipe@1 = erlang:float(_pipe), + gleam@float:square_root(_pipe@1). + +-file("src/gleam/int.gleam", 292). +?DOC( + " Compares two ints, returning an order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(2, 3) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(4, 3) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert compare(3, 3) == Eq\n" + " ```\n" +). +-spec compare(integer(), integer()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/int.gleam", 311). +?DOC( + " Compares two ints, returning the smaller of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert min(2, 3) == 2\n" + " ```\n" +). +-spec min(integer(), integer()) -> integer(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/int.gleam", 326). +?DOC( + " Compares two ints, returning the larger of the two.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert max(2, 3) == 3\n" + " ```\n" +). +-spec max(integer(), integer()) -> integer(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-file("src/gleam/int.gleam", 269). +?DOC( + " Restricts an int between two bounds.\n" + "\n" + " Note: If the `min` argument is larger than the `max` argument then they\n" + " will be swapped, so the minimum bound is always lower than the maximum\n" + " bound.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert clamp(40, min: 50, max: 60) == 50\n" + " ```\n" + "\n" + " ```gleam\n" + " assert clamp(40, min: 50, max: 30) == 40\n" + " ```\n" +). +-spec clamp(integer(), integer(), integer()) -> integer(). +clamp(X, Min_bound, Max_bound) -> + case Min_bound >= Max_bound of + true -> + _pipe = X, + _pipe@1 = min(_pipe, Min_bound), + max(_pipe@1, Max_bound); + + false -> + _pipe@2 = X, + _pipe@3 = min(_pipe@2, Max_bound), + max(_pipe@3, Min_bound) + end. + +-file("src/gleam/int.gleam", 345). +?DOC( + " Returns whether the value provided is even.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_even(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_even(3)\n" + " ```\n" +). +-spec is_even(integer()) -> boolean(). +is_even(X) -> + (X rem 2) =:= 0. + +-file("src/gleam/int.gleam", 361). +?DOC( + " Returns whether the value provided is odd.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_odd(3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_odd(2)\n" + " ```\n" +). +-spec is_odd(integer()) -> boolean(). +is_odd(X) -> + (X rem 2) /= 0. + +-file("src/gleam/int.gleam", 373). +?DOC( + " Returns the negative of the value provided.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(1) == -1\n" + " ```\n" +). +-spec negate(integer()) -> integer(). +negate(X) -> + -1 * X. + +-file("src/gleam/int.gleam", 389). +-spec sum_loop(list(integer()), integer()) -> integer(). +sum_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + sum_loop(Rest, First + Initial); + + [] -> + Initial + end. + +-file("src/gleam/int.gleam", 385). +?DOC( + " Sums a list of ints.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert sum([1, 2, 3]) == 6\n" + " ```\n" +). +-spec sum(list(integer())) -> integer(). +sum(Numbers) -> + sum_loop(Numbers, 0). + +-file("src/gleam/int.gleam", 408). +-spec product_loop(list(integer()), integer()) -> integer(). +product_loop(Numbers, Initial) -> + case Numbers of + [First | Rest] -> + product_loop(Rest, First * Initial); + + [] -> + Initial + end. + +-file("src/gleam/int.gleam", 404). +?DOC( + " Multiplies a list of ints and returns the product.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert product([2, 3, 4]) == 24\n" + " ```\n" +). +-spec product(list(integer())) -> integer(). +product(Numbers) -> + product_loop(Numbers, 1). + +-file("src/gleam/int.gleam", 436). +?DOC( + " Generates a random int between zero and the given maximum.\n" + "\n" + " The lower number is inclusive, the upper number is exclusive.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " random(10)\n" + " // -> 4\n" + " ```\n" + "\n" + " ```gleam\n" + " random(1)\n" + " // -> 0\n" + " ```\n" + "\n" + " ```gleam\n" + " random(-1)\n" + " // -> -1\n" + " ```\n" +). +-spec random(integer()) -> integer(). +random(Max) -> + _pipe = (rand:uniform() * erlang:float(Max)), + _pipe@1 = math:floor(_pipe), + erlang:round(_pipe@1). + +-file("src/gleam/int.gleam", 465). +?DOC( + " Performs a truncated integer division.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert divide(0, 1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(5, 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert divide(-99, 2) == Ok(-49)\n" + " ```\n" +). +-spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend div Gleam@denominator + end} + end. + +-file("src/gleam/int.gleam", 510). +?DOC( + " Computes the remainder of an integer division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " Most of the time you will want to use the `%` operator instead of this\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remainder(3, 2) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(10, -1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(13, by: 3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(-13, by: 3) == Ok(-1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(13, by: -3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remainder(-13, by: -3) == Ok(-1)\n" + " ```\n" +). +-spec remainder(integer(), integer()) -> {ok, integer()} | {error, nil}. +remainder(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end} + end. + +-file("src/gleam/int.gleam", 551). +?DOC( + " Computes the modulo of an integer division of inputs as a `Result`.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " Note that this is different from `int.remainder` and `%` in that the\n" + " computed value will always have the same sign as the `divisor`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert modulo(3, 2) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(10, -1) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13, by: 3) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(-13, by: 3) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert modulo(13, by: -3) == Ok(-2)\n" + " ```\n" +). +-spec modulo(integer(), integer()) -> {ok, integer()} | {error, nil}. +modulo(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + _ -> + Remainder = case Divisor of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end, + case (Remainder * Divisor) < 0 of + true -> + {ok, Remainder + Divisor}; + + false -> + {ok, Remainder} + end + end. + +-file("src/gleam/int.gleam", 591). +?DOC( + " Performs a *floored* integer division, which means that the result will\n" + " always be rounded towards negative infinity.\n" + "\n" + " If you want to perform truncated integer division (rounding towards zero),\n" + " use `int.divide()` or the `/` operator instead.\n" + "\n" + " Returns division of the inputs as a `Result`: If the given divisor equals\n" + " `0`, this function returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert floor_divide(1, 0) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(5, 2) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(6, -4) == Ok(-2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert floor_divide(-99, 2) == Ok(-50)\n" + " ```\n" +). +-spec floor_divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +floor_divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + case ((Dividend * Divisor@1) < 0) andalso ((case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end) /= 0) of + true -> + {ok, (case Divisor@1 of + 0 -> 0; + Gleam@denominator@1 -> Dividend div Gleam@denominator@1 + end) - 1}; + + false -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator@2 -> Dividend div Gleam@denominator@2 + end} + end + end. + +-file("src/gleam/int.gleam", 622). +?DOC( + " Adds two integers together.\n" + "\n" + " It's the function equivalent of the `+` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert add(1, 2) == 3\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + " assert list.fold([1, 2, 3], 0, add) == 6\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> add(2) == 5\n" + " ```\n" +). +-spec add(integer(), integer()) -> integer(). +add(A, B) -> + A + B. + +-file("src/gleam/int.gleam", 647). +?DOC( + " Multiplies two integers together.\n" + "\n" + " It's the function equivalent of the `*` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert multiply(2, 4) == 8\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([2, 3, 4], 1, multiply) == 24\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> multiply(2) == 6\n" + " ```\n" +). +-spec multiply(integer(), integer()) -> integer(). +multiply(A, B) -> + A * B. + +-file("src/gleam/int.gleam", 676). +?DOC( + " Subtracts one int from another.\n" + "\n" + " It's the function equivalent of the `-` operator.\n" + " This function is useful in higher order functions or pipes.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert subtract(3, 1) == 2\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/list\n" + "\n" + " assert list.fold([1, 2, 3], 10, subtract) == 4\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> subtract(2) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert 3 |> subtract(2, _) == -1\n" + " ```\n" +). +-spec subtract(integer(), integer()) -> integer(). +subtract(A, B) -> + A - B. + +-file("src/gleam/int.gleam", 695). +?DOC( + " Calculates the bitwise AND of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_and(integer(), integer()) -> integer(). +bitwise_and(X, Y) -> + erlang:'band'(X, Y). + +-file("src/gleam/int.gleam", 712). +?DOC( + " Calculates the bitwise NOT of its argument.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_not(integer()) -> integer(). +bitwise_not(X) -> + erlang:'bnot'(X). + +-file("src/gleam/int.gleam", 729). +?DOC( + " Calculates the bitwise OR of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_or(integer(), integer()) -> integer(). +bitwise_or(X, Y) -> + erlang:'bor'(X, Y). + +-file("src/gleam/int.gleam", 746). +?DOC( + " Calculates the bitwise XOR of its arguments.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_exclusive_or(integer(), integer()) -> integer(). +bitwise_exclusive_or(X, Y) -> + erlang:'bxor'(X, Y). + +-file("src/gleam/int.gleam", 763). +?DOC( + " Calculates the result of an arithmetic left bitshift.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_shift_left(integer(), integer()) -> integer(). +bitwise_shift_left(X, Y) -> + erlang:'bsl'(X, Y). + +-file("src/gleam/int.gleam", 780). +?DOC( + " Calculates the result of an arithmetic right bitshift.\n" + "\n" + " Most the time you should use the bit array syntaxes instead of manipulating\n" + " bits as ints with bitwise functions.\n" + "\n" + " ## Target specific behaviour\n" + "\n" + " The exact behaviour of this function depends on the target platform.\n" + " On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n" + " is equivalent to bitwise operations on big-ints. If you need to avoid the\n" + " overhead of big-ints on JavaScript use bit arrays or another package that\n" + " provides faster bitwise operations.\n" +). +-spec bitwise_shift_right(integer(), integer()) -> integer(). +bitwise_shift_right(X, Y) -> + erlang:'bsr'(X, Y). + +-file("src/gleam/int.gleam", 813). +-spec range_loop( + integer(), + integer(), + integer(), + CM, + fun((CM, integer()) -> CM) +) -> CM. +range_loop(Current, Stop, Increment, Acc, Reducer) -> + case Current =:= Stop of + true -> + Acc; + + false -> + Acc@1 = Reducer(Acc, Current), + Current@1 = Current + Increment, + range_loop(Current@1, Stop, Increment, Acc@1, Reducer) + end. + +-file("src/gleam/int.gleam", 800). +?DOC( + " Run a function for each int between ints `from` and `to`.\n" + "\n" + " `from` is inclusive, and `to` is exclusive.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " range(from: 0, to: 3, with: \"\", run: fn(acc, i) {\n" + " acc <> to_string(i)\n" + " })\n" + " == \"012\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert range(from: 1, to: -2, with: [], run: list.prepend) == [-1, 0, 1]\n" + " ```\n" +). +-spec range(integer(), integer(), CL, fun((CL, integer()) -> CL)) -> CL. +range(Start, Stop, Acc, Reducer) -> + Increment = case Start < Stop of + true -> + 1; + + false -> + -1 + end, + range_loop(Start, Stop, Increment, Acc, Reducer). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@io.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@io.erl new file mode 100644 index 0000000..4b68e26 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@io.erl @@ -0,0 +1,76 @@ +-module(gleam@io). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/io.gleam"). +-export([print/1, print_error/1, println/1, println_error/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/io.gleam", 14). +?DOC( + " Writes a string to standard output (stdout).\n" + "\n" + " If you want your output to be printed on its own line see `println`.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.print(\"Hi mum\") == Nil\n" + " // Hi mum\n" + " ```\n" +). +-spec print(binary()) -> nil. +print(String) -> + gleam_stdlib:print(String). + +-file("src/gleam/io.gleam", 29). +?DOC( + " Writes a string to standard error (stderr).\n" + "\n" + " If you want your output to be printed on its own line see `println_error`.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.print_error(\"Hi pop\") == Nil\n" + " // Hi pop\n" + " ```\n" +). +-spec print_error(binary()) -> nil. +print_error(String) -> + gleam_stdlib:print_error(String). + +-file("src/gleam/io.gleam", 42). +?DOC( + " Writes a string to standard output (stdout), appending a newline to the end.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.println(\"Hi mum\") == Nil\n" + " // Hi mum\n" + " ```\n" +). +-spec println(binary()) -> nil. +println(String) -> + gleam_stdlib:println(String). + +-file("src/gleam/io.gleam", 55). +?DOC( + " Writes a string to standard error (stderr), appending a newline to the end.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " assert io.println_error(\"Hi pop\") == Nil\n" + " // Hi pop\n" + " ```\n" +). +-spec println_error(binary()) -> nil. +println_error(String) -> + gleam_stdlib:println_error(String). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@list.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@list.erl new file mode 100644 index 0000000..de82548 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@list.erl @@ -0,0 +1,2716 @@ +-module(gleam@list). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/list.gleam"). +-export([length/1, count/2, reverse/1, is_empty/1, contains/2, first/1, rest/1, group/2, filter/2, filter_map/2, map/2, map2/3, map_fold/3, index_map/2, try_map/2, drop/2, take/2, new/0, wrap/1, append/2, prepend/2, flatten/1, flat_map/2, fold/3, fold_right/3, index_fold/3, try_fold/3, fold_until/3, find/2, find_map/2, all/2, any/2, zip/2, strict_zip/2, unzip/1, intersperse/2, unique/1, sort/2, repeat/2, split/2, split_while/2, key_find/2, key_filter/2, key_pop/2, key_set/3, each/2, try_each/2, partition/2, window/2, window_by_2/1, drop_while/2, take_while/2, chunk/2, sized_chunk/2, reduce/2, scan/3, last/1, combinations/2, combination_pairs/1, transpose/1, interleave/1, shuffle/1, max/2, sample/2, permutations/1]). +-export_type([continue_or_stop/1, sorting/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Lists are an ordered sequence of elements and are one of the most common\n" + " data types in Gleam.\n" + "\n" + " New elements can be added and removed from the front of a list in\n" + " constant time, while adding and removing from the end requires traversing\n" + " and copying the whole list, so keep this in mind when designing your\n" + " programs.\n" + "\n" + " There is a dedicated syntax for prefixing to a list:\n" + "\n" + " ```gleam\n" + " let new_list = [1, 2, ..existing_list]\n" + " ```\n" + "\n" + " And a matching syntax for getting the first elements of a list:\n" + "\n" + " ```gleam\n" + " case list {\n" + " [first_element, ..rest] -> first_element\n" + " _ -> \"this pattern matches when the list is empty\"\n" + " }\n" + " ```\n" + "\n" +). + +-type continue_or_stop(AAE) :: {continue, AAE} | {stop, AAE}. + +-type sorting() :: ascending | descending. + +-file("src/gleam/list.gleam", 57). +-spec length_loop(list(any()), integer()) -> integer(). +length_loop(List, Count) -> + case List of + [_ | List@1] -> + length_loop(List@1, Count + 1); + + [] -> + Count + end. + +-file("src/gleam/list.gleam", 53). +?DOC( + " Counts the number of elements in a given list.\n" + "\n" + " This function has to traverse the list to determine the number of elements,\n" + " so it runs in linear time.\n" + "\n" + " This function is natively implemented by the virtual machine and is highly\n" + " optimised.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert length([]) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length([1]) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length([1, 2]) == 2\n" + " ```\n" +). +-spec length(list(any())) -> integer(). +length(List) -> + erlang:length(List). + +-file("src/gleam/list.gleam", 87). +-spec count_loop(list(AAL), fun((AAL) -> boolean()), integer()) -> integer(). +count_loop(List, Predicate, Acc) -> + case List of + [] -> + Acc; + + [First | Rest] -> + case Predicate(First) of + true -> + count_loop(Rest, Predicate, Acc + 1); + + false -> + count_loop(Rest, Predicate, Acc) + end + end. + +-file("src/gleam/list.gleam", 83). +?DOC( + " Counts the number of elements in a given list satisfying a given predicate.\n" + "\n" + " This function has to traverse the list to determine the number of elements,\n" + " so it runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert count([], fn(a) { a > 0 }) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert count([1], fn(a) { a > 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert count([1, 2, 3], int.is_odd) == 2\n" + " ```\n" +). +-spec count(list(AAJ), fun((AAJ) -> boolean())) -> integer(). +count(List, Predicate) -> + count_loop(List, Predicate, 0). + +-file("src/gleam/list.gleam", 122). +?DOC( + " Creates a new list from a given list containing the same elements but in the\n" + " opposite order.\n" + "\n" + " This function has to traverse the list to create the new reversed list, so\n" + " it runs in linear time.\n" + "\n" + " This function is natively implemented by the virtual machine and is highly\n" + " optimised.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert reverse([]) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert reverse([1]) == [1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert reverse([1, 2]) == [2, 1]\n" + " ```\n" +). +-spec reverse(list(AAN)) -> list(AAN). +reverse(List) -> + lists:reverse(List). + +-file("src/gleam/list.gleam", 156). +?DOC( + " Determines whether or not the list is empty.\n" + "\n" + " This function runs in constant time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_empty([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty([1])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty([1, 1])\n" + " ```\n" +). +-spec is_empty(list(any())) -> boolean(). +is_empty(List) -> + List =:= []. + +-file("src/gleam/list.gleam", 187). +?DOC( + " Determines whether or not a given element exists within a given list.\n" + "\n" + " This function traverses the list to find the element, so it runs in linear\n" + " time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !contains([], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [0] |> contains(any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains([1], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains([1, 1], any: 0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 0] |> contains(any: 0)\n" + " ```\n" +). +-spec contains(list(AAW), AAW) -> boolean(). +contains(List, Elem) -> + case List of + [] -> + false; + + [First | _] when First =:= Elem -> + true; + + [_ | Rest] -> + contains(Rest, Elem) + end. + +-file("src/gleam/list.gleam", 211). +?DOC( + " Gets the first element from the start of the list, if there is one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first([0]) == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first([1, 2]) == Ok(1)\n" + " ```\n" +). +-spec first(list(AAY)) -> {ok, AAY} | {error, nil}. +first(List) -> + case List of + [] -> + {error, nil}; + + [First | _] -> + {ok, First} + end. + +-file("src/gleam/list.gleam", 237). +?DOC( + " Returns the list minus the first element. If the list is empty, `Error(Nil)` is\n" + " returned.\n" + "\n" + " This function runs in constant time and does not make a copy of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert rest([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert rest([0]) == Ok([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert rest([1, 2]) == Ok([2])\n" + " ```\n" +). +-spec rest(list(ABC)) -> {ok, list(ABC)} | {error, nil}. +rest(List) -> + case List of + [] -> + {error, nil}; + + [_ | Rest] -> + {ok, Rest} + end. + +-file("src/gleam/list.gleam", 276). +?DOC( + " Groups the elements from the given list by the given key function.\n" + "\n" + " Does not preserve the initial value order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/dict\n" + "\n" + " assert\n" + " [Ok(3), Error(\"Wrong\"), Ok(200), Ok(73)]\n" + " |> group(by: fn(i) {\n" + " case i {\n" + " Ok(_) -> \"Successful\"\n" + " Error(_) -> \"Failed\"\n" + " }\n" + " })\n" + " |> dict.to_list\n" + " == [\n" + " #(\"Failed\", [Error(\"Wrong\")]),\n" + " #(\"Successful\", [Ok(73), Ok(200), Ok(3)])\n" + " ]\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/dict\n" + "\n" + " assert group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 })\n" + " |> dict.to_list\n" + " == [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])]\n" + " ```\n" +). +-spec group(list(ABH), fun((ABH) -> ABJ)) -> gleam@dict:dict(ABJ, list(ABH)). +group(List, Key) -> + gleam@dict:group(Key, List). + +-file("src/gleam/list.gleam", 297). +-spec filter_loop(list(ABQ), fun((ABQ) -> boolean()), list(ABQ)) -> list(ABQ). +filter_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + New_acc = case Fun(First) of + true -> + [First | Acc]; + + false -> + Acc + end, + filter_loop(Rest, Fun, New_acc) + end. + +-file("src/gleam/list.gleam", 293). +?DOC( + " Returns a new list containing only the elements from the first list for\n" + " which the given functions returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert filter([2, 4, 6, 1], fn(x) { x > 2 }) == [4, 6]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert filter([2, 4, 6, 1], fn(x) { x > 6 }) == []\n" + " ```\n" +). +-spec filter(list(ABN), fun((ABN) -> boolean())) -> list(ABN). +filter(List, Predicate) -> + filter_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 327). +-spec filter_map_loop( + list(ACB), + fun((ACB) -> {ok, ACD} | {error, any()}), + list(ACD) +) -> list(ACD). +filter_map_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + New_acc = case Fun(First) of + {ok, First@1} -> + [First@1 | Acc]; + + {error, _} -> + Acc + end, + filter_map_loop(Rest, Fun, New_acc) + end. + +-file("src/gleam/list.gleam", 323). +?DOC( + " Returns a new list containing only the elements from the first list for\n" + " which the given functions returns `Ok(_)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert filter_map([2, 4, 6, 1], Error) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) == [3, 5, 7, 2]\n" + " ```\n" +). +-spec filter_map(list(ABU), fun((ABU) -> {ok, ABW} | {error, any()})) -> list(ABW). +filter_map(List, Fun) -> + filter_map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 356). +-spec map_loop(list(ACN), fun((ACN) -> ACP), list(ACP)) -> list(ACP). +map_loop(List, Fun, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + map_loop(Rest, Fun, [Fun(First) | Acc]) + end. + +-file("src/gleam/list.gleam", 352). +?DOC( + " Returns a new list containing the results of applying the supplied function to each element.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map([2, 4, 6], fn(x) { x * 2 }) == [4, 8, 12]\n" + " ```\n" +). +-spec map(list(ACJ), fun((ACJ) -> ACL)) -> list(ACL). +map(List, Fun) -> + map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 382). +-spec map2_loop(list(ACY), list(ADA), fun((ACY, ADA) -> ADC), list(ADC)) -> list(ADC). +map2_loop(List1, List2, Fun, Acc) -> + case {List1, List2} of + {[], _} -> + lists:reverse(Acc); + + {_, []} -> + lists:reverse(Acc); + + {[A | As_], [B | Bs]} -> + map2_loop(As_, Bs, Fun, [Fun(A, B) | Acc]) + end. + +-file("src/gleam/list.gleam", 378). +?DOC( + " Combines two lists into a single list using the given function.\n" + "\n" + " If a list is longer than the other, the extra elements are dropped.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map2([1, 2, 3], [4, 5, 6], fn(x, y) { x + y }) == [5, 7, 9]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map2([1, 2], [\"a\", \"b\", \"c\"], fn(i, x) { #(i, x) })\n" + " == [#(1, \"a\"), #(2, \"b\")]\n" + " ```\n" +). +-spec map2(list(ACS), list(ACU), fun((ACS, ACU) -> ACW)) -> list(ACW). +map2(List1, List2, Fun) -> + map2_loop(List1, List2, Fun, []). + +-file("src/gleam/list.gleam", 416). +-spec map_fold_loop(list(ADK), fun((ADM, ADK) -> {ADM, ADN}), ADM, list(ADN)) -> {ADM, + list(ADN)}. +map_fold_loop(List, Fun, Acc, List_acc) -> + case List of + [] -> + {Acc, lists:reverse(List_acc)}; + + [First | Rest] -> + {Acc@1, First@1} = Fun(Acc, First), + map_fold_loop(Rest, Fun, Acc@1, [First@1 | List_acc]) + end. + +-file("src/gleam/list.gleam", 408). +?DOC( + " Similar to `map` but also lets you pass around an accumulated value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " map_fold(\n" + " over: [1, 2, 3],\n" + " from: 100,\n" + " with: fn(memo, i) { #(memo + i, i * 2) }\n" + " )\n" + " == #(106, [2, 4, 6])\n" + " ```\n" +). +-spec map_fold(list(ADF), ADH, fun((ADH, ADF) -> {ADH, ADI})) -> {ADH, + list(ADI)}. +map_fold(List, Initial, Fun) -> + map_fold_loop(List, Fun, Initial, []). + +-file("src/gleam/list.gleam", 447). +-spec index_map_loop( + list(ADU), + fun((ADU, integer()) -> ADW), + integer(), + list(ADW) +) -> list(ADW). +index_map_loop(List, Fun, Index, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + Acc@1 = [Fun(First, Index) | Acc], + index_map_loop(Rest, Fun, Index + 1, Acc@1) + end. + +-file("src/gleam/list.gleam", 443). +?DOC( + " Similar to `map`, but the supplied function will also be passed the index\n" + " of the element being mapped as an additional argument.\n" + "\n" + " The index starts at 0, so the first element is 0, the second is 1, and so\n" + " on.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert index_map([\"a\", \"b\"], fn(x, i) { #(i, x) }) == [#(0, \"a\"), #(1, \"b\")]\n" + " ```\n" +). +-spec index_map(list(ADQ), fun((ADQ, integer()) -> ADS)) -> list(ADS). +index_map(List, Fun) -> + index_map_loop(List, Fun, 0, []). + +-file("src/gleam/list.gleam", 497). +-spec try_map_loop(list(AEI), fun((AEI) -> {ok, AEK} | {error, AEL}), list(AEK)) -> {ok, + list(AEK)} | + {error, AEL}. +try_map_loop(List, Fun, Acc) -> + case List of + [] -> + {ok, lists:reverse(Acc)}; + + [First | Rest] -> + case Fun(First) of + {ok, First@1} -> + try_map_loop(Rest, Fun, [First@1 | Acc]); + + {error, Error} -> + {error, Error} + end + end. + +-file("src/gleam/list.gleam", 490). +?DOC( + " Takes a function that returns a `Result` and applies it to each element in a\n" + " given list in turn.\n" + "\n" + " If the function returns `Ok(new_value)` for all elements in the list then a\n" + " list of the new values is returned.\n" + "\n" + " If the function returns `Error(reason)` for any of the elements then it is\n" + " returned immediately. None of the elements in the list are processed after\n" + " one returns an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert try_map([1, 2, 3], fn(x) { Ok(x + 2) }) == Ok([3, 4, 5])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([1, 2, 3], fn(_) { Error(0) }) == Error(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([[1], [2, 3]], first) == Ok([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try_map([[1], [], [2]], first) == Error(Nil)\n" + " ```\n" +). +-spec try_map(list(ADZ), fun((ADZ) -> {ok, AEB} | {error, AEC})) -> {ok, + list(AEB)} | + {error, AEC}. +try_map(List, Fun) -> + try_map_loop(List, Fun, []). + +-file("src/gleam/list.gleam", 530). +?DOC( + " Returns a list that is the given list with up to the given number of\n" + " elements removed from the front of the list.\n" + "\n" + " If the list has less than the number of elements an empty list is\n" + " returned.\n" + "\n" + " This function runs in linear time but does not copy the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop([1, 2, 3, 4], 2) == [3, 4]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert drop([1, 2, 3, 4], 9) == []\n" + " ```\n" +). +-spec drop(list(AES), integer()) -> list(AES). +drop(List, N) -> + case N =< 0 of + true -> + List; + + false -> + case List of + [] -> + []; + + [_ | Rest] -> + drop(Rest, N - 1) + end + end. + +-file("src/gleam/list.gleam", 563). +-spec take_loop(list(AEY), integer(), list(AEY)) -> list(AEY). +take_loop(List, N, Acc) -> + case N =< 0 of + true -> + lists:reverse(Acc); + + false -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + take_loop(Rest, N - 1, [First | Acc]) + end + end. + +-file("src/gleam/list.gleam", 559). +?DOC( + " Returns a list containing the first given number of elements from the given\n" + " list.\n" + "\n" + " If the list has less than the number of elements then the full list is\n" + " returned.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert take([1, 2, 3, 4], 2) == [1, 2]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert take([1, 2, 3, 4], 9) == [1, 2, 3, 4]\n" + " ```\n" +). +-spec take(list(AEV), integer()) -> list(AEV). +take(List, N) -> + take_loop(List, N, []). + +-file("src/gleam/list.gleam", 582). +?DOC( + " Returns a new empty list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() == []\n" + " ```\n" +). +-spec new() -> list(any()). +new() -> + []. + +-file("src/gleam/list.gleam", 603). +?DOC( + " Returns the given item wrapped in a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert wrap(1) == [1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert wrap([\"a\", \"b\", \"c\"]) == [[\"a\", \"b\", \"c\"]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert wrap([[]]) == [[[]]]\n" + " ```\n" +). +-spec wrap(AFE) -> list(AFE). +wrap(Item) -> + [Item]. + +-file("src/gleam/list.gleam", 623). +-spec append_loop(list(AFK), list(AFK)) -> list(AFK). +append_loop(First, Second) -> + case First of + [] -> + Second; + + [First@1 | Rest] -> + append_loop(Rest, [First@1 | Second]) + end. + +-file("src/gleam/list.gleam", 619). +?DOC( + " Joins one list onto the end of another.\n" + "\n" + " This function runs in linear time, and it traverses and copies the first\n" + " list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append([1, 2], [3]) == [1, 2, 3]\n" + " ```\n" +). +-spec append(list(AFG), list(AFG)) -> list(AFG). +append(First, Second) -> + lists:append(First, Second). + +-file("src/gleam/list.gleam", 643). +?DOC( + " Prefixes an item to a list. This can also be done using the dedicated\n" + " syntax instead.\n" + "\n" + " ```gleam\n" + " let existing_list = [2, 3, 4]\n" + " assert [1, ..existing_list] == [1, 2, 3, 4]\n" + " ```\n" + "\n" + " ```gleam\n" + " let existing_list = [2, 3, 4]\n" + " assert prepend(to: existing_list, this: 1) == [1, 2, 3, 4]\n" + " ```\n" +). +-spec prepend(list(AFO), AFO) -> list(AFO). +prepend(List, Item) -> + [Item | List]. + +-file("src/gleam/list.gleam", 663). +-spec flatten_loop(list(list(AFV)), list(AFV)) -> list(AFV). +flatten_loop(Lists, Acc) -> + case Lists of + [] -> + lists:reverse(Acc); + + [List | Further_lists] -> + flatten_loop(Further_lists, lists:reverse(List, Acc)) + end. + +-file("src/gleam/list.gleam", 659). +?DOC( + " Joins a list of lists into a single list.\n" + "\n" + " This function traverses all elements twice on the JavaScript target.\n" + " This function traverses all elements once on the Erlang target.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten([[1], [2, 3], []]) == [1, 2, 3]\n" + " ```\n" +). +-spec flatten(list(list(AFR))) -> list(AFR). +flatten(Lists) -> + lists:append(Lists). + +-file("src/gleam/list.gleam", 679). +?DOC( + " Maps the list with the given function into a list of lists, and then flattens it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flat_map([2, 4, 6], fn(x) { [x, x + 1] }) == [2, 3, 4, 5, 6, 7]\n" + " ```\n" +). +-spec flat_map(list(AGA), fun((AGA) -> list(AGC))) -> list(AGC). +flat_map(List, Fun) -> + lists:append(map(List, Fun)). + +-file("src/gleam/list.gleam", 691). +?DOC( + " Reduces a list of elements into a single value by calling a given function\n" + " on each element, going from left to right.\n" + "\n" + " `fold([1, 2, 3], 0, add)` is the equivalent of\n" + " `add(add(add(0, 1), 2), 3)`.\n" + "\n" + " This function runs in linear time.\n" +). +-spec fold(list(AGF), AGH, fun((AGH, AGF) -> AGH)) -> AGH. +fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + fold(Rest, Fun(Initial, First), Fun) + end. + +-file("src/gleam/list.gleam", 713). +?DOC( + " Reduces a list of elements into a single value by calling a given function\n" + " on each element, going from right to left.\n" + "\n" + " `fold_right([1, 2, 3], 0, add)` is the equivalent of\n" + " `add(add(add(0, 3), 2), 1)`.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " Unlike `fold` this function is not tail recursive. Where possible use\n" + " `fold` instead as it will use less memory.\n" +). +-spec fold_right(list(AGI), AGK, fun((AGK, AGI) -> AGK)) -> AGK. +fold_right(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + Fun(fold_right(Rest, Initial, Fun), First) + end. + +-file("src/gleam/list.gleam", 750). +-spec index_fold_loop( + list(AGO), + AGQ, + fun((AGQ, AGO, integer()) -> AGQ), + integer() +) -> AGQ. +index_fold_loop(Over, Acc, With, Index) -> + case Over of + [] -> + Acc; + + [First | Rest] -> + index_fold_loop(Rest, With(Acc, First, Index), With, Index + 1) + end. + +-file("src/gleam/list.gleam", 742). +?DOC( + " Like `fold` but the folding function also receives the index of the current element.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [\"a\", \"b\", \"c\"]\n" + " |> index_fold(\"\", fn(acc, item, index) {\n" + " acc <> int.to_string(index) <> \":\" <> item <> \" \"\n" + " })\n" + " == \"0:a 1:b 2:c\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [10, 20, 30]\n" + " |> index_fold(0, fn(acc, item, index) { acc + item * index })\n" + " == 80\n" + " ```\n" +). +-spec index_fold(list(AGL), AGN, fun((AGN, AGL, integer()) -> AGN)) -> AGN. +index_fold(List, Initial, Fun) -> + index_fold_loop(List, Initial, Fun, 0). + +-file("src/gleam/list.gleam", 782). +?DOC( + " A variant of fold that might fail.\n" + "\n" + " The folding function should return `Result(accumulator, error)`.\n" + " If the returned value is `Ok(accumulator)` try_fold will try the next value in the list.\n" + " If the returned value is `Error(error)` try_fold will stop and return that error.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4]\n" + " |> try_fold(0, fn(acc, i) {\n" + " case i < 3 {\n" + " True -> Ok(acc + i)\n" + " False -> Error(Nil)\n" + " }\n" + " })\n" + " == Error(Nil)\n" + " ```\n" +). +-spec try_fold(list(AGR), AGT, fun((AGT, AGR) -> {ok, AGT} | {error, AGU})) -> {ok, + AGT} | + {error, AGU}. +try_fold(List, Initial, Fun) -> + case List of + [] -> + {ok, Initial}; + + [First | Rest] -> + case Fun(Initial, First) of + {ok, Result} -> + try_fold(Rest, Result, Fun); + + {error, _} = Error -> + Error + end + end. + +-file("src/gleam/list.gleam", 821). +?DOC( + " A variant of fold that allows to stop folding earlier.\n" + "\n" + " The folding function should return `ContinueOrStop(accumulator)`.\n" + " If the returned value is `Continue(accumulator)` fold_until will try the next value in the list.\n" + " If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4]\n" + " |> fold_until(0, fn(acc, i) {\n" + " case i < 3 {\n" + " True -> Continue(acc + i)\n" + " False -> Stop(acc)\n" + " }\n" + " })\n" + " == 3\n" + " ```\n" +). +-spec fold_until(list(AGZ), AHB, fun((AHB, AGZ) -> continue_or_stop(AHB))) -> AHB. +fold_until(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [First | Rest] -> + case Fun(Initial, First) of + {continue, Next_accumulator} -> + fold_until(Rest, Next_accumulator, Fun); + + {stop, B} -> + B + end + end. + +-file("src/gleam/list.gleam", 855). +?DOC( + " Finds the first element in a given list for which the given function returns\n" + " `True`.\n" + "\n" + " Returns `Error(Nil)` if no such element is found.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert find([1, 2, 3], fn(x) { x > 2 }) == Ok(3)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find([1, 2, 3], fn(x) { x > 4 }) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find([], fn(_) { True }) == Error(Nil)\n" + " ```\n" +). +-spec find(list(AHD), fun((AHD) -> boolean())) -> {ok, AHD} | {error, nil}. +find(List, Is_desired) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + case Is_desired(First) of + true -> + {ok, First}; + + false -> + find(Rest, Is_desired) + end + end. + +-file("src/gleam/list.gleam", 888). +?DOC( + " Finds the first element in a given list for which the given function returns\n" + " `Ok(new_value)`, then returns the wrapped `new_value`.\n" + "\n" + " Returns `Error(Nil)` if no such element is found.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert find_map([[], [2], [3]], first) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find_map([[], []], first) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert find_map([], first) == Error(Nil)\n" + " ```\n" +). +-spec find_map(list(AHH), fun((AHH) -> {ok, AHJ} | {error, any()})) -> {ok, AHJ} | + {error, nil}. +find_map(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + case Fun(First) of + {ok, First@1} -> + {ok, First@1}; + + {error, _} -> + find_map(Rest, Fun) + end + end. + +-file("src/gleam/list.gleam", 920). +?DOC( + " Returns `True` if the given function returns `True` for all the elements in\n" + " the given list. If the function returns `False` for any of the elements it\n" + " immediately returns `False` without checking the rest of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([4, 5], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !all([4, 3], fn(x) { x > 3 })\n" + " ```\n" +). +-spec all(list(AHP), fun((AHP) -> boolean())) -> boolean(). +all(List, Predicate) -> + case List of + [] -> + true; + + [First | Rest] -> + case Predicate(First) of + true -> + all(Rest, Predicate); + + false -> + false + end + end. + +-file("src/gleam/list.gleam", 953). +?DOC( + " Returns `True` if the given function returns `True` for any the elements in\n" + " the given list. If the function returns `True` for any of the elements it\n" + " immediately returns `True` without checking the rest of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !any([], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([4, 5], fn(x) { x > 3 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([4, 3], fn(x) { x > 4 })\n" + " ```\n" + "\n" + " ```gleam\n" + " assert any([3, 4], fn(x) { x > 3 })\n" + " ```\n" +). +-spec any(list(AHR), fun((AHR) -> boolean())) -> boolean(). +any(List, Predicate) -> + case List of + [] -> + false; + + [First | Rest] -> + case Predicate(First) of + true -> + true; + + false -> + any(Rest, Predicate) + end + end. + +-file("src/gleam/list.gleam", 991). +-spec zip_loop(list(AHY), list(AIA), list({AHY, AIA})) -> list({AHY, AIA}). +zip_loop(One, Other, Acc) -> + case {One, Other} of + {[First_one | Rest_one], [First_other | Rest_other]} -> + zip_loop(Rest_one, Rest_other, [{First_one, First_other} | Acc]); + + {_, _} -> + lists:reverse(Acc) + end. + +-file("src/gleam/list.gleam", 987). +?DOC( + " Takes two lists and returns a single list of 2-element tuples.\n" + "\n" + " If one of the lists is longer than the other, the remaining elements from\n" + " the longer list are not used.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert zip([], []) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1, 2], [3]) == [#(1, 3)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1], [3, 4]) == [#(1, 3)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert zip([1, 2], [3, 4]) == [#(1, 3), #(2, 4)]\n" + " ```\n" +). +-spec zip(list(AHT), list(AHV)) -> list({AHT, AHV}). +zip(List, Other) -> + zip_loop(List, Other, []). + +-file("src/gleam/list.gleam", 1028). +-spec strict_zip_loop(list(AIL), list(AIN), list({AIL, AIN})) -> {ok, + list({AIL, AIN})} | + {error, nil}. +strict_zip_loop(One, Other, Acc) -> + case {One, Other} of + {[], []} -> + {ok, lists:reverse(Acc)}; + + {[], _} -> + {error, nil}; + + {_, []} -> + {error, nil}; + + {[First_one | Rest_one], [First_other | Rest_other]} -> + strict_zip_loop( + Rest_one, + Rest_other, + [{First_one, First_other} | Acc] + ) + end. + +-file("src/gleam/list.gleam", 1021). +?DOC( + " Takes two lists and returns a single list of 2-element tuples.\n" + "\n" + " If one of the lists is longer than the other, an `Error` is returned.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert strict_zip([], []) == Ok([])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1, 2], [3]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1], [3, 4]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert strict_zip([1, 2], [3, 4]) == Ok([#(1, 3), #(2, 4)])\n" + " ```\n" +). +-spec strict_zip(list(AIE), list(AIG)) -> {ok, list({AIE, AIG})} | {error, nil}. +strict_zip(List, Other) -> + strict_zip_loop(List, Other, []). + +-file("src/gleam/list.gleam", 1057). +-spec unzip_loop(list({AIY, AIZ}), list(AIY), list(AIZ)) -> {list(AIY), + list(AIZ)}. +unzip_loop(Input, One, Other) -> + case Input of + [] -> + {lists:reverse(One), lists:reverse(Other)}; + + [{First_one, First_other} | Rest] -> + unzip_loop(Rest, [First_one | One], [First_other | Other]) + end. + +-file("src/gleam/list.gleam", 1053). +?DOC( + " Takes a single list of 2-element tuples and returns two lists.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unzip([#(1, 2), #(3, 4)]) == #([1, 3], [2, 4])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unzip([]) == #([], [])\n" + " ```\n" +). +-spec unzip(list({AIT, AIU})) -> {list(AIT), list(AIU)}. +unzip(Input) -> + unzip_loop(Input, [], []). + +-file("src/gleam/list.gleam", 1090). +-spec intersperse_loop(list(AJI), AJI, list(AJI)) -> list(AJI). +intersperse_loop(List, Separator, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + intersperse_loop(Rest, Separator, [First, Separator | Acc]) + end. + +-file("src/gleam/list.gleam", 1083). +?DOC( + " Inserts a given value between each existing element in a given list.\n" + "\n" + " This function runs in linear time and copies the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert intersperse([1, 1, 1], 2) == [1, 2, 1, 2, 1]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert intersperse([], 2) == []\n" + " ```\n" +). +-spec intersperse(list(AJF), AJF) -> list(AJF). +intersperse(List, Elem) -> + case List of + [] -> + List; + + [_] -> + List; + + [First | Rest] -> + intersperse_loop(Rest, Elem, [First]) + end. + +-file("src/gleam/list.gleam", 1112). +-spec unique_loop(list(AJP), gleam@dict:dict(AJP, nil), list(AJP)) -> list(AJP). +unique_loop(List, Seen, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + case gleam@dict:has_key(Seen, First) of + true -> + unique_loop(Rest, Seen, Acc); + + false -> + unique_loop( + Rest, + gleam@dict:insert(Seen, First, nil), + [First | Acc] + ) + end + end. + +-file("src/gleam/list.gleam", 1108). +?DOC( + " Removes any duplicate elements from a given list.\n" + "\n" + " This function returns in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unique([1, 1, 1, 4, 7, 3, 3, 4]) == [1, 4, 7, 3]\n" + " ```\n" +). +-spec unique(list(AJM)) -> list(AJM). +unique(List) -> + unique_loop(List, maps:new(), []). + +-file("src/gleam/list.gleam", 1197). +?DOC( + " Given a list it returns slices of it that are locally sorted in ascending\n" + " order.\n" + "\n" + " Imagine you have this list:\n" + "\n" + " ```\n" + " [1, 2, 3, 2, 1, 0]\n" + " ^^^^^^^ ^^^^^^^ This is a slice in descending order\n" + " |\n" + " | This is a slice that is sorted in ascending order\n" + " ```\n" + "\n" + " So the produced result will contain these two slices, each one sorted in\n" + " ascending order: `[[1, 2, 3], [0, 1, 2]]`.\n" + "\n" + " - `growing` is an accumulator with the current slice being grown\n" + " - `direction` is the growing direction of the slice being grown, it could\n" + " either be ascending or strictly descending\n" + " - `prev` is the previous element that needs to be added to the growing slice\n" + " it is carried around to check whether we have to keep growing the current\n" + " slice or not\n" + " - `acc` is the accumulator containing the slices sorted in ascending order\n" +). +-spec sequences( + list(AJY), + fun((AJY, AJY) -> gleam@order:order()), + list(AJY), + sorting(), + AJY, + list(list(AJY)) +) -> list(list(AJY)). +sequences(List, Compare, Growing, Direction, Prev, Acc) -> + Growing@1 = [Prev | Growing], + case List of + [] -> + case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end; + + [New | Rest] -> + case {Compare(Prev, New), Direction} of + {gt, descending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {lt, ascending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {eq, ascending} -> + sequences(Rest, Compare, Growing@1, Direction, New, Acc); + + {gt, ascending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end; + + {lt, descending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end; + + {eq, descending} -> + Acc@1 = case Direction of + ascending -> + [lists:reverse(Growing@1) | Acc]; + + descending -> + [Growing@1 | Acc] + end, + case Rest of + [] -> + [[New] | Acc@1]; + + [Next | Rest@1] -> + Direction@1 = case Compare(New, Next) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + sequences( + Rest@1, + Compare, + [New], + Direction@1, + Next, + Acc@1 + ) + end + end + end. + +-file("src/gleam/list.gleam", 1345). +?DOC( + " Merges two lists sorted in ascending order into a single list sorted in\n" + " descending order according to the given comparator function.\n" + "\n" + " This reversing of the sort order is not avoidable if we want to implement\n" + " merge as a tail recursive function. We could reverse the accumulator before\n" + " returning it but that would end up being less efficient; so the merging\n" + " algorithm has to play around this.\n" +). +-spec merge_ascendings( + list(AKV), + list(AKV), + fun((AKV, AKV) -> gleam@order:order()), + list(AKV) +) -> list(AKV). +merge_ascendings(List1, List2, Compare, Acc) -> + case {List1, List2} of + {[], List} -> + lists:reverse(List, Acc); + + {List, []} -> + lists:reverse(List, Acc); + + {[First1 | Rest1], [First2 | Rest2]} -> + case Compare(First1, First2) of + lt -> + merge_ascendings(Rest1, List2, Compare, [First1 | Acc]); + + gt -> + merge_ascendings(List1, Rest2, Compare, [First2 | Acc]); + + eq -> + merge_ascendings(List1, Rest2, Compare, [First2 | Acc]) + end + end. + +-file("src/gleam/list.gleam", 1298). +?DOC( + " Given a list of ascending lists, it merges adjacent pairs into a single\n" + " descending list, halving their number.\n" + " It returns a list of the remaining descending lists.\n" +). +-spec merge_ascending_pairs( + list(list(AKJ)), + fun((AKJ, AKJ) -> gleam@order:order()), + list(list(AKJ)) +) -> list(list(AKJ)). +merge_ascending_pairs(Sequences, Compare, Acc) -> + case Sequences of + [] -> + lists:reverse(Acc); + + [Sequence] -> + lists:reverse([lists:reverse(Sequence) | Acc]); + + [Ascending1, Ascending2 | Rest] -> + Descending = merge_ascendings(Ascending1, Ascending2, Compare, []), + merge_ascending_pairs(Rest, Compare, [Descending | Acc]) + end. + +-file("src/gleam/list.gleam", 1372). +?DOC( + " This is exactly the same as merge_ascendings but mirrored: it merges two\n" + " lists sorted in descending order into a single list sorted in ascending\n" + " order according to the given comparator function.\n" + "\n" + " This reversing of the sort order is not avoidable if we want to implement\n" + " merge as a tail recursive function. We could reverse the accumulator before\n" + " returning it but that would end up being less efficient; so the merging\n" + " algorithm has to play around this.\n" +). +-spec merge_descendings( + list(ALA), + list(ALA), + fun((ALA, ALA) -> gleam@order:order()), + list(ALA) +) -> list(ALA). +merge_descendings(List1, List2, Compare, Acc) -> + case {List1, List2} of + {[], List} -> + lists:reverse(List, Acc); + + {List, []} -> + lists:reverse(List, Acc); + + {[First1 | Rest1], [First2 | Rest2]} -> + case Compare(First1, First2) of + lt -> + merge_descendings(List1, Rest2, Compare, [First2 | Acc]); + + gt -> + merge_descendings(Rest1, List2, Compare, [First1 | Acc]); + + eq -> + merge_descendings(Rest1, List2, Compare, [First1 | Acc]) + end + end. + +-file("src/gleam/list.gleam", 1320). +?DOC(" This is the same as merge_ascending_pairs but flipped for descending lists.\n"). +-spec merge_descending_pairs( + list(list(AKP)), + fun((AKP, AKP) -> gleam@order:order()), + list(list(AKP)) +) -> list(list(AKP)). +merge_descending_pairs(Sequences, Compare, Acc) -> + case Sequences of + [] -> + lists:reverse(Acc); + + [Sequence] -> + lists:reverse([lists:reverse(Sequence) | Acc]); + + [Descending1, Descending2 | Rest] -> + Ascending = merge_descendings(Descending1, Descending2, Compare, []), + merge_descending_pairs(Rest, Compare, [Ascending | Acc]) + end. + +-file("src/gleam/list.gleam", 1264). +?DOC( + " Given some some sorted sequences (assumed to be sorted in `direction`) it\n" + " merges them all together until we're left with just a list sorted in\n" + " ascending order.\n" +). +-spec merge_all( + list(list(AKF)), + sorting(), + fun((AKF, AKF) -> gleam@order:order()) +) -> list(AKF). +merge_all(Sequences, Direction, Compare) -> + case {Sequences, Direction} of + {[], _} -> + []; + + {[Sequence], ascending} -> + Sequence; + + {[Sequence@1], descending} -> + lists:reverse(Sequence@1); + + {_, ascending} -> + Sequences@1 = merge_ascending_pairs(Sequences, Compare, []), + merge_all(Sequences@1, descending, Compare); + + {_, descending} -> + Sequences@2 = merge_descending_pairs(Sequences, Compare, []), + merge_all(Sequences@2, ascending, Compare) + end. + +-file("src/gleam/list.gleam", 1135). +?DOC( + " Sorts from smallest to largest based upon the ordering specified by a given\n" + " function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) == [1, 2, 3, 4, 4, 5, 6]\n" + " ```\n" +). +-spec sort(list(AJV), fun((AJV, AJV) -> gleam@order:order())) -> list(AJV). +sort(List, Compare) -> + case List of + [] -> + []; + + [X] -> + [X]; + + [X@1, Y | Rest] -> + Direction = case Compare(X@1, Y) of + lt -> + ascending; + + eq -> + ascending; + + gt -> + descending + end, + Sequences = sequences(Rest, Compare, [X@1], Direction, Y, []), + merge_all(Sequences, ascending, Compare) + end. + +-file("src/gleam/list.gleam", 1405). +-spec repeat_loop(ALH, integer(), list(ALH)) -> list(ALH). +repeat_loop(Item, Times, Acc) -> + case Times =< 0 of + true -> + Acc; + + false -> + repeat_loop(Item, Times - 1, [Item | Acc]) + end. + +-file("src/gleam/list.gleam", 1401). +?DOC( + " Builds a list of a given value a given number of times.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert repeat(\"a\", times: 0) == []\n" + " ```\n" + "\n" + " ```gleam\n" + " assert repeat(\"a\", times: 5) == [\"a\", \"a\", \"a\", \"a\", \"a\"]\n" + " ```\n" +). +-spec repeat(ALF, integer()) -> list(ALF). +repeat(A, Times) -> + repeat_loop(A, Times, []). + +-file("src/gleam/list.gleam", 1435). +-spec split_loop(list(ALO), integer(), list(ALO)) -> {list(ALO), list(ALO)}. +split_loop(List, N, Taken) -> + case N =< 0 of + true -> + {lists:reverse(Taken), List}; + + false -> + case List of + [] -> + {lists:reverse(Taken), []}; + + [First | Rest] -> + split_loop(Rest, N - 1, [First | Taken]) + end + end. + +-file("src/gleam/list.gleam", 1431). +?DOC( + " Splits a list in two before the given index.\n" + "\n" + " If the list is not long enough to have the given index the before list will\n" + " be the input list, and the after list will be empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 0) == #([], [6, 7, 8, 9])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 2) == #([6, 7], [8, 9])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split([6, 7, 8, 9], 4) == #([6, 7, 8, 9], [])\n" + " ```\n" +). +-spec split(list(ALK), integer()) -> {list(ALK), list(ALK)}. +split(List, Index) -> + split_loop(List, Index, []). + +-file("src/gleam/list.gleam", 1471). +-spec split_while_loop(list(ALX), fun((ALX) -> boolean()), list(ALX)) -> {list(ALX), + list(ALX)}. +split_while_loop(List, F, Acc) -> + case List of + [] -> + {lists:reverse(Acc), []}; + + [First | Rest] -> + case F(First) of + true -> + split_while_loop(Rest, F, [First | Acc]); + + false -> + {lists:reverse(Acc), List} + end + end. + +-file("src/gleam/list.gleam", 1464). +?DOC( + " Splits a list in two before the first element that a given function returns\n" + " `False` for.\n" + "\n" + " If the function returns `True` for all elements the first list will be the\n" + " input list, and the second list will be empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 })\n" + " == #([1, 2, 3], [4, 5])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 })\n" + " == #([1, 2, 3, 4, 5], [])\n" + " ```\n" +). +-spec split_while(list(ALT), fun((ALT) -> boolean())) -> {list(ALT), list(ALT)}. +split_while(List, Predicate) -> + split_while_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 1508). +?DOC( + " Given a list of 2-element tuples, finds the first tuple that has a given\n" + " key as the first element and returns the second element.\n" + "\n" + " If no tuple is found with the given key then `Error(Nil)` is returned.\n" + "\n" + " This function may be useful for interacting with Erlang code where lists of\n" + " tuples are common.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"a\") == Ok(0)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"b\") == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_find([#(\"a\", 0), #(\"b\", 1)], \"c\") == Error(Nil)\n" + " ```\n" +). +-spec key_find(list({AMC, AMD}), AMC) -> {ok, AMD} | {error, nil}. +key_find(Keyword_list, Desired_key) -> + find_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/list.gleam", 1537). +?DOC( + " Given a list of 2-element tuples, finds all tuples that have a given\n" + " key as the first element and returns the second element.\n" + "\n" + " This function may be useful for interacting with Erlang code where lists of\n" + " tuples are common.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_filter([#(\"a\", 0), #(\"b\", 1), #(\"a\", 2)], \"a\") == [0, 2]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_filter([#(\"a\", 0), #(\"b\", 1)], \"c\") == []\n" + " ```\n" +). +-spec key_filter(list({AMH, AMI}), AMH) -> list(AMI). +key_filter(Keyword_list, Desired_key) -> + filter_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/list.gleam", 1574). +-spec key_pop_loop(list({AMR, AMS}), AMR, list({AMR, AMS})) -> {ok, + {AMS, list({AMR, AMS})}} | + {error, nil}. +key_pop_loop(List, Key, Checked) -> + case List of + [] -> + {error, nil}; + + [{K, V} | Rest] when K =:= Key -> + {ok, {V, lists:reverse(Checked, Rest)}}; + + [First | Rest@1] -> + key_pop_loop(Rest@1, Key, [First | Checked]) + end. + +-file("src/gleam/list.gleam", 1570). +?DOC( + " Given a list of 2-element tuples, finds the first tuple that has a given\n" + " key as the first element. This function will return the second element\n" + " of the found tuple and list with tuple removed.\n" + "\n" + " If no tuple is found with the given key then `Error(Nil)` is returned.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"a\") == Ok(#(0, [#(\"b\", 1)]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"b\") == Ok(#(1, [#(\"a\", 0)]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_pop([#(\"a\", 0), #(\"b\", 1)], \"c\") == Error(Nil)\n" + " ```\n" +). +-spec key_pop(list({AML, AMM}), AML) -> {ok, {AMM, list({AML, AMM})}} | + {error, nil}. +key_pop(List, Key) -> + key_pop_loop(List, Key, []). + +-file("src/gleam/list.gleam", 1606). +-spec key_set_loop(list({ANC, AND}), ANC, AND, list({ANC, AND})) -> list({ANC, + AND}). +key_set_loop(List, Key, Value, Inspected) -> + case List of + [{K, _} | Rest] when K =:= Key -> + lists:reverse(Inspected, [{K, Value} | Rest]); + + [First | Rest@1] -> + key_set_loop(Rest@1, Key, Value, [First | Inspected]); + + [] -> + lists:reverse([{Key, Value} | Inspected]) + end. + +-file("src/gleam/list.gleam", 1602). +?DOC( + " Given a list of 2-element tuples, inserts a key and value into the list.\n" + "\n" + " If there was already a tuple with the key then it is replaced, otherwise it\n" + " is added to the end of the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert key_set([#(5, 0), #(4, 1)], 4, 100) == [#(5, 0), #(4, 100)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert key_set([#(5, 0), #(4, 1)], 1, 100) == [#(5, 0), #(4, 1), #(1, 100)]\n" + " ```\n" +). +-spec key_set(list({AMY, AMZ}), AMY, AMZ) -> list({AMY, AMZ}). +key_set(List, Key, Value) -> + key_set_loop(List, Key, Value, []). + +-file("src/gleam/list.gleam", 1633). +?DOC( + " Calls a function for each element in a list, discarding the return value.\n" + "\n" + " Useful for calling a side effect for every item of a list.\n" + "\n" + " ```gleam\n" + " import gleam/io\n" + "\n" + " assert each([\"1\", \"2\", \"3\"], io.println) == Nil\n" + " // 1\n" + " // 2\n" + " // 3\n" + " ```\n" +). +-spec each(list(ANH), fun((ANH) -> any())) -> nil. +each(List, F) -> + case List of + [] -> + nil; + + [First | Rest] -> + F(First), + each(Rest, F) + end. + +-file("src/gleam/list.gleam", 1660). +?DOC( + " Calls a `Result` returning function for each element in a list, discarding\n" + " the return value. If the function returns `Error` then the iteration is\n" + " stopped and the error is returned.\n" + "\n" + " Useful for calling a side effect for every item of a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert\n" + " try_each(\n" + " over: [1, 2, 3],\n" + " with: function_that_might_fail,\n" + " )\n" + " == Ok(Nil)\n" + " ```\n" +). +-spec try_each(list(ANK), fun((ANK) -> {ok, any()} | {error, ANN})) -> {ok, nil} | + {error, ANN}. +try_each(List, Fun) -> + case List of + [] -> + {ok, nil}; + + [First | Rest] -> + case Fun(First) of + {ok, _} -> + try_each(Rest, Fun); + + {error, E} -> + {error, E} + end + end. + +-file("src/gleam/list.gleam", 1692). +-spec partition_loop(list(BGI), fun((BGI) -> boolean()), list(BGI), list(BGI)) -> {list(BGI), + list(BGI)}. +partition_loop(List, Categorise, Trues, Falses) -> + case List of + [] -> + {lists:reverse(Trues), lists:reverse(Falses)}; + + [First | Rest] -> + case Categorise(First) of + true -> + partition_loop(Rest, Categorise, [First | Trues], Falses); + + false -> + partition_loop(Rest, Categorise, Trues, [First | Falses]) + end + end. + +-file("src/gleam/list.gleam", 1685). +?DOC( + " Partitions a list into a tuple/pair of lists\n" + " by a given categorisation function.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert [1, 2, 3, 4, 5] |> partition(int.is_odd) == #([1, 3, 5], [2, 4])\n" + " ```\n" +). +-spec partition(list(ANS), fun((ANS) -> boolean())) -> {list(ANS), list(ANS)}. +partition(List, Categorise) -> + partition_loop(List, Categorise, [], []). + +-file("src/gleam/list.gleam", 1769). +-spec window_loop(list(list(AOZ)), list(AOZ), integer()) -> list(list(AOZ)). +window_loop(Acc, List, N) -> + Window = take(List, N), + case erlang:length(Window) =:= N of + true -> + window_loop([Window | Acc], drop(List, 1), N); + + false -> + lists:reverse(Acc) + end. + +-file("src/gleam/list.gleam", 1762). +?DOC( + " Returns a list of sliding windows.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert window([1,2,3,4,5], 3) == [[1, 2, 3], [2, 3, 4], [3, 4, 5]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert window([1, 2], 4) == []\n" + " ```\n" +). +-spec window(list(AOV), integer()) -> list(list(AOV)). +window(List, N) -> + case N =< 0 of + true -> + []; + + false -> + window_loop([], List, N) + end. + +-file("src/gleam/list.gleam", 1790). +?DOC( + " Returns a list of tuples containing two contiguous elements.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert window_by_2([1,2,3,4]) == [#(1, 2), #(2, 3), #(3, 4)]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert window_by_2([1]) == []\n" + " ```\n" +). +-spec window_by_2(list(APF)) -> list({APF, APF}). +window_by_2(List) -> + zip(List, drop(List, 1)). + +-file("src/gleam/list.gleam", 1802). +?DOC( + " Drops the first elements in a given list for which the predicate function returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_while([1, 2, 3, 4], fn (x) { x < 3 }) == [3, 4]\n" + " ```\n" +). +-spec drop_while(list(API), fun((API) -> boolean())) -> list(API). +drop_while(List, Predicate) -> + case List of + [] -> + []; + + [First | Rest] -> + case Predicate(First) of + true -> + drop_while(Rest, Predicate); + + false -> + [First | Rest] + end + end. + +-file("src/gleam/list.gleam", 1831). +-spec take_while_loop(list(APO), fun((APO) -> boolean()), list(APO)) -> list(APO). +take_while_loop(List, Predicate, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + case Predicate(First) of + true -> + take_while_loop(Rest, Predicate, [First | Acc]); + + false -> + lists:reverse(Acc) + end + end. + +-file("src/gleam/list.gleam", 1824). +?DOC( + " Takes the first elements in a given list for which the predicate function returns `True`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert take_while([1, 2, 3, 2, 4], fn (x) { x < 3 }) == [1, 2]\n" + " ```\n" +). +-spec take_while(list(APL), fun((APL) -> boolean())) -> list(APL). +take_while(List, Predicate) -> + take_while_loop(List, Predicate, []). + +-file("src/gleam/list.gleam", 1863). +-spec chunk_loop(list(APX), fun((APX) -> APZ), APZ, list(APX), list(list(APX))) -> list(list(APX)). +chunk_loop(List, F, Previous_key, Current_chunk, Acc) -> + case List of + [First | Rest] -> + Key = F(First), + case Key =:= Previous_key of + true -> + chunk_loop(Rest, F, Key, [First | Current_chunk], Acc); + + false -> + New_acc = [lists:reverse(Current_chunk) | Acc], + chunk_loop(Rest, F, Key, [First], New_acc) + end; + + [] -> + lists:reverse([lists:reverse(Current_chunk) | Acc]) + end. + +-file("src/gleam/list.gleam", 1856). +?DOC( + " Returns a list of chunks in which\n" + " the return value of calling `f` on each element is the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 })\n" + " == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]\n" + " ```\n" +). +-spec chunk(list(APS), fun((APS) -> any())) -> list(list(APS)). +chunk(List, F) -> + case List of + [] -> + []; + + [First | Rest] -> + chunk_loop(Rest, F, F(First), [First], []) + end. + +-file("src/gleam/list.gleam", 1908). +-spec sized_chunk_loop( + list(AQJ), + integer(), + integer(), + list(AQJ), + list(list(AQJ)) +) -> list(list(AQJ)). +sized_chunk_loop(List, Count, Left, Current_chunk, Acc) -> + case List of + [] -> + case Current_chunk of + [] -> + lists:reverse(Acc); + + Remaining -> + lists:reverse([lists:reverse(Remaining) | Acc]) + end; + + [First | Rest] -> + Chunk = [First | Current_chunk], + case Left > 1 of + true -> + sized_chunk_loop(Rest, Count, Left - 1, Chunk, Acc); + + false -> + sized_chunk_loop( + Rest, + Count, + Count, + [], + [lists:reverse(Chunk) | Acc] + ) + end + end. + +-file("src/gleam/list.gleam", 1904). +?DOC( + " Returns a list of chunks containing `count` elements each.\n" + "\n" + " If the last chunk does not have `count` elements, it is instead\n" + " a partial chunk, with less than `count` elements.\n" + "\n" + " For any `count` less than 1 this function behaves as if it was set to 1.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5, 6] |> sized_chunk(into: 2)\n" + " == [[1, 2], [3, 4], [5, 6]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5, 6, 7, 8] |> sized_chunk(into: 3)\n" + " == [[1, 2, 3], [4, 5, 6], [7, 8]]\n" + " ```\n" +). +-spec sized_chunk(list(AQF), integer()) -> list(list(AQF)). +sized_chunk(List, Count) -> + sized_chunk_loop(List, Count, Count, [], []). + +-file("src/gleam/list.gleam", 1950). +?DOC( + " This function acts similar to fold, but does not take an initial state.\n" + " Instead, it starts from the first element in the list\n" + " and combines it with each subsequent element in turn using the given\n" + " function. The function is called as `fun(accumulator, current_element)`.\n" + "\n" + " Returns `Ok` to indicate a successful run, and `Error` if called on an\n" + " empty list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [] |> reduce(fn(acc, x) { acc + x }) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5] |> reduce(fn(acc, x) { acc + x }) == Ok(15)\n" + " ```\n" +). +-spec reduce(list(AQQ), fun((AQQ, AQQ) -> AQQ)) -> {ok, AQQ} | {error, nil}. +reduce(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, fold(Rest, First, Fun)} + end. + +-file("src/gleam/list.gleam", 1974). +-spec scan_loop(list(AQY), ARA, list(ARA), fun((ARA, AQY) -> ARA)) -> list(ARA). +scan_loop(List, Accumulator, Accumulated, Fun) -> + case List of + [] -> + lists:reverse(Accumulated); + + [First | Rest] -> + Next = Fun(Accumulator, First), + scan_loop(Rest, Next, [Next | Accumulated], Fun) + end. + +-file("src/gleam/list.gleam", 1966). +?DOC( + " Similar to `fold`, but yields the state of the accumulator at each stage.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert scan(over: [1, 2, 3], from: 100, with: fn(acc, i) { acc + i })\n" + " == [101, 103, 106]\n" + " ```\n" +). +-spec scan(list(AQU), AQW, fun((AQW, AQU) -> AQW)) -> list(AQW). +scan(List, Initial, Fun) -> + scan_loop(List, Initial, [], Fun). + +-file("src/gleam/list.gleam", 2005). +?DOC( + " Returns the last element in the given list.\n" + "\n" + " Returns `Error(Nil)` if the list is empty.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert last([]) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert last([1, 2, 3, 4, 5]) == Ok(5)\n" + " ```\n" +). +-spec last(list(ARD)) -> {ok, ARD} | {error, nil}. +last(List) -> + case List of + [] -> + {error, nil}; + + [Last] -> + {ok, Last}; + + [_ | Rest] -> + last(Rest) + end. + +-file("src/gleam/list.gleam", 2026). +?DOC( + " Return unique combinations of elements in the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert combinations([1, 2, 3], 2) == [[1, 2], [1, 3], [2, 3]]\n" + " ```\n" + "\n" + " ```gleam\n" + " assert combinations([1, 2, 3, 4], 3)\n" + " == [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]\n" + " ```\n" +). +-spec combinations(list(ARH), integer()) -> list(list(ARH)). +combinations(Items, N) -> + case {N, Items} of + {0, _} -> + [[]]; + + {_, []} -> + []; + + {_, [First | Rest]} -> + _pipe = Rest, + _pipe@1 = combinations(_pipe, N - 1), + _pipe@2 = map( + _pipe@1, + fun(Combination) -> [First | Combination] end + ), + _pipe@3 = lists:reverse(_pipe@2), + fold(_pipe@3, combinations(Rest, N), fun(Acc, C) -> [C | Acc] end) + end. + +-file("src/gleam/list.gleam", 2051). +-spec combination_pairs_loop(list(ARO), list({ARO, ARO})) -> list({ARO, ARO}). +combination_pairs_loop(Items, Acc) -> + case Items of + [] -> + lists:reverse(Acc); + + [First | Rest] -> + First_combinations = map(Rest, fun(Other) -> {First, Other} end), + Acc@1 = lists:reverse(First_combinations, Acc), + combination_pairs_loop(Rest, Acc@1) + end. + +-file("src/gleam/list.gleam", 2047). +?DOC( + " Return unique pair combinations of elements in the list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert combination_pairs([1, 2, 3]) == [#(1, 2), #(1, 3), #(2, 3)]\n" + " ```\n" +). +-spec combination_pairs(list(ARL)) -> list({ARL, ARL}). +combination_pairs(Items) -> + combination_pairs_loop(Items, []). + +-file("src/gleam/list.gleam", 2107). +-spec take_firsts(list(list(ASI)), list(ASI), list(list(ASI))) -> {list(ASI), + list(list(ASI))}. +take_firsts(Rows, Column, Remaining_rows) -> + case Rows of + [] -> + {lists:reverse(Column), lists:reverse(Remaining_rows)}; + + [[] | Rest] -> + take_firsts(Rest, Column, Remaining_rows); + + [[First | Remaining_row] | Rest_rows] -> + Remaining_rows@1 = [Remaining_row | Remaining_rows], + take_firsts(Rest_rows, [First | Column], Remaining_rows@1) + end. + +-file("src/gleam/list.gleam", 2094). +-spec transpose_loop(list(list(ASB)), list(list(ASB))) -> list(list(ASB)). +transpose_loop(Rows, Columns) -> + case Rows of + [] -> + lists:reverse(Columns); + + _ -> + {Column, Rest} = take_firsts(Rows, [], []), + case Column of + [_ | _] -> + transpose_loop(Rest, [Column | Columns]); + + [] -> + transpose_loop(Rest, Columns) + end + end. + +-file("src/gleam/list.gleam", 2090). +?DOC( + " Transpose rows and columns of the list of lists.\n" + "\n" + " Notice: This function is not tail recursive,\n" + " and thus may exceed stack size if called,\n" + " with large lists (on the JavaScript target).\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert transpose([[1, 2, 3], [101, 102, 103]])\n" + " == [[1, 101], [2, 102], [3, 103]]\n" + " ```\n" +). +-spec transpose(list(list(ARW))) -> list(list(ARW)). +transpose(List_of_lists) -> + transpose_loop(List_of_lists, []). + +-file("src/gleam/list.gleam", 2071). +?DOC( + " Make a list alternating the elements from the given lists\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert interleave([[1, 2], [101, 102], [201, 202]])\n" + " == [1, 101, 201, 2, 102, 202]\n" + " ```\n" +). +-spec interleave(list(list(ARS))) -> list(ARS). +interleave(List) -> + _pipe = List, + _pipe@1 = transpose(_pipe), + lists:append(_pipe@1). + +-file("src/gleam/list.gleam", 2140). +-spec shuffle_pair_unwrap_loop(list({float(), ASU}), list(ASU)) -> list(ASU). +shuffle_pair_unwrap_loop(List, Acc) -> + case List of + [] -> + Acc; + + [Elem_pair | Enumerable] -> + shuffle_pair_unwrap_loop( + Enumerable, + [erlang:element(2, Elem_pair) | Acc] + ) + end. + +-file("src/gleam/list.gleam", 2148). +-spec do_shuffle_by_pair_indexes(list({float(), ASY})) -> list({float(), ASY}). +do_shuffle_by_pair_indexes(List_of_pairs) -> + sort( + List_of_pairs, + fun(A_pair, B_pair) -> + gleam@float:compare( + erlang:element(1, A_pair), + erlang:element(1, B_pair) + ) + end + ). + +-file("src/gleam/list.gleam", 2133). +?DOC( + " Takes a list, randomly sorts all items and returns the shuffled list.\n" + "\n" + " This function uses `float.random` to decide the order of the elements.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> shuffle\n" + " // -> [1, 6, 9, 10, 3, 8, 4, 2, 7, 5]\n" + " ```\n" +). +-spec shuffle(list(ASR)) -> list(ASR). +shuffle(List) -> + _pipe = List, + _pipe@1 = fold(_pipe, [], fun(Acc, A) -> [{rand:uniform(), A} | Acc] end), + _pipe@2 = do_shuffle_by_pair_indexes(_pipe@1), + shuffle_pair_unwrap_loop(_pipe@2, []). + +-file("src/gleam/list.gleam", 2178). +-spec max_loop(list(ATI), fun((ATI, ATI) -> gleam@order:order()), ATI) -> ATI. +max_loop(List, Compare, Max) -> + case List of + [] -> + Max; + + [First | Rest] -> + case Compare(First, Max) of + gt -> + max_loop(Rest, Compare, First); + + lt -> + max_loop(Rest, Compare, Max); + + eq -> + max_loop(Rest, Compare, Max) + end + end. + +-file("src/gleam/list.gleam", 2168). +?DOC( + " Takes a list and a comparator, and returns the maximum element in the list\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert [1, 2, 3, 4, 5] |> list.max(int.compare) == Ok(5)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert [\"a\", \"c\", \"b\"] |> list.max(string.compare) == Ok(\"c\")\n" + " ```\n" +). +-spec max(list(ATB), fun((ATB, ATB) -> gleam@order:order())) -> {ok, ATB} | + {error, nil}. +max(List, Compare) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, max_loop(Rest, Compare, First)} + end. + +-file("src/gleam/list.gleam", 2259). +-spec build_reservoir_loop( + list(ATX), + integer(), + gleam@dict:dict(integer(), ATX) +) -> {gleam@dict:dict(integer(), ATX), list(ATX)}. +build_reservoir_loop(List, Size, Reservoir) -> + Reservoir_size = maps:size(Reservoir), + case Reservoir_size >= Size of + true -> + {Reservoir, List}; + + false -> + case List of + [] -> + {Reservoir, []}; + + [First | Rest] -> + Reservoir@1 = gleam@dict:insert( + Reservoir, + Reservoir_size, + First + ), + build_reservoir_loop(Rest, Size, Reservoir@1) + end + end. + +-file("src/gleam/list.gleam", 2255). +?DOC( + " Builds the initial reservoir used by Algorithm L.\n" + " This is a dictionary with keys ranging from `0` up to `n - 1` where each\n" + " value is the corresponding element at that position in `list`.\n" + "\n" + " This also returns the remaining elements of `list` that didn't end up in\n" + " the reservoir.\n" +). +-spec build_reservoir(list(ATS), integer()) -> {gleam@dict:dict(integer(), ATS), + list(ATS)}. +build_reservoir(List, N) -> + build_reservoir_loop(List, N, maps:new()). + +-file("src/gleam/list.gleam", 2243). +-spec log_random() -> float(). +log_random() -> + Random@1 = case gleam@float:logarithm( + rand:uniform() + 2.2250738585072014e-308 + ) of + {ok, Random} -> Random; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gleam/list"/utf8>>, + function => <<"log_random"/utf8>>, + line => 2244, + value => _assert_fail, + start => 55129, + 'end' => 55200, + pattern_start => 55140, + pattern_end => 55150}) + end, + Random@1. + +-file("src/gleam/list.gleam", 2220). +-spec sample_loop( + list(ATM), + gleam@dict:dict(integer(), ATM), + integer(), + float() +) -> gleam@dict:dict(integer(), ATM). +sample_loop(List, Reservoir, N, W) -> + Skip = begin + Log@1 = case gleam@float:logarithm(1.0 - W) of + {ok, Log} -> Log; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gleam/list"/utf8>>, + function => <<"sample_loop"/utf8>>, + line => 2227, + value => _assert_fail, + start => 54690, + 'end' => 54736, + pattern_start => 54701, + pattern_end => 54708}) + end, + erlang:round(math:floor(case Log@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> log_random() / Gleam@denominator + end)) + end, + case drop(List, Skip) of + [] -> + Reservoir; + + [First | Rest] -> + Reservoir@1 = gleam@dict:insert( + Reservoir, + gleam@int:random(N), + First + ), + W@1 = W * math:exp(case erlang:float(N) of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator@1 -> log_random() / Gleam@denominator@1 + end), + sample_loop(Rest, Reservoir@1, N, W@1) + end. + +-file("src/gleam/list.gleam", 2202). +?DOC( + " Returns a random sample of up to n elements from a list using reservoir\n" + " sampling via [Algorithm L](https://en.wikipedia.org/wiki/Reservoir_sampling#Optimal:_Algorithm_L).\n" + " Returns an empty list if the sample size is less than or equal to 0.\n" + "\n" + " Order is not random, only selection is.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " sample([1, 2, 3, 4, 5], 3)\n" + " // -> [2, 4, 5] // A random sample of 3 items\n" + " ```\n" +). +-spec sample(list(ATJ), integer()) -> list(ATJ). +sample(List, N) -> + {Reservoir, Rest} = build_reservoir(List, N), + case gleam@dict:is_empty(Reservoir) of + true -> + []; + + false -> + W = math:exp(case erlang:float(N) of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> log_random() / Gleam@denominator + end), + maps:values(sample_loop(Rest, Reservoir, N, W)) + end. + +-file("src/gleam/list.gleam", 1718). +-spec permutation_zip(list(AOF), list(AOF), list(list(AOF))) -> list(list(AOF)). +permutation_zip(List, Rest, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [Head | Tail] -> + permutation_prepend( + Head, + permutations(lists:reverse(Rest, Tail)), + Tail, + [Head | Rest], + Acc + ) + end. + +-file("src/gleam/list.gleam", 1711). +?DOC( + " Returns all the permutations of a list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert permutations([1, 2]) == [[1, 2], [2, 1]]\n" + " ```\n" +). +-spec permutations(list(AOB)) -> list(list(AOB)). +permutations(List) -> + case List of + [] -> + [[]]; + + L -> + permutation_zip(L, [], []) + end. + +-file("src/gleam/list.gleam", 1736). +-spec permutation_prepend( + AOM, + list(list(AOM)), + list(AOM), + list(AOM), + list(list(AOM)) +) -> list(list(AOM)). +permutation_prepend(El, Permutations, List_1, List_2, Acc) -> + case Permutations of + [] -> + permutation_zip(List_1, List_2, Acc); + + [Head | Tail] -> + permutation_prepend(El, Tail, List_1, List_2, [[El | Head] | Acc]) + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@option.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@option.erl new file mode 100644 index 0000000..1204ae6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@option.erl @@ -0,0 +1,381 @@ +-module(gleam@option). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/option.gleam"). +-export([all/1, is_some/1, is_none/1, to_result/2, from_result/1, unwrap/2, lazy_unwrap/2, map/2, flatten/1, then/2, 'or'/2, lazy_or/2, values/1]). +-export_type([option/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type option(EL) :: {some, EL} | none. + +-file("src/gleam/option.gleam", 57). +-spec reverse_and_prepend(list(FA), list(FA)) -> list(FA). +reverse_and_prepend(Prefix, Suffix) -> + case Prefix of + [] -> + Suffix; + + [First | Rest] -> + reverse_and_prepend(Rest, [First | Suffix]) + end. + +-file("src/gleam/option.gleam", 42). +-spec all_loop(list(option(ER)), list(ER)) -> option(list(ER)). +all_loop(List, Acc) -> + case List of + [] -> + {some, lists:reverse(Acc)}; + + [none | _] -> + none; + + [{some, First} | Rest] -> + all_loop(Rest, [First | Acc]) + end. + +-file("src/gleam/option.gleam", 38). +?DOC( + " Combines a list of `Option`s into a single `Option`.\n" + " If all elements in the list are `Some` then returns a `Some` holding the list of values.\n" + " If any element is `None` then returns `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([Some(1), Some(2)]) == Some([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([Some(1), None]) == None\n" + " ```\n" +). +-spec all(list(option(EM))) -> option(list(EM)). +all(List) -> + all_loop(List, []). + +-file("src/gleam/option.gleam", 76). +?DOC( + " Checks whether the `Option` is a `Some` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_some(Some(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_some(None)\n" + " ```\n" +). +-spec is_some(option(any())) -> boolean(). +is_some(Option) -> + Option /= none. + +-file("src/gleam/option.gleam", 92). +?DOC( + " Checks whether the `Option` is a `None` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !is_none(Some(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_none(None)\n" + " ```\n" +). +-spec is_none(option(any())) -> boolean(). +is_none(Option) -> + Option =:= none. + +-file("src/gleam/option.gleam", 108). +?DOC( + " Converts an `Option` type to a `Result` type.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_result(Some(1), \"some_error\") == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_result(None, \"some_error\") == Error(\"some_error\")\n" + " ```\n" +). +-spec to_result(option(FI), FL) -> {ok, FI} | {error, FL}. +to_result(Option, E) -> + case Option of + {some, A} -> + {ok, A}; + + none -> + {error, E} + end. + +-file("src/gleam/option.gleam", 127). +?DOC( + " Converts a `Result` type to an `Option` type.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_result(Ok(1)) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_result(Error(\"some_error\")) == None\n" + " ```\n" +). +-spec from_result({ok, FO} | {error, any()}) -> option(FO). +from_result(Result) -> + case Result of + {ok, A} -> + {some, A}; + + {error, _} -> + none + end. + +-file("src/gleam/option.gleam", 146). +?DOC( + " Extracts the value from an `Option`, returning a default value if there is none.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap(Some(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap(None, 0) == 0\n" + " ```\n" +). +-spec unwrap(option(FT), FT) -> FT. +unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default + end. + +-file("src/gleam/option.gleam", 165). +?DOC( + " Extracts the value from an `Option`, evaluating the default function if the option is `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Some(1), fn() { 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(None, fn() { 0 }) == 0\n" + " ```\n" +). +-spec lazy_unwrap(option(FV), fun(() -> FV)) -> FV. +lazy_unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default() + end. + +-file("src/gleam/option.gleam", 188). +?DOC( + " Updates a value held within the `Some` of an `Option` by calling a given function\n" + " on it.\n" + "\n" + " If the `Option` is a `None` rather than `Some`, the function is not called and the\n" + " `Option` stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map(over: Some(1), with: fn(x) { x + 1 }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map(over: None, with: fn(x) { x + 1 }) == None\n" + " ```\n" +). +-spec map(option(FX), fun((FX) -> FZ)) -> option(FZ). +map(Option, Fun) -> + case Option of + {some, X} -> + {some, Fun(X)}; + + none -> + none + end. + +-file("src/gleam/option.gleam", 211). +?DOC( + " Merges a nested `Option` into a single layer.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten(Some(Some(1))) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Some(None)) == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(None) == None\n" + " ```\n" +). +-spec flatten(option(option(GB))) -> option(GB). +flatten(Option) -> + case Option of + {some, X} -> + X; + + none -> + none + end. + +-file("src/gleam/option.gleam", 246). +?DOC( + " Updates a value held within the `Some` of an `Option` by calling a given function\n" + " on it, where the given function also returns an `Option`. The two options are\n" + " then merged together into one `Option`.\n" + "\n" + " If the `Option` is a `None` rather than `Some` the function is not called and the\n" + " option stays the same.\n" + "\n" + " This function is the equivalent of calling `map` followed by `flatten`, and\n" + " it is useful for chaining together multiple functions that return `Option`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(x) { Some(x + 1) }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(x) { Some(#(\"a\", x)) }) == Some(#(\"a\", 1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(Some(1), fn(_) { None }) == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert then(None, fn(x) { Some(x + 1) }) == None\n" + " ```\n" +). +-spec then(option(GF), fun((GF) -> option(GH))) -> option(GH). +then(Option, Fun) -> + case Option of + {some, X} -> + Fun(X); + + none -> + none + end. + +-file("src/gleam/option.gleam", 273). +?DOC( + " Returns the first value if it is `Some`, otherwise returns the second value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(Some(1), Some(2)) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Some(1), None) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(None, Some(2)) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(None, None) == None\n" + " ```\n" +). +-spec 'or'(option(GK), option(GK)) -> option(GK). +'or'(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second + end. + +-file("src/gleam/option.gleam", 300). +?DOC( + " Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_or(Some(1), fn() { Some(2) }) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Some(1), fn() { None }) == Some(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(None, fn() { Some(2) }) == Some(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(None, fn() { None }) == None\n" + " ```\n" +). +-spec lazy_or(option(GO), fun(() -> option(GO))) -> option(GO). +lazy_or(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second() + end. + +-file("src/gleam/option.gleam", 320). +-spec values_loop(list(option(GW)), list(GW)) -> list(GW). +values_loop(List, Acc) -> + case List of + [] -> + lists:reverse(Acc); + + [none | Rest] -> + values_loop(Rest, Acc); + + [{some, First} | Rest@1] -> + values_loop(Rest@1, [First | Acc]) + end. + +-file("src/gleam/option.gleam", 316). +?DOC( + " Given a list of `Option`s,\n" + " returns only the values inside `Some`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert values([Some(1), None, Some(3)]) == [1, 3]\n" + " ```\n" +). +-spec values(list(option(GS))) -> list(GS). +values(Options) -> + values_loop(Options, []). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@order.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@order.erl new file mode 100644 index 0000000..c4e87be --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@order.erl @@ -0,0 +1,188 @@ +-module(gleam@order). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/order.gleam"). +-export([negate/1, to_int/1, compare/2, reverse/1, break_tie/2, lazy_break_tie/2]). +-export_type([order/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type order() :: lt | eq | gt. + +-file("src/gleam/order.gleam", 32). +?DOC( + " Inverts an order, so less-than becomes greater-than and greater-than\n" + " becomes less-than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert negate(Lt) == Gt\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(Eq) == Eq\n" + " ```\n" + "\n" + " ```gleam\n" + " assert negate(Gt) == Lt\n" + " ```\n" +). +-spec negate(order()) -> order(). +negate(Order) -> + case Order of + lt -> + gt; + + eq -> + eq; + + gt -> + lt + end. + +-file("src/gleam/order.gleam", 56). +?DOC( + " Produces a numeric representation of the order.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_int(Lt) == -1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_int(Eq) == 0\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_int(Gt) == 1\n" + " ```\n" +). +-spec to_int(order()) -> integer(). +to_int(Order) -> + case Order of + lt -> + -1; + + eq -> + 0; + + gt -> + 1 + end. + +-file("src/gleam/order.gleam", 72). +?DOC( + " Compares two `Order` values to one another, producing a new `Order`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert compare(Eq, with: Lt) == Gt\n" + " ```\n" +). +-spec compare(order(), order()) -> order(). +compare(A, B) -> + case {A, B} of + {X, Y} when X =:= Y -> + eq; + + {lt, _} -> + lt; + + {eq, gt} -> + lt; + + {_, _} -> + gt + end. + +-file("src/gleam/order.gleam", 92). +?DOC( + " Inverts an ordering function, so less-than becomes greater-than and greater-than\n" + " becomes less-than.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + " import gleam/list\n" + "\n" + " assert list.sort([1, 5, 4], by: reverse(int.compare)) == [5, 4, 1]\n" + " ```\n" +). +-spec reverse(fun((I, I) -> order())) -> fun((I, I) -> order()). +reverse(Orderer) -> + fun(A, B) -> Orderer(B, A) end. + +-file("src/gleam/order.gleam", 112). +?DOC( + " Return a fallback `Order` in case the first argument is `Eq`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert break_tie(in: int.compare(1, 1), with: Lt) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert break_tie(in: int.compare(1, 0), with: Eq) == Gt\n" + " ```\n" +). +-spec break_tie(order(), order()) -> order(). +break_tie(Order, Other) -> + case Order of + lt -> + Order; + + gt -> + Order; + + eq -> + Other + end. + +-file("src/gleam/order.gleam", 139). +?DOC( + " Invokes a fallback function returning an `Order` in case the first argument\n" + " is `Eq`.\n" + "\n" + " This can be useful when the fallback comparison might be expensive and it\n" + " needs to be delayed until strictly necessary.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt }) == Lt\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq }) == Gt\n" + " ```\n" +). +-spec lazy_break_tie(order(), fun(() -> order())) -> order(). +lazy_break_tie(Order, Comparison) -> + case Order of + lt -> + Order; + + gt -> + Order; + + eq -> + Comparison() + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@pair.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@pair.erl new file mode 100644 index 0000000..68ac227 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@pair.erl @@ -0,0 +1,104 @@ +-module(gleam@pair). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/pair.gleam"). +-export([first/1, second/1, swap/1, map_first/2, map_second/2, new/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/gleam/pair.gleam", 9). +?DOC( + " Returns the first element in a pair.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first(#(1, 2)) == 1\n" + " ```\n" +). +-spec first({CLY, any()}) -> CLY. +first(Pair) -> + {A, _} = Pair, + A. + +-file("src/gleam/pair.gleam", 22). +?DOC( + " Returns the second element in a pair.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert second(#(1, 2)) == 2\n" + " ```\n" +). +-spec second({any(), CMB}) -> CMB. +second(Pair) -> + {_, A} = Pair, + A. + +-file("src/gleam/pair.gleam", 35). +?DOC( + " Returns a new pair with the elements swapped.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert swap(#(1, 2)) == #(2, 1)\n" + " ```\n" +). +-spec swap({CMC, CMD}) -> {CMD, CMC}. +swap(Pair) -> + {A, B} = Pair, + {B, A}. + +-file("src/gleam/pair.gleam", 49). +?DOC( + " Returns a new pair with the first element having had `with` applied to\n" + " it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert #(1, 2) |> map_first(fn(n) { n * 2 }) == #(2, 2)\n" + " ```\n" +). +-spec map_first({CME, CMF}, fun((CME) -> CMG)) -> {CMG, CMF}. +map_first(Pair, Fun) -> + {A, B} = Pair, + {Fun(A), B}. + +-file("src/gleam/pair.gleam", 63). +?DOC( + " Returns a new pair with the second element having had `with` applied to\n" + " it.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert #(1, 2) |> map_second(fn(n) { n * 2 }) == #(1, 4)\n" + " ```\n" +). +-spec map_second({CMH, CMI}, fun((CMI) -> CMJ)) -> {CMH, CMJ}. +map_second(Pair, Fun) -> + {A, B} = Pair, + {A, Fun(B)}. + +-file("src/gleam/pair.gleam", 77). +?DOC( + " Returns a new pair with the given elements. This can also be done using the dedicated\n" + " syntax instead: `new(1, 2) == #(1, 2)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new(1, 2) == #(1, 2)\n" + " ```\n" +). +-spec new(CMK, CML) -> {CMK, CML}. +new(First, Second) -> + {First, Second}. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@result.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@result.erl new file mode 100644 index 0000000..7aed253 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@result.erl @@ -0,0 +1,500 @@ +-module(gleam@result). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/result.gleam"). +-export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, 'try'/2, unwrap/2, lazy_unwrap/2, unwrap_error/2, 'or'/2, lazy_or/2, all/1, partition/1, replace/2, replace_error/2, values/1, try_recover/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Result represents the result of something that may succeed or not.\n" + " `Ok` means it was successful, `Error` means it was not successful.\n" +). + +-file("src/gleam/result.gleam", 18). +?DOC( + " Checks whether the result is an `Ok` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_ok(Ok(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_ok(Error(Nil))\n" + " ```\n" +). +-spec is_ok({ok, any()} | {error, any()}) -> boolean(). +is_ok(Result) -> + case Result of + {error, _} -> + false; + + {ok, _} -> + true + end. + +-file("src/gleam/result.gleam", 37). +?DOC( + " Checks whether the result is an `Error` value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !is_error(Ok(1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_error(Error(Nil))\n" + " ```\n" +). +-spec is_error({ok, any()} | {error, any()}) -> boolean(). +is_error(Result) -> + case Result of + {ok, _} -> + false; + + {error, _} -> + true + end. + +-file("src/gleam/result.gleam", 60). +?DOC( + " Updates a value held within the `Ok` of a result by calling a given function\n" + " on it.\n" + "\n" + " If the result is an `Error` rather than `Ok` the function is not called and the\n" + " result stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map(over: Ok(1), with: fn(x) { x + 1 }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map(over: Error(1), with: fn(x) { x + 1 }) == Error(1)\n" + " ```\n" +). +-spec map({ok, CMV} | {error, CMW}, fun((CMV) -> CMZ)) -> {ok, CMZ} | + {error, CMW}. +map(Result, Fun) -> + case Result of + {ok, X} -> + {ok, Fun(X)}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/result.gleam", 83). +?DOC( + " Updates a value held within the `Error` of a result by calling a given function\n" + " on it.\n" + "\n" + " If the result is `Ok` rather than `Error` the function is not called and the\n" + " result stays the same.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert map_error(over: Error(1), with: fn(x) { x + 1 }) == Error(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert map_error(over: Ok(1), with: fn(x) { x + 1 }) == Ok(1)\n" + " ```\n" +). +-spec map_error({ok, CNC} | {error, CND}, fun((CND) -> CNG)) -> {ok, CNC} | + {error, CNG}. +map_error(Result, Fun) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, Error} -> + {error, Fun(Error)} + end. + +-file("src/gleam/result.gleam", 109). +?DOC( + " Merges a nested `Result` into a single layer.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert flatten(Ok(Ok(1))) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Ok(Error(\"\"))) == Error(\"\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert flatten(Error(Nil)) == Error(Nil)\n" + " ```\n" +). +-spec flatten({ok, {ok, CNJ} | {error, CNK}} | {error, CNK}) -> {ok, CNJ} | + {error, CNK}. +flatten(Result) -> + case Result of + {ok, X} -> + X; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 143). +?DOC( + " \"Updates\" an `Ok` result by passing its value to a function that yields a result,\n" + " and returning the yielded result. (This may \"replace\" the `Ok` with an `Error`.)\n" + "\n" + " If the input is an `Error` rather than an `Ok`, the function is not called and\n" + " the original `Error` is returned.\n" + "\n" + " This function is the equivalent of calling `map` followed by `flatten`, and\n" + " it is useful for chaining together multiple functions that may fail.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(x) { Ok(x + 1) }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(x) { Ok(#(\"a\", x)) }) == Ok(#(\"a\", 1))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Ok(1), fn(_) { Error(\"Oh no\") }) == Error(\"Oh no\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert try(Error(Nil), fn(x) { Ok(x + 1) }) == Error(Nil)\n" + " ```\n" +). +-spec 'try'({ok, CNR} | {error, CNS}, fun((CNR) -> {ok, CNV} | {error, CNS})) -> {ok, + CNV} | + {error, CNS}. +'try'(Result, Fun) -> + case Result of + {ok, X} -> + Fun(X); + + {error, E} -> + {error, E} + end. + +-file("src/gleam/result.gleam", 166). +?DOC( + " Extracts the `Ok` value from a result, returning a default value if the result\n" + " is an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap(Ok(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap(Error(\"\"), 0) == 0\n" + " ```\n" +). +-spec unwrap({ok, COA} | {error, any()}, COA) -> COA. +unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default + end. + +-file("src/gleam/result.gleam", 186). +?DOC( + " Extracts the `Ok` value from a result, evaluating the default function if the result\n" + " is an `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Ok(1), fn() { 0 }) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_unwrap(Error(\"\"), fn() { 0 }) == 0\n" + " ```\n" +). +-spec lazy_unwrap({ok, COE} | {error, any()}, fun(() -> COE)) -> COE. +lazy_unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default() + end. + +-file("src/gleam/result.gleam", 206). +?DOC( + " Extracts the `Error` value from a result, returning a default value if the result\n" + " is an `Ok`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert unwrap_error(Error(1), 0) == 1\n" + " ```\n" + "\n" + " ```gleam\n" + " assert unwrap_error(Ok(\"\"), 0) == 0\n" + " ```\n" +). +-spec unwrap_error({ok, any()} | {error, COJ}, COJ) -> COJ. +unwrap_error(Result, Default) -> + case Result of + {ok, _} -> + Default; + + {error, E} -> + E + end. + +-file("src/gleam/result.gleam", 233). +?DOC( + " Returns the first value if it is `Ok`, otherwise returns the second value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert or(Ok(1), Ok(2)) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Ok(1), Error(\"Error 2\")) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Error(\"Error 1\"), Ok(2)) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert or(Error(\"Error 1\"), Error(\"Error 2\")) == Error(\"Error 2\")\n" + " ```\n" +). +-spec 'or'({ok, COM} | {error, CON}, {ok, COM} | {error, CON}) -> {ok, COM} | + {error, CON}. +'or'(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second + end. + +-file("src/gleam/result.gleam", 263). +?DOC( + " Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value.\n" + "\n" + " If you need access to the initial error value, use `result.try_recover`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lazy_or(Ok(1), fn() { Ok(2) }) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Ok(1), fn() { Error(\"Error 2\") }) == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Error(\"Error 1\"), fn() { Ok(2) }) == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert lazy_or(Error(\"Error 1\"), fn() { Error(\"Error 2\") })\n" + " == Error(\"Error 2\")\n" + " ```\n" +). +-spec lazy_or({ok, COU} | {error, COV}, fun(() -> {ok, COU} | {error, COV})) -> {ok, + COU} | + {error, COV}. +lazy_or(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second() + end. + +-file("src/gleam/result.gleam", 287). +?DOC( + " Combines a list of results into a single result.\n" + " If all elements in the list are `Ok` then returns an `Ok` holding the list of values.\n" + " If any element is `Error` then returns the first error.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert all([Ok(1), Ok(2)]) == Ok([1, 2])\n" + " ```\n" + "\n" + " ```gleam\n" + " assert all([Ok(1), Error(\"e\")]) == Error(\"e\")\n" + " ```\n" +). +-spec all(list({ok, CPC} | {error, CPD})) -> {ok, list(CPC)} | {error, CPD}. +all(Results) -> + gleam@list:try_map(Results, fun(Result) -> Result end). + +-file("src/gleam/result.gleam", 307). +-spec partition_loop(list({ok, CPR} | {error, CPS}), list(CPR), list(CPS)) -> {list(CPR), + list(CPS)}. +partition_loop(Results, Oks, Errors) -> + case Results of + [] -> + {Oks, Errors}; + + [{ok, A} | Rest] -> + partition_loop(Rest, [A | Oks], Errors); + + [{error, E} | Rest@1] -> + partition_loop(Rest@1, Oks, [E | Errors]) + end. + +-file("src/gleam/result.gleam", 303). +?DOC( + " Given a list of results, returns a pair where the first element is a list\n" + " of all the values inside `Ok` and the second element is a list with all the\n" + " values inside `Error`. The values in both lists appear in reverse order with\n" + " respect to their position in the original list of results.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert partition([Ok(1), Error(\"a\"), Error(\"b\"), Ok(2)])\n" + " == #([2, 1], [\"b\", \"a\"])\n" + " ```\n" +). +-spec partition(list({ok, CPK} | {error, CPL})) -> {list(CPK), list(CPL)}. +partition(Results) -> + partition_loop(Results, [], []). + +-file("src/gleam/result.gleam", 327). +?DOC( + " Replace the value within a result\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace(Ok(1), Nil) == Ok(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace(Error(1), Nil) == Error(1)\n" + " ```\n" +). +-spec replace({ok, any()} | {error, CQA}, CQD) -> {ok, CQD} | {error, CQA}. +replace(Result, Value) -> + case Result of + {ok, _} -> + {ok, Value}; + + {error, Error} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 346). +?DOC( + " Replace the error within a result\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace_error(Error(1), Nil) == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace_error(Ok(1), Nil) == Ok(1)\n" + " ```\n" +). +-spec replace_error({ok, CQG} | {error, any()}, CQK) -> {ok, CQG} | {error, CQK}. +replace_error(Result, Error) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, _} -> + {error, Error} + end. + +-file("src/gleam/result.gleam", 361). +?DOC( + " Given a list of results, returns only the values inside `Ok`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert values([Ok(1), Error(\"a\"), Ok(3)]) == [1, 3]\n" + " ```\n" +). +-spec values(list({ok, CQN} | {error, any()})) -> list(CQN). +values(Results) -> + gleam@list:filter_map(Results, fun(Result) -> Result end). + +-file("src/gleam/result.gleam", 397). +?DOC( + " Updates a value held within the `Error` of a result by calling a given function\n" + " on it, where the given function also returns a result. The two results are\n" + " then merged together into one result.\n" + "\n" + " If the result is an `Ok` rather than `Error` the function is not called and the\n" + " result stays the same.\n" + "\n" + " This function is useful for chaining together computations that may fail\n" + " and trying to recover from possible errors.\n" + "\n" + " If you do not need access to the initial error value, use `result.lazy_or`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert Ok(1)\n" + " |> try_recover(with: fn(_) { Error(\"failed to recover\") })\n" + " == Ok(1)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert Error(1)\n" + " |> try_recover(with: fn(error) { Ok(error + 1) })\n" + " == Ok(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert Error(1)\n" + " |> try_recover(with: fn(error) { Error(\"failed to recover\") })\n" + " == Error(\"failed to recover\")\n" + " ```\n" +). +-spec try_recover( + {ok, CQT} | {error, CQU}, + fun((CQU) -> {ok, CQT} | {error, CQX}) +) -> {ok, CQT} | {error, CQX}. +try_recover(Result, Fun) -> + case Result of + {ok, Value} -> + {ok, Value}; + + {error, Error} -> + Fun(Error) + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@set.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@set.erl new file mode 100644 index 0000000..194c622 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@set.erl @@ -0,0 +1,430 @@ +-module(gleam@set). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/set.gleam"). +-export([new/0, size/1, is_empty/1, contains/2, delete/2, to_list/1, fold/3, filter/2, drop/2, take/2, intersection/2, difference/2, is_subset/2, is_disjoint/2, each/2, insert/2, from_list/1, map/2, union/2, symmetric_difference/2]). +-export_type([set/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-opaque set(CVK) :: {set, gleam@dict:dict(CVK, list(nil))}. + +-file("src/gleam/set.gleam", 32). +?DOC(" Creates a new empty set.\n"). +-spec new() -> set(any()). +new() -> + {set, maps:new()}. + +-file("src/gleam/set.gleam", 50). +?DOC( + " Gets the number of members in a set.\n" + "\n" + " This function runs in constant time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(1)\n" + " |> insert(2)\n" + " |> size\n" + " == 2\n" + " ```\n" +). +-spec size(set(any())) -> integer(). +size(Set) -> + maps:size(erlang:element(2, Set)). + +-file("src/gleam/set.gleam", 66). +?DOC( + " Determines whether or not the set is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{ new() |> insert(1) |> is_empty }\n" + " ```\n" +). +-spec is_empty(set(any())) -> boolean(). +is_empty(Set) -> + Set =:= new(). + +-file("src/gleam/set.gleam", 108). +?DOC( + " Checks whether a set contains a given member.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(2)\n" + " |> contains(2)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !{\n" + " new()\n" + " |> insert(2)\n" + " |> contains(1)\n" + " }\n" + " ```\n" +). +-spec contains(set(CVV), CVV) -> boolean(). +contains(Set, Member) -> + _pipe = erlang:element(2, Set), + _pipe@1 = gleam_stdlib:map_get(_pipe, Member), + gleam@result:is_ok(_pipe@1). + +-file("src/gleam/set.gleam", 130). +?DOC( + " Removes a member from a set. If the set does not contain the member then\n" + " the set is returned unchanged.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !{\n" + " new()\n" + " |> insert(2)\n" + " |> delete(2)\n" + " |> contains(2)\n" + " }\n" + " ```\n" +). +-spec delete(set(CVX), CVX) -> set(CVX). +delete(Set, Member) -> + {set, gleam@dict:delete(erlang:element(2, Set), Member)}. + +-file("src/gleam/set.gleam", 147). +?DOC( + " Converts the set into a list of the contained members.\n" + "\n" + " The list has no specific ordering, any unintentional ordering may change in\n" + " future versions of Gleam or Erlang.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new() |> insert(2) |> to_list == [2]\n" + " ```\n" +). +-spec to_list(set(CWA)) -> list(CWA). +to_list(Set) -> + maps:keys(erlang:element(2, Set)). + +-file("src/gleam/set.gleam", 191). +?DOC( + " Combines all entries into a single value by calling a given function on each\n" + " one.\n" + "\n" + " Sets are not ordered so the values are not returned in any specific order.\n" + " Do not write code that relies on the order entries are used by this\n" + " function as it may change in later versions of Gleam or Erlang.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 3, 9])\n" + " |> fold(0, fn(accumulator, member) { accumulator + member })\n" + " == 13\n" + " ```\n" +). +-spec fold(set(CWG), CWI, fun((CWI, CWG) -> CWI)) -> CWI. +fold(Set, Initial, Reducer) -> + gleam@dict:fold( + erlang:element(2, Set), + Initial, + fun(A, K, _) -> Reducer(A, K) end + ). + +-file("src/gleam/set.gleam", 215). +?DOC( + " Creates a new set from an existing set, minus any members that a given\n" + " function returns `False` for.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + "\n" + " assert from_list([1, 4, 6, 3, 675, 44, 67])\n" + " |> filter(keeping: int.is_even)\n" + " |> to_list\n" + " == [4, 6, 44]\n" + " ```\n" +). +-spec filter(set(CWJ), fun((CWJ) -> boolean())) -> set(CWJ). +filter(Set, Predicate) -> + {set, + gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}. + +-file("src/gleam/set.gleam", 252). +?DOC( + " Creates a new set from a given set with all the same entries except any\n" + " entry found on the given list.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3, 4])\n" + " |> drop([1, 3])\n" + " |> to_list\n" + " == [2, 4]\n" + " ```\n" +). +-spec drop(set(CWQ), list(CWQ)) -> set(CWQ). +drop(Set, Disallowed) -> + gleam@list:fold(Disallowed, Set, fun delete/2). + +-file("src/gleam/set.gleam", 270). +?DOC( + " Creates a new set from a given set, only including any members which are in\n" + " a given list.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3])\n" + " |> take([1, 3, 5])\n" + " |> to_list\n" + " == [1, 3]\n" + " ```\n" +). +-spec take(set(CWU), list(CWU)) -> set(CWU). +take(Set, Desired) -> + {set, gleam@dict:take(erlang:element(2, Set), Desired)}. + +-file("src/gleam/set.gleam", 290). +-spec order(set(CXC), set(CXC)) -> {set(CXC), set(CXC)}. +order(First, Second) -> + case maps:size(erlang:element(2, First)) > maps:size( + erlang:element(2, Second) + ) of + true -> + {First, Second}; + + false -> + {Second, First} + end. + +-file("src/gleam/set.gleam", 308). +?DOC( + " Creates a new set that contains members that are present in both given sets.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert intersection(from_list([1, 2]), from_list([2, 3])) |> to_list\n" + " == [2]\n" + " ```\n" +). +-spec intersection(set(CXH), set(CXH)) -> set(CXH). +intersection(First, Second) -> + {Larger, Smaller} = order(First, Second), + take(Larger, to_list(Smaller)). + +-file("src/gleam/set.gleam", 326). +?DOC( + " Creates a new set that contains members that are present in the first set\n" + " but not the second.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list\n" + " == [1]\n" + " ```\n" +). +-spec difference(set(CXL), set(CXL)) -> set(CXL). +difference(First, Second) -> + drop(First, to_list(Second)). + +-file("src/gleam/set.gleam", 345). +?DOC( + " Determines if a set is fully contained by another.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_subset(from_list([1]), from_list([1, 2]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_subset(from_list([1, 2, 3]), from_list([3, 4, 5]))\n" + " ```\n" +). +-spec is_subset(set(CXP), set(CXP)) -> boolean(). +is_subset(First, Second) -> + intersection(First, Second) =:= First. + +-file("src/gleam/set.gleam", 361). +?DOC( + " Determines if two sets contain no common members\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6]))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5]))\n" + " ```\n" +). +-spec is_disjoint(set(CXS), set(CXS)) -> boolean(). +is_disjoint(First, Second) -> + intersection(First, Second) =:= new(). + +-file("src/gleam/set.gleam", 405). +?DOC( + " Calls a function for each member in a set, discarding the return\n" + " value.\n" + "\n" + " Useful for producing a side effect for every item of a set.\n" + "\n" + " The order of elements in the iteration is an implementation detail that\n" + " should not be relied upon.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let set = from_list([\"apple\", \"banana\", \"cherry\"])\n" + "\n" + " assert each(set, io.println) == Nil\n" + " // apple\n" + " // banana\n" + " // cherry\n" + " ```\n" +). +-spec each(set(CXZ), fun((CXZ) -> any())) -> nil. +each(Set, Fun) -> + fold( + Set, + nil, + fun(Nil, Member) -> + Fun(Member), + Nil + end + ). + +-file("src/gleam/set.gleam", 84). +?DOC( + " Inserts a member into the set.\n" + "\n" + " This function runs in logarithmic time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert new()\n" + " |> insert(1)\n" + " |> insert(2)\n" + " |> size\n" + " == 2\n" + " ```\n" +). +-spec insert(set(CVS), CVS) -> set(CVS). +insert(Set, Member) -> + {set, gleam@dict:insert(erlang:element(2, Set), Member, [])}. + +-file("src/gleam/set.gleam", 168). +?DOC( + " Creates a new set of the members in a given list.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/int\n" + " import gleam/list\n" + "\n" + " assert [1, 1, 2, 4, 3, 2]\n" + " |> from_list\n" + " |> to_list\n" + " |> list.sort(by: int.compare)\n" + " == [1, 2, 3, 4]\n" + " ```\n" +). +-spec from_list(list(CWD)) -> set(CWD). +from_list(Members) -> + Dict = gleam@list:fold( + Members, + maps:new(), + fun(M, K) -> gleam@dict:insert(M, K, []) end + ), + {set, Dict}. + +-file("src/gleam/set.gleam", 234). +?DOC( + " Creates a new set from a given set with the result of applying the given\n" + " function to each member.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_list([1, 2, 3, 4])\n" + " |> map(with: fn(x) { x * 2 })\n" + " |> to_list\n" + " == [2, 4, 6, 8]\n" + " ```\n" +). +-spec map(set(CWM), fun((CWM) -> CWO)) -> set(CWO). +map(Set, Fun) -> + fold(Set, new(), fun(Acc, Member) -> insert(Acc, Fun(Member)) end). + +-file("src/gleam/set.gleam", 285). +?DOC( + " Creates a new set that contains all members of both given sets.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert union(from_list([1, 2]), from_list([2, 3])) |> to_list\n" + " == [1, 2, 3]\n" + " ```\n" +). +-spec union(set(CWY), set(CWY)) -> set(CWY). +union(First, Second) -> + {Larger, Smaller} = order(First, Second), + fold(Smaller, Larger, fun insert/2). + +-file("src/gleam/set.gleam", 376). +?DOC( + " Creates a new set that contains members that are present in either set, but\n" + " not both.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert symmetric_difference(from_list([1, 2, 3]), from_list([3, 4]))\n" + " |> to_list\n" + " == [1, 2, 4]\n" + " ```\n" +). +-spec symmetric_difference(set(CXV), set(CXV)) -> set(CXV). +symmetric_difference(First, Second) -> + difference(union(First, Second), intersection(First, Second)). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string.erl new file mode 100644 index 0000000..410b9ba --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string.erl @@ -0,0 +1,1006 @@ +-module(gleam@string). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/string.gleam"). +-export([is_empty/1, length/1, reverse/1, replace/3, lowercase/1, uppercase/1, compare/2, slice/3, crop/2, drop_end/2, contains/2, starts_with/2, ends_with/2, split_once/2, append/2, concat/1, repeat/2, join/2, pad_start/3, pad_end/3, trim_start/1, trim_end/1, trim/1, pop_grapheme/1, to_graphemes/1, split/2, to_utf_codepoints/1, from_utf_codepoints/1, utf_codepoint/1, utf_codepoint_to_int/1, to_option/1, first/1, last/1, capitalise/1, inspect/1, byte_size/1, drop_start/2, remove_prefix/2, remove_suffix/2]). +-export_type([direction/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Strings in Gleam are UTF-8 binaries. They can be written in your code as\n" + " text surrounded by `\"double quotes\"`.\n" +). + +-type direction() :: leading | trailing. + +-file("src/gleam/string.gleam", 21). +?DOC( + " Determines if a `String` is empty.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert is_empty(\"\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !is_empty(\"the world\")\n" + " ```\n" +). +-spec is_empty(binary()) -> boolean(). +is_empty(Str) -> + Str =:= <<""/utf8>>. + +-file("src/gleam/string.gleam", 46). +?DOC( + " Gets the number of grapheme clusters in a given `String`.\n" + "\n" + " This function has to iterate across the whole string to count the number of\n" + " graphemes, so it runs in linear time. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert length(\"Gleam\") == 5\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length(\"ß↑e̊\") == 3\n" + " ```\n" + "\n" + " ```gleam\n" + " assert length(\"\") == 0\n" + " ```\n" +). +-spec length(binary()) -> integer(). +length(String) -> + string:length(String). + +-file("src/gleam/string.gleam", 59). +?DOC( + " Reverses a `String`.\n" + "\n" + " This function has to iterate across the whole `String` so it runs in linear\n" + " time. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert reverse(\"stressed\") == \"desserts\"\n" + " ```\n" +). +-spec reverse(binary()) -> binary(). +reverse(String) -> + _pipe = String, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = string:reverse(_pipe@1), + unicode:characters_to_binary(_pipe@2). + +-file("src/gleam/string.gleam", 78). +?DOC( + " Creates a new `String` by replacing all occurrences of a given substring.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert replace(\"www.example.com\", each: \".\", with: \"-\") == \"www-example-com\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert replace(\"a,b,c,d,e\", each: \",\", with: \"/\") == \"a/b/c/d/e\"\n" + " ```\n" +). +-spec replace(binary(), binary(), binary()) -> binary(). +replace(String, Pattern, Substitute) -> + _pipe = String, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = gleam_stdlib:string_replace(_pipe@1, Pattern, Substitute), + unicode:characters_to_binary(_pipe@2). + +-file("src/gleam/string.gleam", 102). +?DOC( + " Creates a new `String` with all the graphemes in the input `String` converted to\n" + " lowercase.\n" + "\n" + " Useful for case-insensitive comparisons.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert lowercase(\"X-FILES\") == \"x-files\"\n" + " ```\n" +). +-spec lowercase(binary()) -> binary(). +lowercase(String) -> + string:lowercase(String). + +-file("src/gleam/string.gleam", 117). +?DOC( + " Creates a new `String` with all the graphemes in the input `String` converted to\n" + " uppercase.\n" + "\n" + " Useful for case-insensitive comparisons and VIRTUAL YELLING.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert uppercase(\"skinner\") == \"SKINNER\"\n" + " ```\n" +). +-spec uppercase(binary()) -> binary(). +uppercase(String) -> + string:uppercase(String). + +-file("src/gleam/string.gleam", 137). +?DOC( + " Compares two `String`s to see which is \"larger\" by comparing their graphemes.\n" + "\n" + " This does not compare the size or length of the given `String`s.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " import gleam/order\n" + "\n" + " assert compare(\"Anthony\", \"Anthony\") == order.Eq\n" + " ```\n" + "\n" + " ```gleam\n" + " import gleam/order\n" + "\n" + " assert compare(\"A\", \"B\") == order.Lt\n" + " ```\n" +). +-spec compare(binary(), binary()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + _ -> + case gleam_stdlib:less_than(A, B) of + true -> + lt; + + false -> + gt + end + end. + +-file("src/gleam/string.gleam", 181). +?DOC( + " Takes a substring given a start grapheme index and a length. Negative indexes\n" + " are taken starting from the *end* of the string.\n" + "\n" + " This function runs in linear time with the size of the index and the\n" + " length. Negative indexes are linear with the size of the input string in\n" + " addition to the other costs.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 1, length: 2) == \"le\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 1, length: 10) == \"leam\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: 10, length: 3) == \"\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: -2, length: 2) == \"am\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert slice(from: \"gleam\", at_index: -12, length: 2) == \"\"\n" + " ```\n" +). +-spec slice(binary(), integer(), integer()) -> binary(). +slice(String, Idx, Len) -> + case Len =< 0 of + true -> + <<""/utf8>>; + + false -> + case Idx < 0 of + true -> + Translated_idx = string:length(String) + Idx, + case Translated_idx < 0 of + true -> + <<""/utf8>>; + + false -> + gleam_stdlib:slice(String, Translated_idx, Len) + end; + + false -> + gleam_stdlib:slice(String, Idx, Len) + end + end. + +-file("src/gleam/string.gleam", 218). +?DOC( + " Drops contents of the first `String` that occur before the second `String`.\n" + " If the `from` string does not contain the `before` string, `from` is\n" + " returned unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert crop(from: \"The Lone Gunmen\", before: \"Lone\") == \"Lone Gunmen\"\n" + " ```\n" +). +-spec crop(binary(), binary()) -> binary(). +crop(String, Substring) -> + gleam_stdlib:crop_string(String, Substring). + +-file("src/gleam/string.gleam", 253). +?DOC( + " Drops *n* graphemes from the end of a `String`.\n" + "\n" + " This function traverses the full string, so it runs in linear time with the\n" + " size of the string. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_end(from: \"Cigarette Smoking Man\", up_to: 2)\n" + " == \"Cigarette Smoking M\"\n" + " ```\n" +). +-spec drop_end(binary(), integer()) -> binary(). +drop_end(String, Num_graphemes) -> + case Num_graphemes =< 0 of + true -> + String; + + false -> + slice(String, 0, string:length(String) - Num_graphemes) + end. + +-file("src/gleam/string.gleam", 278). +?DOC( + " Checks if the first `String` contains the second.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert contains(does: \"theory\", contain: \"ory\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert contains(does: \"theory\", contain: \"the\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert !contains(does: \"theory\", contain: \"THE\")\n" + " ```\n" +). +-spec contains(binary(), binary()) -> boolean(). +contains(Haystack, Needle) -> + gleam_stdlib:contains_string(Haystack, Needle). + +-file("src/gleam/string.gleam", 290). +?DOC( + " Checks whether the first `String` starts with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !starts_with(\"theory\", \"ory\")\n" + " ```\n" +). +-spec starts_with(binary(), binary()) -> boolean(). +starts_with(String, Prefix) -> + gleam_stdlib:string_starts_with(String, Prefix). + +-file("src/gleam/string.gleam", 302). +?DOC( + " Checks whether the first `String` ends with the second one.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert ends_with(\"theory\", \"ory\")\n" + " ```\n" +). +-spec ends_with(binary(), binary()) -> boolean(). +ends_with(String, Suffix) -> + gleam_stdlib:string_ends_with(String, Suffix). + +-file("src/gleam/string.gleam", 340). +?DOC( + " Splits a `String` a single time on the given substring.\n" + "\n" + " Returns an `Error` if substring not present.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split_once(\"home/gleam/desktop/\", on: \"/\")\n" + " == Ok(#(\"home\", \"gleam/desktop/\"))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert split_once(\"home/gleam/desktop/\", on: \"?\") == Error(Nil)\n" + " ```\n" +). +-spec split_once(binary(), binary()) -> {ok, {binary(), binary()}} | + {error, nil}. +split_once(String, Substring) -> + case string:split(String, Substring) of + [First, Rest] -> + {ok, {First, Rest}}; + + _ -> + {error, nil} + end. + +-file("src/gleam/string.gleam", 370). +?DOC( + " Creates a new `String` by joining two `String`s together.\n" + "\n" + " This function typically copies both `String`s and runs in linear time, but\n" + " the exact behaviour will depend on how the runtime you are using optimises\n" + " your code. Benchmark and profile your code if you need to understand its\n" + " performance better.\n" + "\n" + " If you are joining together large string and want to avoid copying any data\n" + " you may want to investigate using the [`string_tree`](../gleam/string_tree.html)\n" + " module.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert append(to: \"butter\", suffix: \"fly\") == \"butterfly\"\n" + " ```\n" +). +-spec append(binary(), binary()) -> binary(). +append(First, Second) -> + <>. + +-file("src/gleam/string.gleam", 389). +-spec concat_loop(list(binary()), binary()) -> binary(). +concat_loop(Strings, Accumulator) -> + case Strings of + [String | Strings@1] -> + concat_loop(Strings@1, <>); + + [] -> + Accumulator + end. + +-file("src/gleam/string.gleam", 385). +?DOC( + " Creates a new `String` by joining many `String`s together.\n" + "\n" + " This function copies all the `String`s and runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert concat([\"never\", \"the\", \"less\"]) == \"nevertheless\"\n" + " ```\n" +). +-spec concat(list(binary())) -> binary(). +concat(Strings) -> + erlang:list_to_binary(Strings). + +-file("src/gleam/string.gleam", 413). +-spec repeat_loop(integer(), binary(), binary()) -> binary(). +repeat_loop(Times, Doubling_acc, Acc) -> + Acc@1 = case Times rem 2 of + 0 -> + Acc; + + _ -> + <> + end, + Times@1 = Times div 2, + case Times@1 =< 0 of + true -> + Acc@1; + + false -> + repeat_loop( + Times@1, + <>, + Acc@1 + ) + end. + +-file("src/gleam/string.gleam", 406). +?DOC( + " Creates a new `String` by repeating a `String` a given number of times.\n" + "\n" + " This function runs in loglinear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert repeat(\"ha\", times: 3) == \"hahaha\"\n" + " ```\n" +). +-spec repeat(binary(), integer()) -> binary(). +repeat(String, Times) -> + case Times =< 0 of + true -> + <<""/utf8>>; + + false -> + repeat_loop(Times, String, <<""/utf8>>) + end. + +-file("src/gleam/string.gleam", 442). +-spec join_loop(list(binary()), binary(), binary()) -> binary(). +join_loop(Strings, Separator, Accumulator) -> + case Strings of + [] -> + Accumulator; + + [String | Strings@1] -> + join_loop( + Strings@1, + Separator, + <<<>/binary, + String/binary>> + ) + end. + +-file("src/gleam/string.gleam", 435). +?DOC( + " Joins many `String`s together with a given separator.\n" + "\n" + " This function runs in linear time.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert join([\"home\",\"evan\",\"Desktop\"], with: \"/\") == \"home/evan/Desktop\"\n" + " ```\n" +). +-spec join(list(binary()), binary()) -> binary(). +join(Strings, Separator) -> + case Strings of + [] -> + <<""/utf8>>; + + [First | Rest] -> + join_loop(Rest, Separator, First) + end. + +-file("src/gleam/string.gleam", 514). +-spec padding(integer(), binary()) -> binary(). +padding(Size, Pad_string) -> + Pad_string_length = string:length(Pad_string), + Num_pads = case Pad_string_length of + 0 -> 0; + Gleam@denominator -> Size div Gleam@denominator + end, + Extra = case Pad_string_length of + 0 -> 0; + Gleam@denominator@1 -> Size rem Gleam@denominator@1 + end, + <<(repeat(Pad_string, Num_pads))/binary, + (slice(Pad_string, 0, Extra))/binary>>. + +-file("src/gleam/string.gleam", 470). +?DOC( + " Pads the start of a `String` until it has a given length.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 5, with: \".\") == \"..121\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 3, with: \".\") == \"121\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_start(\"121\", to: 2, with: \".\") == \"121\"\n" + " ```\n" +). +-spec pad_start(binary(), integer(), binary()) -> binary(). +pad_start(String, Desired_length, Pad_string) -> + Current_length = string:length(String), + To_pad_length = Desired_length - Current_length, + case To_pad_length =< 0 of + true -> + String; + + false -> + <<(padding(To_pad_length, Pad_string))/binary, String/binary>> + end. + +-file("src/gleam/string.gleam", 500). +?DOC( + " Pads the end of a `String` until it has a given length.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 5, with: \".\") == \"123..\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 3, with: \".\") == \"123\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pad_end(\"123\", to: 2, with: \".\") == \"123\"\n" + " ```\n" +). +-spec pad_end(binary(), integer(), binary()) -> binary(). +pad_end(String, Desired_length, Pad_string) -> + Current_length = string:length(String), + To_pad_length = Desired_length - Current_length, + case To_pad_length =< 0 of + true -> + String; + + false -> + <> + end. + +-file("src/gleam/string.gleam", 556). +?DOC( + " Removes whitespace at the start of a `String`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim_start(\" hats \\n\") == \"hats \\n\"\n" + " ```\n" +). +-spec trim_start(binary()) -> binary(). +trim_start(String) -> + string:trim(String, leading). + +-file("src/gleam/string.gleam", 569). +?DOC( + " Removes whitespace at the end of a `String`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim_end(\" hats \\n\") == \" hats\"\n" + " ```\n" +). +-spec trim_end(binary()) -> binary(). +trim_end(String) -> + string:trim(String, trailing). + +-file("src/gleam/string.gleam", 535). +?DOC( + " Removes whitespace on both sides of a `String`.\n" + "\n" + " Whitespace in this function is the set of nonbreakable whitespace\n" + " codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1].\n" + "\n" + " [1]: https://unicode.org/reports/tr31/\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert trim(\" hats \\n\") == \"hats\"\n" + " ```\n" +). +-spec trim(binary()) -> binary(). +trim(String) -> + _pipe = String, + _pipe@1 = trim_start(_pipe), + trim_end(_pipe@1). + +-file("src/gleam/string.gleam", 594). +?DOC( + " Splits a non-empty `String` into its first element (head) and rest (tail).\n" + " This lets you pattern match on `String`s exactly as you would with lists.\n" + "\n" + " ## Performance\n" + "\n" + " There is a notable overhead to using this function, so you may not want to\n" + " use it in a tight loop. If you wish to efficiently parse a string you may\n" + " want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter).\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert pop_grapheme(\"gleam\") == Ok(#(\"g\", \"leam\"))\n" + " ```\n" + "\n" + " ```gleam\n" + " assert pop_grapheme(\"\") == Error(Nil)\n" + " ```\n" +). +-spec pop_grapheme(binary()) -> {ok, {binary(), binary()}} | {error, nil}. +pop_grapheme(String) -> + gleam_stdlib:string_pop_grapheme(String). + +-file("src/gleam/string.gleam", 610). +-spec to_graphemes_loop(binary(), list(binary())) -> list(binary()). +to_graphemes_loop(String, Acc) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {Grapheme, Rest}} -> + to_graphemes_loop(Rest, [Grapheme | Acc]); + + {error, _} -> + Acc + end. + +-file("src/gleam/string.gleam", 604). +?DOC( + " Converts a `String` to a list of\n" + " [graphemes](https://en.wikipedia.org/wiki/Grapheme).\n" + "\n" + " ```gleam\n" + " assert to_graphemes(\"abc\") == [\"a\", \"b\", \"c\"]\n" + " ```\n" +). +-spec to_graphemes(binary()) -> list(binary()). +to_graphemes(String) -> + _pipe = String, + _pipe@1 = to_graphemes_loop(_pipe, []), + lists:reverse(_pipe@1). + +-file("src/gleam/string.gleam", 313). +?DOC( + " Creates a list of `String`s by splitting a given string on a given substring.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert split(\"home/gleam/desktop/\", on: \"/\")\n" + " == [\"home\", \"gleam\", \"desktop\", \"\"]\n" + " ```\n" +). +-spec split(binary(), binary()) -> list(binary()). +split(X, Substring) -> + case Substring of + <<""/utf8>> -> + to_graphemes(X); + + _ -> + _pipe = X, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = gleam@string_tree:split(_pipe@1, Substring), + gleam@list:map(_pipe@2, fun unicode:characters_to_binary/1) + end. + +-file("src/gleam/string.gleam", 656). +-spec to_utf_codepoints_loop(bitstring(), list(integer())) -> list(integer()). +to_utf_codepoints_loop(Bit_array, Acc) -> + case Bit_array of + <> -> + to_utf_codepoints_loop(Rest, [First | Acc]); + + _ -> + lists:reverse(Acc) + end. + +-file("src/gleam/string.gleam", 651). +-spec do_to_utf_codepoints(binary()) -> list(integer()). +do_to_utf_codepoints(String) -> + to_utf_codepoints_loop(<>, []). + +-file("src/gleam/string.gleam", 646). +?DOC( + " Converts a `String` to a `List` of `UtfCodepoint`.\n" + "\n" + " See and\n" + " for an\n" + " explanation on code points.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert \"a\" |> to_utf_codepoints == [UtfCodepoint(97)]\n" + " ```\n" + "\n" + " ```gleam\n" + " // Semantically the same as:\n" + " // [\"🏳\", \"️\", \"‍\", \"🌈\"] or:\n" + " // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow]\n" + " assert \"🏳️‍🌈\" |> to_utf_codepoints\n" + " == [\n" + " UtfCodepoint(127987),\n" + " UtfCodepoint(65039),\n" + " UtfCodepoint(8205),\n" + " UtfCodepoint(127752),\n" + " ]\n" + " ```\n" +). +-spec to_utf_codepoints(binary()) -> list(integer()). +to_utf_codepoints(String) -> + do_to_utf_codepoints(String). + +-file("src/gleam/string.gleam", 695). +?DOC( + " Converts a `List` of `UtfCodepoint`s to a `String`.\n" + "\n" + " See and\n" + " for an\n" + " explanation on code points.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert Ok(a) = utf_codepoint(97)\n" + " let assert Ok(b) = utf_codepoint(98)\n" + " let assert Ok(c) = utf_codepoint(99)\n" + " assert from_utf_codepoints([a, b, c]) == \"abc\"\n" + " ```\n" +). +-spec from_utf_codepoints(list(integer())) -> binary(). +from_utf_codepoints(Utf_codepoints) -> + gleam_stdlib:utf_codepoint_list_to_string(Utf_codepoints). + +-file("src/gleam/string.gleam", 701). +?DOC( + " Converts an integer to a `UtfCodepoint`.\n" + "\n" + " Returns an `Error` if the integer does not represent a valid UTF codepoint.\n" +). +-spec utf_codepoint(integer()) -> {ok, integer()} | {error, nil}. +utf_codepoint(Value) -> + case Value of + I when I > 1114111 -> + {error, nil}; + + I@1 when (I@1 >= 55296) andalso (I@1 =< 57343) -> + {error, nil}; + + I@2 when I@2 < 0 -> + {error, nil}; + + I@3 -> + {ok, gleam_stdlib:identity(I@3)} + end. + +-file("src/gleam/string.gleam", 721). +?DOC( + " Converts a `UtfCodepoint` to its ordinal code point value.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert [utf_codepoint, ..] = to_utf_codepoints(\"💜\")\n" + " assert utf_codepoint_to_int(utf_codepoint) == 128156\n" + " ```\n" +). +-spec utf_codepoint_to_int(integer()) -> integer(). +utf_codepoint_to_int(Cp) -> + gleam_stdlib:identity(Cp). + +-file("src/gleam/string.gleam", 736). +?DOC( + " Converts a `String` into `Option(String)` where an empty `String` becomes\n" + " `None`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert to_option(\"\") == None\n" + " ```\n" + "\n" + " ```gleam\n" + " assert to_option(\"hats\") == Some(\"hats\")\n" + " ```\n" +). +-spec to_option(binary()) -> gleam@option:option(binary()). +to_option(String) -> + case String of + <<""/utf8>> -> + none; + + _ -> + {some, String} + end. + +-file("src/gleam/string.gleam", 757). +?DOC( + " Returns the first grapheme cluster in a given `String` and wraps it in a\n" + " `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n" + " Otherwise, it returns `Ok(String)`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert first(\"\") == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert first(\"icecream\") == Ok(\"i\")\n" + " ```\n" +). +-spec first(binary()) -> {ok, binary()} | {error, nil}. +first(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, _}} -> + {ok, First}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/string.gleam", 781). +?DOC( + " Returns the last grapheme cluster in a given `String` and wraps it in a\n" + " `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n" + " Otherwise, it returns `Ok(String)`.\n" + "\n" + " This function traverses the full string, so it runs in linear time with the\n" + " length of the string. Avoid using this in a loop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert last(\"\") == Error(Nil)\n" + " ```\n" + "\n" + " ```gleam\n" + " assert last(\"icecream\") == Ok(\"m\")\n" + " ```\n" +). +-spec last(binary()) -> {ok, binary()} | {error, nil}. +last(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, <<""/utf8>>}} -> + {ok, First}; + + {ok, {_, Rest}} -> + {ok, slice(Rest, -1, 1)}; + + {error, E} -> + {error, E} + end. + +-file("src/gleam/string.gleam", 798). +?DOC( + " Creates a new `String` with the first grapheme in the input `String`\n" + " converted to uppercase and the remaining graphemes to lowercase.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert capitalise(\"mamouna\") == \"Mamouna\"\n" + " ```\n" +). +-spec capitalise(binary()) -> binary(). +capitalise(String) -> + case gleam_stdlib:string_pop_grapheme(String) of + {ok, {First, Rest}} -> + append(string:uppercase(First), string:lowercase(Rest)); + + {error, _} -> + <<""/utf8>> + end. + +-file("src/gleam/string.gleam", 829). +?DOC( + " Returns a `String` representation of a term in Gleam syntax.\n" + "\n" + " This may be occasionally useful for quick-and-dirty printing of values in\n" + " scripts. For error reporting and other uses prefer constructing strings by\n" + " pattern matching on the values.\n" + "\n" + " ## Limitations\n" + "\n" + " The output format of this function is not stable and could change at any\n" + " time. The output is not suitable for parsing.\n" + "\n" + " This function works using runtime reflection, so the output may not be\n" + " perfectly accurate for data structures where the runtime structure doesn't\n" + " hold enough information to determine the original syntax. For example,\n" + " tuples with an Erlang atom in the first position will be mistaken for Gleam\n" + " records.\n" + "\n" + " ## Security and safety\n" + "\n" + " There is no limit to how large the strings that this function can produce.\n" + " Be careful not to call this function with large data structures or you\n" + " could use very large amounts of memory, potentially causing runtime\n" + " problems.\n" +). +-spec inspect(any()) -> binary(). +inspect(Term) -> + _pipe = Term, + _pipe@1 = gleam_stdlib:inspect(_pipe), + unicode:characters_to_binary(_pipe@1). + +-file("src/gleam/string.gleam", 852). +?DOC( + " Returns the number of bytes in a `String`.\n" + "\n" + " This function runs in constant time on Erlang and in linear time on\n" + " JavaScript.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert byte_size(\"🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻\") == 58\n" + " ```\n" +). +-spec byte_size(binary()) -> integer(). +byte_size(String) -> + erlang:byte_size(String). + +-file("src/gleam/string.gleam", 230). +?DOC( + " Drops *n* graphemes from the start of a `String`.\n" + "\n" + " This function runs in linear time with the number of graphemes to drop.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert drop_start(from: \"The Lone Gunmen\", up_to: 2) == \"e Lone Gunmen\"\n" + " ```\n" +). +-spec drop_start(binary(), integer()) -> binary(). +drop_start(String, Num_graphemes) -> + case Num_graphemes =< 0 of + true -> + String; + + false -> + Prefix = gleam_stdlib:slice(String, 0, Num_graphemes), + Prefix_size = erlang:byte_size(Prefix), + binary:part( + String, + Prefix_size, + erlang:byte_size(String) - Prefix_size + ) + end. + +-file("src/gleam/string.gleam", 871). +?DOC( + " Removes the given prefix from the start of a `String`, if present.\n" + "\n" + " If the `String` does not start with the given prefix the string is returned\n" + " unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remove_prefix(\"@lpil\", \"@\") == \"lpil\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remove_prefix(\"hello!\", \"@\") == \"hello!\"\n" + " ```\n" +). +-spec remove_prefix(binary(), binary()) -> binary(). +remove_prefix(String, Prefix) -> + gleam_stdlib:string_remove_prefix(String, Prefix). + +-file("src/gleam/string.gleam", 890). +?DOC( + " Removes the given suffix from the end of a `String`, if present.\n" + "\n" + " If the `String` does not end with the given suffix the string is returned\n" + " unchanged.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert remove_suffix(\"Hello!\", \"!\") == \"Hello\"\n" + " ```\n" + "\n" + " ```gleam\n" + " assert remove_suffix(\"Hello!?\", \"!\") == \"Hello!?\"\n" + " ```\n" +). +-spec remove_suffix(binary(), binary()) -> binary(). +remove_suffix(String, Suffix) -> + gleam_stdlib:string_remove_suffix(String, Suffix). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string_tree.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string_tree.erl new file mode 100644 index 0000000..0720f3b --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@string_tree.erl @@ -0,0 +1,202 @@ +-module(gleam@string_tree). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/string_tree.gleam"). +-export([append_tree/2, prepend_tree/2, from_strings/1, new/0, concat/1, from_string/1, prepend/2, append/2, to_string/1, byte_size/1, join/2, lowercase/1, uppercase/1, reverse/1, split/2, replace/3, is_equal/2, is_empty/1]). +-export_type([string_tree/0, direction/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type string_tree() :: any(). + +-type direction() :: all. + +-file("src/gleam/string_tree.gleam", 61). +?DOC( + " Appends some `StringTree` onto the end of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec append_tree(string_tree(), string_tree()) -> string_tree(). +append_tree(Tree, Suffix) -> + gleam_stdlib:iodata_append(Tree, Suffix). + +-file("src/gleam/string_tree.gleam", 48). +?DOC( + " Prepends some `StringTree` onto the start of another.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend_tree(string_tree(), string_tree()) -> string_tree(). +prepend_tree(Tree, Prefix) -> + gleam_stdlib:iodata_append(Prefix, Tree). + +-file("src/gleam/string_tree.gleam", 69). +?DOC( + " Converts a list of strings into a `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_strings(list(binary())) -> string_tree(). +from_strings(Strings) -> + gleam_stdlib:identity(Strings). + +-file("src/gleam/string_tree.gleam", 24). +?DOC( + " Create an empty `StringTree`. Useful as the start of a pipe chaining many\n" + " trees together.\n" +). +-spec new() -> string_tree(). +new() -> + gleam_stdlib:identity([]). + +-file("src/gleam/string_tree.gleam", 77). +?DOC( + " Joins a list of trees into a single tree.\n" + "\n" + " Runs in constant time.\n" +). +-spec concat(list(string_tree())) -> string_tree(). +concat(Trees) -> + gleam_stdlib:identity(Trees). + +-file("src/gleam/string_tree.gleam", 85). +?DOC( + " Converts a string into a `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec from_string(binary()) -> string_tree(). +from_string(String) -> + gleam_stdlib:identity(String). + +-file("src/gleam/string_tree.gleam", 32). +?DOC( + " Prepends a `String` onto the start of some `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec prepend(string_tree(), binary()) -> string_tree(). +prepend(Tree, Prefix) -> + gleam_stdlib:iodata_append(gleam_stdlib:identity(Prefix), Tree). + +-file("src/gleam/string_tree.gleam", 40). +?DOC( + " Appends a `String` onto the end of some `StringTree`.\n" + "\n" + " Runs in constant time.\n" +). +-spec append(string_tree(), binary()) -> string_tree(). +append(Tree, Second) -> + gleam_stdlib:iodata_append(Tree, gleam_stdlib:identity(Second)). + +-file("src/gleam/string_tree.gleam", 94). +?DOC( + " Turns a `StringTree` into a `String`.\n" + "\n" + " This function is implemented natively by the virtual machine and is highly\n" + " optimised.\n" +). +-spec to_string(string_tree()) -> binary(). +to_string(Tree) -> + unicode:characters_to_binary(Tree). + +-file("src/gleam/string_tree.gleam", 100). +?DOC(" Returns the size of the `StringTree` in bytes.\n"). +-spec byte_size(string_tree()) -> integer(). +byte_size(Tree) -> + erlang:iolist_size(Tree). + +-file("src/gleam/string_tree.gleam", 104). +?DOC(" Joins the given trees into a new tree separated with the given string.\n"). +-spec join(list(string_tree()), binary()) -> string_tree(). +join(Trees, Sep) -> + _pipe = Trees, + _pipe@1 = gleam@list:intersperse(_pipe, gleam_stdlib:identity(Sep)), + gleam_stdlib:identity(_pipe@1). + +-file("src/gleam/string_tree.gleam", 115). +?DOC( + " Converts a `StringTree` to a new one where the contents have been\n" + " lowercased.\n" +). +-spec lowercase(string_tree()) -> string_tree(). +lowercase(Tree) -> + string:lowercase(Tree). + +-file("src/gleam/string_tree.gleam", 122). +?DOC( + " Converts a `StringTree` to a new one where the contents have been\n" + " uppercased.\n" +). +-spec uppercase(string_tree()) -> string_tree(). +uppercase(Tree) -> + string:uppercase(Tree). + +-file("src/gleam/string_tree.gleam", 127). +?DOC(" Converts a `StringTree` to a new one with the contents reversed.\n"). +-spec reverse(string_tree()) -> string_tree(). +reverse(Tree) -> + string:reverse(Tree). + +-file("src/gleam/string_tree.gleam", 145). +?DOC(" Splits a `StringTree` on a given pattern into a list of trees.\n"). +-spec split(string_tree(), binary()) -> list(string_tree()). +split(Tree, Pattern) -> + string:split(Tree, Pattern, all). + +-file("src/gleam/string_tree.gleam", 156). +?DOC(" Replaces all instances of a pattern with a given string substitute.\n"). +-spec replace(string_tree(), binary(), binary()) -> string_tree(). +replace(Tree, Pattern, Substitute) -> + gleam_stdlib:string_replace(Tree, Pattern, Substitute). + +-file("src/gleam/string_tree.gleam", 180). +?DOC( + " Compares two string trees to determine if they have the same textual\n" + " content.\n" + "\n" + " Comparing two string trees using the `==` operator may return `False` even\n" + " if they have the same content as they may have been build in different ways,\n" + " so using this function is often preferred.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert from_strings([\"a\", \"b\"]) != from_string(\"ab\")\n" + " ```\n" + "\n" + " ```gleam\n" + " assert is_equal(from_strings([\"a\", \"b\"]), from_string(\"ab\"))\n" + " ```\n" +). +-spec is_equal(string_tree(), string_tree()) -> boolean(). +is_equal(A, B) -> + string:equal(A, B). + +-file("src/gleam/string_tree.gleam", 201). +?DOC( + " Inspects a `StringTree` to determine if it is equivalent to an empty string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert !{ from_string(\"ok\") |> is_empty }\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_string(\"\") |> is_empty\n" + " ```\n" + "\n" + " ```gleam\n" + " assert from_strings([]) |> is_empty\n" + " ```\n" +). +-spec is_empty(string_tree()) -> boolean(). +is_empty(Tree) -> + string:is_empty(Tree). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@uri.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@uri.erl new file mode 100644 index 0000000..7acb110 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam@uri.erl @@ -0,0 +1,1042 @@ +-module(gleam@uri). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/uri.gleam"). +-export([parse_query/1, percent_encode/1, query_to_string/1, percent_decode/1, path_segments/1, to_string/1, origin/1, merge/2, parse/1]). +-export_type([uri/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Utilities for working with URIs\n" + "\n" + " This module provides functions for working with URIs (for example, parsing\n" + " URIs or encoding query strings). The functions in this module are implemented\n" + " according to [RFC 3986](https://tools.ietf.org/html/rfc3986).\n" + "\n" + " Query encoding (Form encoding) is defined in the\n" + " [W3C specification](https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data).\n" +). + +-type uri() :: {uri, + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(integer()), + binary(), + gleam@option:option(binary()), + gleam@option:option(binary())}. + +-file("src/gleam/uri.gleam", 289). +-spec is_valid_host_within_brackets_char(integer()) -> boolean(). +is_valid_host_within_brackets_char(Char) -> + (((((48 >= Char) andalso (Char =< 57)) orelse ((65 >= Char) andalso (Char =< 90))) + orelse ((97 >= Char) andalso (Char =< 122))) + orelse (Char =:= 58)) + orelse (Char =:= 46). + +-file("src/gleam/uri.gleam", 506). +-spec parse_fragment(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_fragment(Rest, Pieces) -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + {some, Rest}}}. + +-file("src/gleam/uri.gleam", 478). +-spec parse_query_with_question_mark_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_query_with_question_mark_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"#"/utf8, Rest/binary>> when Size =:= 0 -> + parse_fragment(Rest, Pieces); + + <<"#"/utf8, Rest@1/binary>> -> + Query = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + {some, Query}, + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@1); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + {some, Original}, + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_query_with_question_mark_loop( + Original, + Rest@2, + Pieces, + Size + 1 + ) + end. + +-file("src/gleam/uri.gleam", 471). +-spec parse_query_with_question_mark(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_query_with_question_mark(Uri_string, Pieces) -> + parse_query_with_question_mark_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 437). +-spec parse_path_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_path_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"?"/utf8, Rest/binary>> -> + Path = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Path, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest, Pieces@1); + + <<"#"/utf8, Rest@1/binary>> -> + Path@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Path@1, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@2); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Original, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_path_loop(Original, Rest@2, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 433). +-spec parse_path(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_path(Uri_string, Pieces) -> + parse_path_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 388). +-spec parse_port_loop(binary(), uri(), integer()) -> {ok, uri()} | {error, nil}. +parse_port_loop(Uri_string, Pieces, Port) -> + case Uri_string of + <<"0"/utf8, Rest/binary>> -> + parse_port_loop(Rest, Pieces, Port * 10); + + <<"1"/utf8, Rest@1/binary>> -> + parse_port_loop(Rest@1, Pieces, (Port * 10) + 1); + + <<"2"/utf8, Rest@2/binary>> -> + parse_port_loop(Rest@2, Pieces, (Port * 10) + 2); + + <<"3"/utf8, Rest@3/binary>> -> + parse_port_loop(Rest@3, Pieces, (Port * 10) + 3); + + <<"4"/utf8, Rest@4/binary>> -> + parse_port_loop(Rest@4, Pieces, (Port * 10) + 4); + + <<"5"/utf8, Rest@5/binary>> -> + parse_port_loop(Rest@5, Pieces, (Port * 10) + 5); + + <<"6"/utf8, Rest@6/binary>> -> + parse_port_loop(Rest@6, Pieces, (Port * 10) + 6); + + <<"7"/utf8, Rest@7/binary>> -> + parse_port_loop(Rest@7, Pieces, (Port * 10) + 7); + + <<"8"/utf8, Rest@8/binary>> -> + parse_port_loop(Rest@8, Pieces, (Port * 10) + 8); + + <<"9"/utf8, Rest@9/binary>> -> + parse_port_loop(Rest@9, Pieces, (Port * 10) + 9); + + <<"?"/utf8, Rest@10/binary>> -> + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@10, Pieces@1); + + <<"#"/utf8, Rest@11/binary>> -> + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@11, Pieces@2); + + <<"/"/utf8, _/binary>> -> + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@3); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + {some, Port}, + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 353). +-spec parse_port(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_port(Uri_string, Pieces) -> + case Uri_string of + <<":0"/utf8, Rest/binary>> -> + parse_port_loop(Rest, Pieces, 0); + + <<":1"/utf8, Rest@1/binary>> -> + parse_port_loop(Rest@1, Pieces, 1); + + <<":2"/utf8, Rest@2/binary>> -> + parse_port_loop(Rest@2, Pieces, 2); + + <<":3"/utf8, Rest@3/binary>> -> + parse_port_loop(Rest@3, Pieces, 3); + + <<":4"/utf8, Rest@4/binary>> -> + parse_port_loop(Rest@4, Pieces, 4); + + <<":5"/utf8, Rest@5/binary>> -> + parse_port_loop(Rest@5, Pieces, 5); + + <<":6"/utf8, Rest@6/binary>> -> + parse_port_loop(Rest@6, Pieces, 6); + + <<":7"/utf8, Rest@7/binary>> -> + parse_port_loop(Rest@7, Pieces, 7); + + <<":8"/utf8, Rest@8/binary>> -> + parse_port_loop(Rest@8, Pieces, 8); + + <<":9"/utf8, Rest@9/binary>> -> + parse_port_loop(Rest@9, Pieces, 9); + + <<":"/utf8>> -> + {ok, Pieces}; + + <<""/utf8>> -> + {ok, Pieces}; + + <<"?"/utf8, Rest@10/binary>> -> + parse_query_with_question_mark(Rest@10, Pieces); + + <<":?"/utf8, Rest@10/binary>> -> + parse_query_with_question_mark(Rest@10, Pieces); + + <<"#"/utf8, Rest@11/binary>> -> + parse_fragment(Rest@11, Pieces); + + <<":#"/utf8, Rest@11/binary>> -> + parse_fragment(Rest@11, Pieces); + + <<"/"/utf8, _/binary>> -> + parse_path(Uri_string, Pieces); + + <<":"/utf8, Rest@12/binary>> -> + case Rest@12 of + <<"/"/utf8, _/binary>> -> + parse_path(Rest@12, Pieces); + + _ -> + {error, nil} + end; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 309). +-spec parse_host_outside_of_brackets_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_host_outside_of_brackets_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Original}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<":"/utf8, _/binary>> -> + Host = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Uri_string, Pieces@1); + + <<"/"/utf8, _/binary>> -> + Host@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@1}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@2); + + <<"?"/utf8, Rest/binary>> -> + Host@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@2}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest, Pieces@3); + + <<"#"/utf8, Rest@1/binary>> -> + Host@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@3}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@1, Pieces@4); + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_host_outside_of_brackets_loop( + Original, + Rest@2, + Pieces, + Size + 1 + ) + end. + +-file("src/gleam/uri.gleam", 229). +-spec parse_host_within_brackets_loop(binary(), binary(), uri(), integer()) -> {ok, + uri()} | + {error, nil}. +parse_host_within_brackets_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Uri_string}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<"]"/utf8, Rest/binary>> when Size =:= 0 -> + parse_port(Rest, Pieces); + + <<"]"/utf8, Rest@1/binary>> -> + Host = binary:part(Original, 0, Size + 1), + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Rest@1, Pieces@1); + + <<"/"/utf8, _/binary>> when Size =:= 0 -> + parse_path(Uri_string, Pieces); + + <<"/"/utf8, _/binary>> -> + Host@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@1}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_path(Uri_string, Pieces@2); + + <<"?"/utf8, Rest@2/binary>> when Size =:= 0 -> + parse_query_with_question_mark(Rest@2, Pieces); + + <<"?"/utf8, Rest@3/binary>> -> + Host@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@2}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@3, Pieces@3); + + <<"#"/utf8, Rest@4/binary>> when Size =:= 0 -> + parse_fragment(Rest@4, Pieces); + + <<"#"/utf8, Rest@5/binary>> -> + Host@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, Host@3}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@5, Pieces@4); + + _ -> + {Char, Rest@6} = gleam_stdlib:string_pop_codeunit(Uri_string), + case is_valid_host_within_brackets_char(Char) of + true -> + parse_host_within_brackets_loop( + Original, + Rest@6, + Pieces, + Size + 1 + ); + + false -> + parse_host_outside_of_brackets_loop( + Original, + Original, + Pieces, + 0 + ) + end + end. + +-file("src/gleam/uri.gleam", 222). +-spec parse_host_within_brackets(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_host_within_brackets(Uri_string, Pieces) -> + parse_host_within_brackets_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 302). +-spec parse_host_outside_of_brackets(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_host_outside_of_brackets(Uri_string, Pieces) -> + parse_host_outside_of_brackets_loop(Uri_string, Uri_string, Pieces, 0). + +-file("src/gleam/uri.gleam", 199). +-spec parse_host(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_host(Uri_string, Pieces) -> + case Uri_string of + <<"["/utf8, _/binary>> -> + parse_host_within_brackets(Uri_string, Pieces); + + <<":"/utf8, _/binary>> -> + Pieces@1 = {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_port(Uri_string, Pieces@1); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + parse_host_outside_of_brackets(Uri_string, Pieces) + end. + +-file("src/gleam/uri.gleam", 167). +-spec parse_userinfo_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_userinfo_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"@"/utf8, Rest/binary>> when Size =:= 0 -> + parse_host(Rest, Pieces); + + <<"@"/utf8, Rest@1/binary>> -> + Userinfo = binary:part(Original, 0, Size), + Pieces@1 = {uri, + erlang:element(2, Pieces), + {some, Userinfo}, + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_host(Rest@1, Pieces@1); + + <<""/utf8>> -> + parse_host(Original, Pieces); + + <<"/"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + <<"?"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + <<"#"/utf8, _/binary>> -> + parse_host(Original, Pieces); + + _ -> + {_, Rest@2} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_userinfo_loop(Original, Rest@2, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 163). +-spec parse_authority_pieces(binary(), uri()) -> {ok, uri()} | {error, nil}. +parse_authority_pieces(String, Pieces) -> + parse_userinfo_loop(String, String, Pieces, 0). + +-file("src/gleam/uri.gleam", 150). +-spec parse_authority_with_slashes(binary(), uri()) -> {ok, uri()} | + {error, nil}. +parse_authority_with_slashes(Uri_string, Pieces) -> + case Uri_string of + <<"//"/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + {some, <<""/utf8>>}, + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + <<"//"/utf8, Rest/binary>> -> + parse_authority_pieces(Rest, Pieces); + + _ -> + parse_path(Uri_string, Pieces) + end. + +-file("src/gleam/uri.gleam", 91). +-spec parse_scheme_loop(binary(), binary(), uri(), integer()) -> {ok, uri()} | + {error, nil}. +parse_scheme_loop(Original, Uri_string, Pieces, Size) -> + case Uri_string of + <<"/"/utf8, _/binary>> when Size =:= 0 -> + parse_authority_with_slashes(Uri_string, Pieces); + + <<"/"/utf8, _/binary>> -> + Scheme = binary:part(Original, 0, Size), + Pieces@1 = {uri, + {some, string:lowercase(Scheme)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_authority_with_slashes(Uri_string, Pieces@1); + + <<"?"/utf8, Rest/binary>> when Size =:= 0 -> + parse_query_with_question_mark(Rest, Pieces); + + <<"?"/utf8, Rest@1/binary>> -> + Scheme@1 = binary:part(Original, 0, Size), + Pieces@2 = {uri, + {some, string:lowercase(Scheme@1)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_query_with_question_mark(Rest@1, Pieces@2); + + <<"#"/utf8, Rest@2/binary>> when Size =:= 0 -> + parse_fragment(Rest@2, Pieces); + + <<"#"/utf8, Rest@3/binary>> -> + Scheme@2 = binary:part(Original, 0, Size), + Pieces@3 = {uri, + {some, string:lowercase(Scheme@2)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_fragment(Rest@3, Pieces@3); + + <<":"/utf8, _/binary>> when Size =:= 0 -> + {error, nil}; + + <<":"/utf8, Rest@4/binary>> -> + Scheme@3 = binary:part(Original, 0, Size), + Pieces@4 = {uri, + {some, string:lowercase(Scheme@3)}, + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + erlang:element(6, Pieces), + erlang:element(7, Pieces), + erlang:element(8, Pieces)}, + parse_authority_with_slashes(Rest@4, Pieces@4); + + <<""/utf8>> -> + {ok, + {uri, + erlang:element(2, Pieces), + erlang:element(3, Pieces), + erlang:element(4, Pieces), + erlang:element(5, Pieces), + Original, + erlang:element(7, Pieces), + erlang:element(8, Pieces)}}; + + _ -> + {_, Rest@5} = gleam_stdlib:string_pop_codeunit(Uri_string), + parse_scheme_loop(Original, Rest@5, Pieces, Size + 1) + end. + +-file("src/gleam/uri.gleam", 536). +?DOC( + " Parses an URL-encoded query string into a list of key value pairs.\n" + " Returns an error for invalid encoding.\n" + "\n" + " The opposite operation is `uri.query_to_string`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse_query(\"a=1&b=2\") == Ok([#(\"a\", \"1\"), #(\"b\", \"2\")])\n" + " ```\n" +). +-spec parse_query(binary()) -> {ok, list({binary(), binary()})} | {error, nil}. +parse_query(Query) -> + gleam_stdlib:parse_query(Query). + +-file("src/gleam/uri.gleam", 576). +?DOC( + " Encodes a string into a percent encoded representation.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert percent_encode(\"100% great\") == \"100%25%20great\"\n" + " ```\n" +). +-spec percent_encode(binary()) -> binary(). +percent_encode(Value) -> + gleam_stdlib:percent_encode(Value). + +-file("src/gleam/uri.gleam", 561). +-spec percent_encode_query(binary()) -> binary(). +percent_encode_query(Part) -> + _pipe = gleam_stdlib:percent_encode(Part), + gleam@string:replace(_pipe, <<"+"/utf8>>, <<"%2B"/utf8>>). + +-file("src/gleam/uri.gleam", 556). +-spec query_pair({binary(), binary()}) -> gleam@string_tree:string_tree(). +query_pair(Pair) -> + _pipe = [percent_encode_query(erlang:element(1, Pair)), + <<"="/utf8>>, + percent_encode_query(erlang:element(2, Pair))], + gleam_stdlib:identity(_pipe). + +-file("src/gleam/uri.gleam", 548). +?DOC( + " Encodes a list of key value pairs as a URI query string.\n" + "\n" + " The opposite operation is `uri.parse_query`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert query_to_string([#(\"a\", \"1\"), #(\"b\", \"2\")]) == \"a=1&b=2\"\n" + " ```\n" +). +-spec query_to_string(list({binary(), binary()})) -> binary(). +query_to_string(Query) -> + _pipe = Query, + _pipe@1 = gleam@list:map(_pipe, fun query_pair/1), + _pipe@2 = gleam@list:intersperse( + _pipe@1, + gleam_stdlib:identity(<<"&"/utf8>>) + ), + _pipe@3 = gleam_stdlib:identity(_pipe@2), + unicode:characters_to_binary(_pipe@3). + +-file("src/gleam/uri.gleam", 588). +?DOC( + " Decodes a percent encoded string.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert percent_decode(\"100%25%20great+fun\") == Ok(\"100% great+fun\")\n" + " ```\n" +). +-spec percent_decode(binary()) -> {ok, binary()} | {error, nil}. +percent_decode(Value) -> + gleam_stdlib:percent_decode(Value). + +-file("src/gleam/uri.gleam", 609). +-spec remove_dot_segments_loop(list(binary()), list(binary())) -> list(binary()). +remove_dot_segments_loop(Input, Accumulator) -> + case Input of + [] -> + lists:reverse(Accumulator); + + [Segment | Rest] -> + Accumulator@5 = case {Segment, Accumulator} of + {<<""/utf8>>, Accumulator@1} -> + Accumulator@1; + + {<<"."/utf8>>, Accumulator@2} -> + Accumulator@2; + + {<<".."/utf8>>, []} -> + []; + + {<<".."/utf8>>, [_ | Accumulator@3]} -> + Accumulator@3; + + {Segment@1, Accumulator@4} -> + [Segment@1 | Accumulator@4] + end, + remove_dot_segments_loop(Rest, Accumulator@5) + end. + +-file("src/gleam/uri.gleam", 605). +-spec remove_dot_segments(list(binary())) -> list(binary()). +remove_dot_segments(Input) -> + remove_dot_segments_loop(Input, []). + +-file("src/gleam/uri.gleam", 601). +?DOC( + " Splits the path section of a URI into its constituent segments.\n" + "\n" + " Removes empty segments and resolves dot-segments as specified in\n" + " [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert path_segments(\"/users/1\") == [\"users\" ,\"1\"]\n" + " ```\n" +). +-spec path_segments(binary()) -> list(binary()). +path_segments(Path) -> + remove_dot_segments(gleam@string:split(Path, <<"/"/utf8>>)). + +-file("src/gleam/uri.gleam", 639). +?DOC( + " Encodes a `Uri` value as a URI string.\n" + "\n" + " The opposite operation is `uri.parse`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let uri = Uri(..empty, scheme: Some(\"https\"), host: Some(\"example.com\"))\n" + " assert to_string(uri) == \"https://example.com\"\n" + " ```\n" +). +-spec to_string(uri()) -> binary(). +to_string(Uri) -> + Parts = case erlang:element(8, Uri) of + {some, Fragment} -> + [<<"#"/utf8>>, Fragment]; + + none -> + [] + end, + Parts@1 = case erlang:element(7, Uri) of + {some, Query} -> + [<<"?"/utf8>>, Query | Parts]; + + none -> + Parts + end, + Parts@2 = [erlang:element(6, Uri) | Parts@1], + Parts@3 = case {erlang:element(4, Uri), + gleam_stdlib:string_starts_with(erlang:element(6, Uri), <<"/"/utf8>>)} of + {{some, Host}, false} when Host =/= <<""/utf8>> -> + [<<"/"/utf8>> | Parts@2]; + + {_, _} -> + Parts@2 + end, + Parts@4 = case {erlang:element(4, Uri), erlang:element(5, Uri)} of + {{some, _}, {some, Port}} -> + [<<":"/utf8>>, erlang:integer_to_binary(Port) | Parts@3]; + + {_, _} -> + Parts@3 + end, + Parts@5 = case {erlang:element(2, Uri), + erlang:element(3, Uri), + erlang:element(4, Uri)} of + {{some, S}, {some, U}, {some, H}} -> + [S, <<"://"/utf8>>, U, <<"@"/utf8>>, H | Parts@4]; + + {{some, S@1}, none, {some, H@1}} -> + [S@1, <<"://"/utf8>>, H@1 | Parts@4]; + + {{some, S@2}, {some, _}, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {{some, S@2}, none, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {none, none, {some, H@2}} -> + [<<"//"/utf8>>, H@2 | Parts@4]; + + {_, _, _} -> + Parts@4 + end, + erlang:list_to_binary(Parts@5). + +-file("src/gleam/uri.gleam", 682). +?DOC( + " Fetches the origin of a URI.\n" + "\n" + " Returns the origin of a uri as defined in\n" + " [RFC 6454](https://tools.ietf.org/html/rfc6454)\n" + "\n" + " The supported URI schemes are `http` and `https`.\n" + " URLs without a scheme will return `Error`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " let assert Ok(uri) = parse(\"https://example.com/path?foo#bar\")\n" + " assert origin(uri) == Ok(\"https://example.com\")\n" + " ```\n" +). +-spec origin(uri()) -> {ok, binary()} | {error, nil}. +origin(Uri) -> + {uri, Scheme, _, Host, Port, _, _, _} = Uri, + case {Host, Scheme} of + {{some, H}, {some, <<"https"/utf8>>}} when Port =:= {some, 443} -> + {ok, erlang:list_to_binary([<<"https://"/utf8>>, H])}; + + {{some, H@1}, {some, <<"http"/utf8>>}} when Port =:= {some, 80} -> + {ok, erlang:list_to_binary([<<"http://"/utf8>>, H@1])}; + + {{some, H@2}, {some, S}} when (S =:= <<"http"/utf8>>) orelse (S =:= <<"https"/utf8>>) -> + case Port of + {some, P} -> + {ok, + erlang:list_to_binary( + [S, + <<"://"/utf8>>, + H@2, + <<":"/utf8>>, + erlang:integer_to_binary(P)] + )}; + + none -> + {ok, erlang:list_to_binary([S, <<"://"/utf8>>, H@2])} + end; + + {_, _} -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 763). +-spec drop_last(list(DDN)) -> list(DDN). +drop_last(Elements) -> + gleam@list:take(Elements, erlang:length(Elements) - 1). + +-file("src/gleam/uri.gleam", 767). +-spec join_segments(list(binary())) -> binary(). +join_segments(Segments) -> + gleam@string:join([<<""/utf8>> | Segments], <<"/"/utf8>>). + +-file("src/gleam/uri.gleam", 705). +?DOC( + " Resolves a URI with respect to the given base URI.\n" + "\n" + " The base URI must be an absolute URI or this function will return an error.\n" + " The algorithm for merging URIs is described in\n" + " [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2).\n" +). +-spec merge(uri(), uri()) -> {ok, uri()} | {error, nil}. +merge(Base, Relative) -> + case Base of + {uri, {some, _}, _, {some, _}, _, _, _, _} -> + case Relative of + {uri, _, _, {some, _}, _, _, _, _} -> + Path = begin + _pipe = erlang:element(6, Relative), + _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), + _pipe@2 = remove_dot_segments(_pipe@1), + join_segments(_pipe@2) + end, + Resolved = {uri, + gleam@option:'or'( + erlang:element(2, Relative), + erlang:element(2, Base) + ), + none, + erlang:element(4, Relative), + gleam@option:'or'( + erlang:element(5, Relative), + erlang:element(5, Base) + ), + Path, + erlang:element(7, Relative), + erlang:element(8, Relative)}, + {ok, Resolved}; + + _ -> + {New_path, New_query} = case erlang:element(6, Relative) of + <<""/utf8>> -> + {erlang:element(6, Base), + gleam@option:'or'( + erlang:element(7, Relative), + erlang:element(7, Base) + )}; + + _ -> + Path_segments = case gleam_stdlib:string_starts_with( + erlang:element(6, Relative), + <<"/"/utf8>> + ) of + true -> + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ); + + false -> + _pipe@3 = erlang:element(6, Base), + _pipe@4 = gleam@string:split( + _pipe@3, + <<"/"/utf8>> + ), + _pipe@5 = drop_last(_pipe@4), + lists:append( + _pipe@5, + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ) + ) + end, + Path@1 = begin + _pipe@6 = Path_segments, + _pipe@7 = remove_dot_segments(_pipe@6), + join_segments(_pipe@7) + end, + {Path@1, erlang:element(7, Relative)} + end, + Resolved@1 = {uri, + erlang:element(2, Base), + none, + erlang:element(4, Base), + erlang:element(5, Base), + New_path, + New_query, + erlang:element(8, Relative)}, + {ok, Resolved@1} + end; + + _ -> + {error, nil} + end. + +-file("src/gleam/uri.gleam", 81). +?DOC( + " Parses a compliant URI string into the `Uri` type.\n" + " If the string is not a valid URI string then an error is returned.\n" + "\n" + " The opposite operation is `uri.to_string`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert parse(\"https://example.com:1234/a/b?query=true#fragment\")\n" + " == Ok(\n" + " Uri(\n" + " scheme: Some(\"https\"),\n" + " userinfo: None,\n" + " host: Some(\"example.com\"),\n" + " port: Some(1234),\n" + " path: \"/a/b\",\n" + " query: Some(\"query=true\"),\n" + " fragment: Some(\"fragment\")\n" + " )\n" + " )\n" + " ```\n" +). +-spec parse(binary()) -> {ok, uri()} | {error, nil}. +parse(Uri_string) -> + gleam_stdlib:uri_parse(Uri_string). diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.app.src b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.app.src new file mode 100644 index 0000000..e312da4 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.app.src @@ -0,0 +1,31 @@ +{application, gleam_stdlib, [ + {vsn, "1.0.0"}, + {applications, []}, + {description, "A standard library for the Gleam programming language"}, + {modules, [gleam@bit_array, + gleam@bool, + gleam@bytes_tree, + gleam@dict, + gleam@dynamic, + gleam@dynamic@decode, + gleam@float, + gleam@function, + gleam@int, + gleam@io, + gleam@list, + gleam@option, + gleam@order, + gleam@pair, + gleam@result, + gleam@set, + gleam@string, + gleam@string_tree, + gleam@uri, + gleam_stdlib, + gleam_stdlib@@main, + gleam_stdlib_test_ffi, + gleeunit_ffi, + gleeunit_gleam_panic_ffi, + gleeunit_progress]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.erl b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.erl new file mode 100644 index 0000000..92c91cd --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.erl @@ -0,0 +1,551 @@ +-module(gleam_stdlib). + +-export([ + map_get/2, iodata_append/2, identity/1, parse_int/1, parse_float/1, + less_than/2, string_pop_grapheme/1, string_pop_codeunit/1, + string_starts_with/2, wrap_list/1, string_ends_with/2, string_pad/4, + uri_parse/1, bit_array_slice/3, percent_encode/1, percent_decode/1, + base64_decode/1, parse_query/1, bit_array_concat/1, + base64_encode/2, tuple_get/2, classify_dynamic/1, print/1, + println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, + int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, + crop_string/2, base16_encode/1, base16_decode/1, string_replace/3, slice/3, + bit_array_to_int_and_size/1, bit_array_pad_to_bytes/1, index/2, list/5, + dict/1, int/1, float/1, bit_array/1, is_null/1, string_remove_prefix/2, + string_remove_suffix/2 +]). + +%% Taken from OTP's uri_string module +-define(DEC2HEX(X), + if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0; + ((X) >= 10) andalso ((X) =< 15) -> (X) + $A - 10 + end). + +%% Taken from OTP's uri_string module +-define(HEX2DEC(X), + if ((X) >= $0) andalso ((X) =< $9) -> (X) - $0; + ((X) >= $A) andalso ((X) =< $F) -> (X) - $A + 10; + ((X) >= $a) andalso ((X) =< $f) -> (X) - $a + 10 + end). + +-define(is_lowercase_char(X), + (X > 96 andalso X < 123)). +-define(is_underscore_char(X), + (X == 95)). +-define(is_digit_char(X), + (X > 47 andalso X < 58)). +-define(is_ascii_character(X), + (erlang:is_integer(X) andalso X >= 32 andalso X =< 126)). + +uppercase(X) -> X - 32. + +map_get(Map, Key) -> + case maps:find(Key, Map) of + error -> {error, nil}; + OkFound -> OkFound + end. + +iodata_append(Iodata, String) -> [Iodata, String]. + +identity(X) -> X. + +classify_dynamic(nil) -> <<"Nil">>; +classify_dynamic(null) -> <<"Nil">>; +classify_dynamic(undefined) -> <<"Nil">>; +classify_dynamic(X) when is_boolean(X) -> <<"Bool">>; +classify_dynamic(X) when is_atom(X) -> <<"Atom">>; +classify_dynamic(X) when is_binary(X) -> <<"String">>; +classify_dynamic(X) when is_bitstring(X) -> <<"BitArray">>; +classify_dynamic(X) when is_integer(X) -> <<"Int">>; +classify_dynamic(X) when is_float(X) -> <<"Float">>; +classify_dynamic(X) when is_list(X) -> <<"List">>; +classify_dynamic(X) when is_map(X) -> <<"Dict">>; +classify_dynamic(X) when is_tuple(X) -> <<"Array">>; +classify_dynamic(X) when is_reference(X) -> <<"Reference">>; +classify_dynamic(X) when is_pid(X) -> <<"Pid">>; +classify_dynamic(X) when is_port(X) -> <<"Port">>; +classify_dynamic(X) when is_function(X) -> <<"Function">>; +classify_dynamic(_) -> <<"Unknown">>. + +tuple_get(_tup, Index) when Index < 0 -> {error, nil}; +tuple_get(Data, Index) when Index >= tuple_size(Data) -> {error, nil}; +tuple_get(Data, Index) -> {ok, element(Index + 1, Data)}. + +int_from_base_string(String, Base) -> + case catch binary_to_integer(String, Base) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_int(String) -> + case catch binary_to_integer(String) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_float(String) -> + case catch binary_to_float(String) of + Float when is_float(Float) -> {ok, Float}; + _ -> {error, nil} + end. + +less_than(Lhs, Rhs) -> + Lhs < Rhs. + +string_starts_with(_, <<>>) -> true; +string_starts_with(String, Prefix) when byte_size(Prefix) > byte_size(String) -> false; +string_starts_with(String, Prefix) -> + PrefixSize = byte_size(Prefix), + Prefix == binary_part(String, 0, PrefixSize). + +string_ends_with(_, <<>>) -> true; +string_ends_with(String, Suffix) when byte_size(Suffix) > byte_size(String) -> false; +string_ends_with(String, Suffix) -> + SuffixSize = byte_size(Suffix), + Suffix == binary_part(String, byte_size(String) - SuffixSize, SuffixSize). + +string_pad(String, Length, Dir, PadString) -> + Chars = string:pad(String, Length, Dir, binary_to_list(PadString)), + case unicode:characters_to_binary(Chars) of + Bin when is_binary(Bin) -> Bin; + Error -> erlang:error({gleam_error, {string_invalid_utf8, Error}}) + end. + +string_pop_grapheme(String) -> + case string:next_grapheme(String) of + [ Next | Rest ] when is_binary(Rest) -> + {ok, {unicode:characters_to_binary([Next]), Rest}}; + + [ Next | Rest ] -> + {ok, {unicode:characters_to_binary([Next]), unicode:characters_to_binary(Rest)}}; + + _ -> {error, nil} + end. + +string_pop_codeunit(<>) -> {Cp, Rest}; +string_pop_codeunit(Binary) -> {0, Binary}. + +bit_array_pad_to_bytes(Bin) -> + case erlang:bit_size(Bin) rem 8 of + 0 -> Bin; + TrailingBits -> + PaddingBits = 8 - TrailingBits, + <> + end. + +bit_array_concat(BitArrays) -> + list_to_bitstring(BitArrays). + +-if(?OTP_RELEASE >= 26). +base64_encode(Bin, Padding) -> + PaddedBin = bit_array_pad_to_bytes(Bin), + base64:encode(PaddedBin, #{padding => Padding}). +-else. +base64_encode(_Bin, _Padding) -> + erlang:error(<<"Erlang OTP/26 or higher is required to use base64:encode">>). +-endif. + +bit_array_slice(Bin, Pos, Len) -> + try {ok, binary:part(Bin, Pos, Len)} + catch error:badarg -> {error, nil} + end. + +base64_decode(S) -> + try {ok, base64:decode(S)} + catch error:_ -> {error, nil} + end. + +wrap_list(X) when is_list(X) -> X; +wrap_list(X) -> [X]. + +parse_query(Query) -> + case uri_string:dissect_query(Query) of + {error, _, _} -> {error, nil}; + Pairs -> + Pairs1 = lists:map(fun + ({K, true}) -> {K, <<"">>}; + (Pair) -> Pair + end, Pairs), + {ok, Pairs1} + end. + +percent_encode(B) -> percent_encode(B, <<>>). +percent_encode(<<>>, Acc) -> + Acc; +percent_encode(<>, Acc) -> + case percent_ok(H) of + true -> + percent_encode(T, <>); + false -> + <> = <>, + percent_encode(T, <>) + end. + +percent_decode(Cs) -> percent_decode(Cs, <<>>). +percent_decode(<<$%, C0, C1, Cs/binary>>, Acc) -> + case is_hex_digit(C0) andalso is_hex_digit(C1) of + true -> + B = ?HEX2DEC(C0)*16+?HEX2DEC(C1), + percent_decode(Cs, <>); + false -> + {error, nil} + end; +percent_decode(<>, Acc) -> + percent_decode(Cs, <>); +percent_decode(<<>>, Acc) -> + check_utf8(Acc). + +percent_ok($!) -> true; +percent_ok($$) -> true; +percent_ok($') -> true; +percent_ok($() -> true; +percent_ok($)) -> true; +percent_ok($*) -> true; +percent_ok($+) -> true; +percent_ok($-) -> true; +percent_ok($.) -> true; +percent_ok($_) -> true; +percent_ok($~) -> true; +percent_ok(C) when $0 =< C, C =< $9 -> true; +percent_ok(C) when $A =< C, C =< $Z -> true; +percent_ok(C) when $a =< C, C =< $z -> true; +percent_ok(_) -> false. + +is_hex_digit(C) -> + ($0 =< C andalso C =< $9) orelse ($a =< C andalso C =< $f) orelse ($A =< C andalso C =< $F). + +check_utf8(Cs) -> + case unicode:characters_to_list(Cs) of + {incomplete, _, _} -> {error, nil}; + {error, _, _} -> {error, nil}; + _ -> {ok, Cs} + end. + +uri_parse(String) -> + case uri_string:parse(String) of + {error, _, _} -> {error, nil}; + Uri -> + Port = + try maps:get(port, Uri) of + undefined -> none; + Value -> {some, Value} + catch _:_ -> none + end, + {ok, {uri, + maps_get_optional_lowercase(Uri, scheme), + maps_get_optional(Uri, userinfo), + maps_get_optional(Uri, host), + Port, + maps_get_or(Uri, path, <<>>), + maps_get_optional(Uri, query), + maps_get_optional(Uri, fragment) + }} + end. + +maps_get_optional_lowercase(Map, Key) -> + try {some, string:lowercase(maps:get(Key, Map))} + catch _:_ -> none + end. + +maps_get_optional(Map, Key) -> + try {some, maps:get(Key, Map)} + catch _:_ -> none + end. + +maps_get_or(Map, Key, Default) -> + try maps:get(Key, Map) + catch _:_ -> Default + end. + +print(String) -> + io:put_chars(String), + nil. + +println(String) -> + io:put_chars([String, $\n]), + nil. + +print_error(String) -> + io:put_chars(standard_error, String), + nil. + +println_error(String) -> + io:put_chars(standard_error, [String, $\n]), + nil. + +inspect(true) -> + "True"; +inspect(false) -> + "False"; +inspect(nil) -> + "Nil"; +inspect(Data) when is_map(Data) -> + Fields = [ + [<<"#(">>, inspect(Key), <<", ">>, inspect(Value), <<")">>] + || {Key, Value} <- maps:to_list(Data) + ], + ["dict.from_list([", lists:join(", ", Fields), "])"]; +inspect(Atom) when is_atom(Atom) -> + erlang:element(2, inspect_atom(Atom)); +inspect(Any) when is_integer(Any) -> + erlang:integer_to_list(Any); +inspect(Any) when is_float(Any) -> + io_lib_format:fwrite_g(Any); +inspect(Binary) when is_binary(Binary) -> + case inspect_maybe_utf8_string(Binary, <<>>) of + {ok, InspectedUtf8String} -> InspectedUtf8String; + {error, not_a_utf8_string} -> + Segments = [erlang:integer_to_list(X) || <> <= Binary], + ["<<", lists:join(", ", Segments), ">>"] + end; +inspect(Bits) when is_bitstring(Bits) -> + inspect_bit_array(Bits); +inspect(List) when is_list(List) -> + case inspect_list(List, true) of + {charlist, _} -> ["charlist.from_string(\"", list_to_binary(List), "\")"]; + {proper, Elements} -> ["[", Elements, "]"]; + {improper, Elements} -> ["//erl([", Elements, "])"] + end; +inspect(Any) when is_tuple(Any) % Record constructors + andalso is_atom(element(1, Any)) + andalso element(1, Any) =/= false + andalso element(1, Any) =/= true + andalso element(1, Any) =/= nil +-> + [Atom | ArgsList] = erlang:tuple_to_list(Any), + InspectedArgs = lists:map(fun inspect/1, ArgsList), + case inspect_atom(Atom) of + {gleam_atom, GleamAtom} -> + Args = lists:join(<<", ">>, InspectedArgs), + [GleamAtom, "(", Args, ")"]; + {erlang_atom, ErlangAtom} -> + Args = lists:join(<<", ">>, [ErlangAtom | InspectedArgs]), + ["#(", Args, ")"] + end; +inspect(Tuple) when is_tuple(Tuple) -> + Elements = lists:map(fun inspect/1, erlang:tuple_to_list(Tuple)), + ["#(", lists:join(", ", Elements), ")"]; +inspect(Any) when is_function(Any) -> + {arity, Arity} = erlang:fun_info(Any, arity), + ArgsAsciiCodes = lists:seq($a, $a + Arity - 1), + Args = lists:join(<<", ">>, + lists:map(fun(Arg) -> <> end, ArgsAsciiCodes) + ), + ["//fn(", Args, ") { ... }"]; +inspect(Any) -> + ["//erl(", io_lib:format("~p", [Any]), ")"]. + +inspect_atom(Atom) -> + Binary = erlang:atom_to_binary(Atom), + case inspect_maybe_gleam_atom(Binary, none, <<>>) of + {ok, Inspected} -> {gleam_atom, Inspected}; + {error, _} -> {erlang_atom, ["atom.create(\"", Binary, "\")"]} + end. + +inspect_maybe_gleam_atom(<<>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, none, _) when ?is_digit_char(First) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_">>, _PrevChar, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, $_, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, _PrevChar, _Acc) + when not (?is_lowercase_char(First) orelse ?is_underscore_char(First) orelse ?is_digit_char(First)) -> + {error, nil}; +inspect_maybe_gleam_atom(<>, none, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<<"_", Rest/binary>>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, $_, Acc); +inspect_maybe_gleam_atom(<>, $_, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <>); +inspect_maybe_gleam_atom(<<>>, _PrevChar, Acc) -> + {ok, Acc}; +inspect_maybe_gleam_atom(A, B, C) -> + erlang:display({A, B, C}), + throw({gleam_error, A, B, C}). + +inspect_list([], _) -> + {proper, []}; +inspect_list([First], true) when ?is_ascii_character(First) -> + {charlist, nil}; +inspect_list([First], _) -> + {proper, [inspect(First)]}; +inspect_list([First | Rest], ValidCharlist) when is_list(Rest) -> + StillValidCharlist = ValidCharlist andalso ?is_ascii_character(First), + {Kind, Inspected} = inspect_list(Rest, StillValidCharlist), + {Kind, [inspect(First), <<", ">> | Inspected]}; +inspect_list([First | ImproperTail], _) -> + {improper, [inspect(First), <<" | ">>, inspect(ImproperTail)]}. + +inspect_bit_array(Bits) -> + Text = inspect_bit_array(Bits, <<"<<">>), + <>">>. + +inspect_bit_array(<<>>, Acc) -> + Acc; +inspect_bit_array(<>, Acc) -> + inspect_bit_array(Rest, append_segment(Acc, erlang:integer_to_binary(X))); +inspect_bit_array(Rest, Acc) -> + Size = bit_size(Rest), + <> = Rest, + X1 = erlang:integer_to_binary(X), + Size1 = erlang:integer_to_binary(Size), + Segment = <>, + inspect_bit_array(<<>>, append_segment(Acc, Segment)). + +bit_array_to_int_and_size(A) -> + Size = bit_size(A), + <> = A, + {A1, Size}. + +append_segment(<<"<<">>, Segment) -> + <<"<<", Segment/binary>>; +append_segment(Acc, Segment) -> + <>. + + +inspect_maybe_utf8_string(Binary, Acc) -> + case Binary of + <<>> -> {ok, <<$", Acc/binary, $">>}; + <> -> + Escaped = case First of + $" -> <<$\\, $">>; + $\\ -> <<$\\, $\\>>; + $\r -> <<$\\, $r>>; + $\n -> <<$\\, $n>>; + $\t -> <<$\\, $t>>; + $\f -> <<$\\, $f>>; + X when X > 126, X < 160 -> convert_to_u(X); + X when X < 32 -> convert_to_u(X); + Other -> <> + end, + inspect_maybe_utf8_string(Rest, <>); + _ -> {error, not_a_utf8_string} + end. + +convert_to_u(Code) -> + list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])). + +float_to_string(Float) when is_float(Float) -> + erlang:iolist_to_binary(io_lib_format:fwrite_g(Float)). + +utf_codepoint_list_to_string(List) -> + case unicode:characters_to_binary(List) of + {error, _} -> erlang:error({gleam_error, {string_invalid_utf8, List}}); + Binary -> Binary + end. + +crop_string(String, Prefix) -> + case string:find(String, Prefix) of + nomatch -> String; + New -> New + end. + +contains_string(String, Substring) -> + is_bitstring(string:find(String, Substring)). + +base16_encode(Bin) -> + PaddedBin = bit_array_pad_to_bytes(Bin), + binary:encode_hex(PaddedBin). + +base16_decode(String) -> + try + {ok, binary:decode_hex(String)} + catch + _:_ -> {error, nil} + end. + +string_replace(String, Pattern, Replacement) -> + string:replace(String, Pattern, Replacement, all). + +slice(String, Index, Length) -> + case string:slice(String, Index, Length) of + X when is_binary(X) -> X; + X when is_list(X) -> unicode:characters_to_binary(X) + end. + +index([X | _], 0) -> + {ok, {some, X}}; +index([_, X | _], 1) -> + {ok, {some, X}}; +index([_, _, X | _], 2) -> + {ok, {some, X}}; +index([_, _, _, X | _], 3) -> + {ok, {some, X}}; +index([_, _, _, _, X | _], 4) -> + {ok, {some, X}}; +index([_, _, _, _, _, X | _], 5) -> + {ok, {some, X}}; +index([_, _, _, _, _, _, X | _], 6) -> + {ok, {some, X}}; +index([_, _, _, _, _, _, _, X | _], 7) -> + {ok, {some, X}}; +index(Tuple, Index) when is_tuple(Tuple) andalso is_integer(Index) -> + {ok, try + {some, element(Index + 1, Tuple)} + catch _:_ -> + none + end}; +index(Map, Key) when is_map(Map) -> + {ok, try + {some, maps:get(Key, Map)} + catch _:_ -> + none + end}; +index(_, Index) when is_integer(Index) -> + {error, <<"Indexable">>}; +index(_, _) -> + {error, <<"Dict">>}. + +list(T, A, B, C, D) when is_tuple(T) -> + list(tuple_to_list(T), A, B, C, D); +list([], _, _, _, Acc) -> + {lists:reverse(Acc), []}; +list([X | Xs], Decode, PushPath, Index, Acc) -> + {Out, Errors} = Decode(X), + case Errors of + [] -> list(Xs, Decode, PushPath, Index + 1, [Out | Acc]); + _ -> PushPath({[], Errors}, integer_to_binary(Index)) + end; +list(Unexpected, _, _, _, []) -> + Found = gleam@dynamic:classify(Unexpected), + Error = {decode_error, <<"List"/utf8>>, Found, []}, + {[], [Error]}; +list(_, _, _, _, Acc) -> + {lists:reverse(Acc), []}. + +dict(#{} = Data) -> {ok, Data}; +dict(_) -> {error, nil}. + +int(I) when is_integer(I) -> {ok, I}; +int(_) -> {error, 0}. + +float(F) when is_float(F) -> {ok, F}; +float(_) -> {error, 0.0}. + +bit_array(B) when is_bitstring(B) -> {ok, B}; +bit_array(_) -> {error, <<>>}. + +is_null(X) -> + X =:= undefined orelse X =:= null orelse X =:= nil. + +string_remove_prefix(String, Prefix) -> + PrefixSize = byte_size(Prefix), + case String of + <> -> Suffix; + _ -> String + end. + +string_remove_suffix(String, Suffix) -> + StringSize = byte_size(String), + SuffixSize = byte_size(Suffix), + Offset = StringSize - SuffixSize, + case String of + <> -> Prefix; + _ -> String + end. diff --git a/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.mjs b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.mjs new file mode 100644 index 0000000..ebc5213 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_stdlib/src/gleam_stdlib.mjs @@ -0,0 +1,1150 @@ +import { + BitArray, + List$Empty, + List$NonEmpty, + List$isEmpty, + List$isNonEmpty, + Result$Ok, + Result$Error, + Result$isOk, + Result$isError, + UtfCodepoint, + stringBits, + toBitArray, + bitArraySlice, + CustomType, +} from "./gleam.mjs"; +import { Some, None } from "./gleam/option.mjs"; +import { + default as Dict, + fold as dict_fold, + get as dict_get, + from as dict_from_iterable, +} from "./dict.mjs"; +import { classify } from "./gleam/dynamic.mjs"; +import { DecodeError$DecodeError } from "./gleam/dynamic/decode.mjs"; + +const Nil = undefined; + +export function identity(x) { + return x; +} + +export function parse_int(value) { + if (/^[-+]?(\d+)$/.test(value)) { + return Result$Ok(parseInt(value)); + } else { + return Result$Error(Nil); + } +} + +export function parse_float(value) { + if (/^[-+]?(\d+)\.(\d+)([eE][-+]?\d+)?$/.test(value)) { + return Result$Ok(parseFloat(value)); + } else { + return Result$Error(Nil); + } +} + +export function to_string(term) { + return term.toString(); +} + +export function int_to_base_string(int, base) { + return int.toString(base).toUpperCase(); +} + +const int_base_patterns = { + 2: /[^0-1]/, + 3: /[^0-2]/, + 4: /[^0-3]/, + 5: /[^0-4]/, + 6: /[^0-5]/, + 7: /[^0-6]/, + 8: /[^0-7]/, + 9: /[^0-8]/, + 10: /[^0-9]/, + 11: /[^0-9a]/, + 12: /[^0-9a-b]/, + 13: /[^0-9a-c]/, + 14: /[^0-9a-d]/, + 15: /[^0-9a-e]/, + 16: /[^0-9a-f]/, + 17: /[^0-9a-g]/, + 18: /[^0-9a-h]/, + 19: /[^0-9a-i]/, + 20: /[^0-9a-j]/, + 21: /[^0-9a-k]/, + 22: /[^0-9a-l]/, + 23: /[^0-9a-m]/, + 24: /[^0-9a-n]/, + 25: /[^0-9a-o]/, + 26: /[^0-9a-p]/, + 27: /[^0-9a-q]/, + 28: /[^0-9a-r]/, + 29: /[^0-9a-s]/, + 30: /[^0-9a-t]/, + 31: /[^0-9a-u]/, + 32: /[^0-9a-v]/, + 33: /[^0-9a-w]/, + 34: /[^0-9a-x]/, + 35: /[^0-9a-y]/, + 36: /[^0-9a-z]/, +}; + +export function int_from_base_string(string, base) { + if (int_base_patterns[base].test(string.replace(/^-/, "").toLowerCase())) { + return Result$Error(Nil); + } + + const result = parseInt(string, base); + + if (isNaN(result)) { + return Result$Error(Nil); + } + + return Result$Ok(result); +} + +export function string_replace(string, target, substitute) { + return string.replaceAll(target, substitute); +} + +export function string_reverse(string) { + return [...string].reverse().join(""); +} + +export function string_length(string) { + if (string === "") { + return 0; + } + const iterator = graphemes_iterator(string); + if (iterator) { + let i = 0; + for (const _ of iterator) { + i++; + } + return i; + } else { + return string.match(/./gsu).length; + } +} + +export function graphemes(string) { + const iterator = graphemes_iterator(string); + if (iterator) { + return arrayToList(Array.from(iterator).map((item) => item.segment)); + } else { + return arrayToList(string.match(/./gsu)); + } +} + +let segmenter = undefined; + +function graphemes_iterator(string) { + if (globalThis.Intl && Intl.Segmenter) { + segmenter ||= new Intl.Segmenter(); + return segmenter.segment(string)[Symbol.iterator](); + } +} + +export function pop_grapheme(string) { + let first; + const iterator = graphemes_iterator(string); + if (iterator) { + first = iterator.next().value?.segment; + } else { + first = string.match(/./su)?.[0]; + } + if (first) { + return Result$Ok([first, string.slice(first.length)]); + } else { + return Result$Error(Nil); + } +} + +export function pop_codeunit(str) { + return [str.charCodeAt(0) | 0, str.slice(1)]; +} + +export function lowercase(string) { + return string.toLowerCase(); +} + +export function uppercase(string) { + return string.toUpperCase(); +} + +export function less_than(a, b) { + return a < b; +} + +export function add(a, b) { + return a + b; +} + +export function split(xs, pattern) { + return arrayToList(xs.split(pattern)); +} + +export function concat(xs) { + let result = ""; + for (const x of xs) { + result = result + x; + } + return result; +} + +export function length(data) { + return data.length; +} + +export function string_byte_slice(string, index, length) { + return string.slice(index, index + length); +} + +export function string_grapheme_slice(string, idx, len) { + if (len <= 0 || idx >= string.length) { + return ""; + } + + const iterator = graphemes_iterator(string); + if (iterator) { + while (idx-- > 0) { + iterator.next(); + } + + let result = ""; + + while (len-- > 0) { + const v = iterator.next().value; + if (v === undefined) { + break; + } + + result += v.segment; + } + + return result; + } else { + return string + .match(/./gsu) + .slice(idx, idx + len) + .join(""); + } +} + +export function string_codeunit_slice(str, from, length) { + return str.slice(from, from + length); +} +export function crop_string(string, substring) { + return string.substring(string.indexOf(substring)); +} + +export function contains_string(haystack, needle) { + return haystack.indexOf(needle) >= 0; +} + +export function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} + +export function ends_with(haystack, needle) { + return haystack.endsWith(needle); +} + +export function split_once(haystack, needle) { + const index = haystack.indexOf(needle); + if (index >= 0) { + const before = haystack.slice(0, index); + const after = haystack.slice(index + needle.length); + return Result$Ok([before, after]); + } else { + return Result$Error(Nil); + } +} + +const unicode_whitespaces = [ + "\u0020", // Space + "\u0009", // Horizontal tab + "\u000A", // Line feed + "\u000B", // Vertical tab + "\u000C", // Form feed + "\u000D", // Carriage return + "\u0085", // Next line + "\u2028", // Line separator + "\u2029", // Paragraph separator +].join(""); + +const trim_start_regex = /* @__PURE__ */ new RegExp( + `^[${unicode_whitespaces}]*`, +); +const trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); + +export function trim_start(string) { + return string.replace(trim_start_regex, ""); +} + +export function trim_end(string) { + return string.replace(trim_end_regex, ""); +} + +export function bit_array_from_string(string) { + return toBitArray([stringBits(string)]); +} + +export function bit_array_bit_size(bit_array) { + return bit_array.bitSize; +} + +export function bit_array_byte_size(bit_array) { + return bit_array.byteSize; +} + +export function bit_array_pad_to_bytes(bit_array) { + const trailingBitsCount = bit_array.bitSize % 8; + + // If the bit array is a whole number of bytes it can be returned unchanged + if (trailingBitsCount === 0) { + return bit_array; + } + + const finalByte = bit_array.byteAt(bit_array.byteSize - 1); + + // The required final byte has its unused trailing bits set to zero + const unusedBitsCount = 8 - trailingBitsCount; + const correctFinalByte = (finalByte >> unusedBitsCount) << unusedBitsCount; + + // If the unused bits in the final byte are already set to zero then the + // existing buffer can be re-used, avoiding a copy + if (finalByte === correctFinalByte) { + return new BitArray( + bit_array.rawBuffer, + bit_array.byteSize * 8, + bit_array.bitOffset, + ); + } + + // Copy the bit array into a new aligned buffer and set the correct final byte + const buffer = new Uint8Array(bit_array.byteSize); + for (let i = 0; i < buffer.length - 1; i++) { + buffer[i] = bit_array.byteAt(i); + } + buffer[buffer.length - 1] = correctFinalByte; + + return new BitArray(buffer); +} + +export function bit_array_concat(bit_arrays) { + return toBitArray(bit_arrays.toArray()); +} + +export function console_log(term) { + console.log(term); +} + +export function console_error(term) { + console.error(term); +} + +export function crash(message) { + throw new globalThis.Error(message); +} + +export function bit_array_to_string(bit_array) { + // If the bit array isn't a whole number of bytes then return an error + if (bit_array.bitSize % 8 !== 0) { + return Result$Error(Nil); + } + + try { + const decoder = new TextDecoder("utf-8", { fatal: true }); + + if (bit_array.bitOffset === 0) { + return Result$Ok(decoder.decode(bit_array.rawBuffer)); + } else { + // The input data isn't aligned, so copy it into a new aligned buffer so + // that TextDecoder can be used + const buffer = new Uint8Array(bit_array.byteSize); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = bit_array.byteAt(i); + } + return Result$Ok(decoder.decode(buffer)); + } + } catch { + return Result$Error(Nil); + } +} + +export function print(string) { + if (typeof process === "object" && process.stdout?.write) { + process.stdout.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stdout.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.log(string); // We're in a browser. Newlines are mandated + } +} + +export function print_error(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.error(string); // We're in a browser. Newlines are mandated + } +} + +export function print_debug(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string + "\n"); // If we're in Node.js, use `stderr` + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string + "\n")); // If we're in Deno, use `stderr` + } else { + console.log(string); // Otherwise, use `console.log` (so that it doesn't look like an error) + } +} + +export function ceiling(float) { + return Math.ceil(float); +} + +export function floor(float) { + return Math.floor(float); +} + +export function round(float) { + return Math.round(float); +} + +export function truncate(float) { + return Math.trunc(float); +} + +export function power(base, exponent) { + // It is checked in Gleam that: + // - The base is non-negative and that the exponent is not fractional. + // - The base is non-zero and the exponent is non-negative (otherwise + // the result will essentially be division by zero). + // It can thus be assumed that valid input is passed to the Math.pow + // function and a NaN or Infinity value will not be produced. + return Math.pow(base, exponent); +} + +export function random_uniform() { + const random_uniform_result = Math.random(); + // With round-to-nearest-even behavior, the ranges claimed for the functions below + // (excluding the one for Math.random() itself) aren't exact. + // If extremely large bounds are chosen (2^53 or higher), + // it's possible in extremely rare cases to calculate the usually-excluded upper bound. + // Note that as numbers in JavaScript are IEEE 754 floating point numbers + // See: + // Because of this, we just loop 'until' we get a valid result where 0.0 <= x < 1.0: + if (random_uniform_result === 1.0) { + return random_uniform(); + } + return random_uniform_result; +} + +export function bit_array_slice(bits, position, length) { + const start = Math.min(position, position + length); + const end = Math.max(position, position + length); + + if (start < 0 || end * 8 > bits.bitSize) { + return Result$Error(Nil); + } + + return Result$Ok(bitArraySlice(bits, start * 8, end * 8)); +} + +export function codepoint(int) { + return new UtfCodepoint(int); +} + +export function string_to_codepoint_integer_list(string) { + return arrayToList(Array.from(string).map((item) => item.codePointAt(0))); +} + +export function utf_codepoint_list_to_string(utf_codepoint_integer_list) { + return utf_codepoint_integer_list + .toArray() + .map((x) => String.fromCodePoint(x.value)) + .join(""); +} + +export function utf_codepoint_to_int(utf_codepoint) { + return utf_codepoint.value; +} + +function unsafe_percent_decode(string) { + return decodeURIComponent(string || ""); +} + +function unsafe_percent_decode_query(string) { + return decodeURIComponent((string || "").replace("+", " ")); +} + +export function percent_decode(string) { + try { + return Result$Ok(unsafe_percent_decode(string)); + } catch { + return Result$Error(Nil); + } +} + +export function percent_encode(string) { + return encodeURIComponent(string).replace("%2B", "+"); +} + +export function parse_query(query) { + try { + const pairs = []; + for (const section of query.split("&")) { + const [key, value] = section.split("="); + if (!key) continue; + + const decodedKey = unsafe_percent_decode_query(key); + const decodedValue = unsafe_percent_decode_query(value); + pairs.push([decodedKey, decodedValue]); + } + return Result$Ok(arrayToList(pairs)); + } catch { + return Result$Error(Nil); + } +} + +const b64EncodeLookup = [ + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47, +]; + +let b64TextDecoder; + +// Implementation based on https://github.com/mitschabaude/fast-base64/blob/main/js.js +export function base64_encode(bit_array, padding) { + b64TextDecoder ??= new TextDecoder(); + + bit_array = bit_array_pad_to_bytes(bit_array); + + const m = bit_array.byteSize; + const k = m % 3; + const n = Math.floor(m / 3) * 4 + (k && k + 1); + const N = Math.ceil(m / 3) * 4; + const encoded = new Uint8Array(N); + + for (let i = 0, j = 0; j < m; i += 4, j += 3) { + const y = + (bit_array.byteAt(j) << 16) + + (bit_array.byteAt(j + 1) << 8) + + (bit_array.byteAt(j + 2) | 0); + + encoded[i] = b64EncodeLookup[y >> 18]; + encoded[i + 1] = b64EncodeLookup[(y >> 12) & 0x3f]; + encoded[i + 2] = b64EncodeLookup[(y >> 6) & 0x3f]; + encoded[i + 3] = b64EncodeLookup[y & 0x3f]; + } + + let base64 = b64TextDecoder.decode(new Uint8Array(encoded.buffer, 0, n)); + + if (padding) { + if (k === 1) { + base64 += "=="; + } else if (k === 2) { + base64 += "="; + } + } + + return base64; +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64 +export function base64_decode(sBase64) { + try { + const binString = atob(sBase64); + const length = binString.length; + const array = new Uint8Array(length); + for (let i = 0; i < length; i++) { + array[i] = binString.charCodeAt(i); + } + return Result$Ok(new BitArray(array)); + } catch { + return Result$Error(Nil); + } +} + +export function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (typeof data === "boolean") { + return "Bool"; + } else if (isResult(data)) { + return "Result"; + } else if (isList(data)) { + return "List"; + } else if (data instanceof BitArray) { + return "BitArray"; + } else if (data instanceof Dict) { + return "Dict"; + } else if (Number.isInteger(data)) { + return "Int"; + } else if (Array.isArray(data)) { + return `Array`; + } else if (typeof data === "number") { + return "Float"; + } else if (data === null) { + return "Nil"; + } else if (data === undefined) { + return "Nil"; + } else { + const type = typeof data; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} + +export function byte_size(string) { + return new TextEncoder().encode(string).length; +} + +// In JavaScript, bitwise operations convert numbers to a sequence of 32 bits, +// while Erlang uses arbitrary precision integers. +// +// To get around this, every function here follows this pattern: +// +// 1. If both values fit in the 32-bit signed integer range, use the standard +// JavaScript bitwise operators directly. +// +// Note: For bitwise_shift_left, the result also needs to fit in 32 bits, +// so we use floating-point multiplication instead. +// +// 2. If either value falls outside the safe integer range (-2^53, 2^53), +// fall back to BigInt arithmetic, then downcast the result back to a Number. +// +// 3. Otherwise (safe integers outside the 32-bit range), we split the operation +// across the high 21 bits and low 32 bits individually: +// +// x1 $ x2 = ((hi(x1) $ hi(x2)) << 32) | (lo(x1) $ lo(x2)) +// +// where `$` is a bitwise operator. +// +// We split both values into a `hi` and a `lo` part: +// +// hi(x) = Math.floor(x / 2^32) the upper 21 bits +// lo(x) = x >>> 0 the lower 32 bits (as unsigned) +// +// For `hi`, we use that shifts are equal to multiplication/division with +// powers of two to get around the 32-bit range limitation. Math.floor is +// used instead of truncation since arithmetic right shift fills with the +// sign bit. For negative numbers, the discarded bits were non-zero +// (representing a positive fractional part), so discarding them makes the +// result strictly more negative, i.e. rounding away from 0. +// +// This works because bitwise operators are distributive across bit ranges: +// +// x1 $ x2 = (hi(x1) $ hi(x2)) << 32 | (lo(x1) $ lo(x2)) +// = (hi(x1) $ hi(x2)) * 2^32 + (lo(x1) $ lo(x2)) +// +// JavaScript bitwise operators truncate inputs to signed 32-bit integers, +// so `x1 $ x2` already computes `lo(x1) $ lo(x2)` — we just need to +// reinterpret the signed result as unsigned using `>>> 0`: +// +// lo(x1) $ lo(x2) = (x1 $ x2) >>> 0 = lo(x1 $ x2) +// => x1 $ x2 = (hi(x1) $ hi(x2)) * 2^32 + lo(x1 $ x2). +// +const MIN_I32 = -(2 ** 31); // -2147483648 +const MAX_I32 = 2 ** 31 - 1; // 2147483647 +const U32 = 2 ** 32; +const MAX_SAFE = Number.MAX_SAFE_INTEGER; +const MIN_SAFE = Number.MIN_SAFE_INTEGER; + +export function bitwise_and(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x & y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) & BigInt(y)); + + return (Math.floor(x / U32) & Math.floor(y / U32)) * U32 + ((x & y) >>> 0); +} + +export function bitwise_or(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x | y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) | BigInt(y)); + + return (Math.floor(x / U32) | Math.floor(y / U32)) * U32 + ((x | y) >>> 0); +} + +export function bitwise_exclusive_or(x, y) { + if (x >= MIN_I32 && x <= MAX_I32 && y >= MIN_I32 && y <= MAX_I32) + return x ^ y; + if (x < MIN_SAFE || x > MAX_SAFE || y < MIN_SAFE || y > MAX_SAFE) + return Number(BigInt(x) ^ BigInt(y)); + + return (Math.floor(x / U32) ^ Math.floor(y / U32)) * U32 + ((x ^ y) >>> 0); +} + +export function bitwise_not(x) { + if (x >= MIN_I32 && x <= MAX_I32) return ~x; + if (x < MIN_SAFE || x > MAX_SAFE) return Number(~BigInt(x)); + + return ~Math.floor(x / U32) * U32 + (~x >>> 0); +} + +export function bitwise_shift_right(x, y) { + if (y === 0) return x; + if (y < 0) return bitwise_shift_left(x, -y); + if (y < 32 && x >= MIN_I32 && x <= MAX_I32) return x >> y; + if (x < MIN_SAFE || x > MAX_SAFE) return Number(BigInt(x) >> BigInt(y)); + + const ahi = Math.floor(x / U32); + + // Shifting right by y < 32 moves bits across the hi/lo boundary: + // + // before: [ hi (21 bits) | lo (32 bits) ] + // after: [ hi >> y | (hi's low y bits) ++ (lo >> y) ] + // + // The new low word has two sources: + // - lo's bits shifted down: x >>> y (>>> treats x as unsigned 32-bit) + // - hi's bottom y bits shifted up: ahi << (32 - y). + if (y < 32) return (ahi >> y) * U32 + (((x >>> y) | (ahi << (32 - y))) >>> 0); + + // Shifting by >= 32 wipes out the entire low word. The result is just the + // high word shifted right by the remaining amount. + return ahi >> (y - 32); +} + +export function bitwise_shift_left(x, y) { + if (y === 0) return x; + if (y < 0) return bitwise_shift_right(x, -y); + if (y < 31) return x * (1 << y); + return x * 2 ** y; +} + +export function inspect(v) { + return new Inspector().inspect(v); +} + +export function float_to_string(float) { + const string = float.toString().replace("+", ""); + if (string.indexOf(".") >= 0) { + return string; + } else { + const index = string.indexOf("e"); + if (index >= 0) { + return string.slice(0, index) + ".0" + string.slice(index); + } else { + return string + ".0"; + } + } +} + +class Inspector { + #references = new Set(); + + inspect(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === undefined) return "Nil"; + if (t === "string") return this.#string(v); + if (t === "bigint" || Number.isInteger(v)) return v.toString(); + if (t === "number") return float_to_string(v); + if (v instanceof UtfCodepoint) return this.#utfCodepoint(v); + if (v instanceof BitArray) return this.#bit_array(v); + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof globalThis.Error) return `//js(${v.toString()})`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + + if (this.#references.size === this.#references.add(v).size) { + return "//js(circular reference)"; + } + + let printed; + if (Array.isArray(v)) { + printed = `#(${v.map((v) => this.inspect(v)).join(", ")})`; + } else if (isList(v)) { + printed = this.#list(v); + } else if (v instanceof CustomType) { + printed = this.#customType(v); + } else if (v instanceof Dict) { + printed = this.#dict(v); + } else if (v instanceof Set) { + return `//js(Set(${[...v].map((v) => this.inspect(v)).join(", ")}))`; + } else { + printed = this.#object(v); + } + this.#references.delete(v); + return printed; + } + + #object(v) { + const name = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${this.inspect(k)}: ${this.inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name === "Object" ? "" : name + " "; + return `//js(${head}{${body}})`; + } + + #dict(map) { + let body = "dict.from_list(["; + let first = true; + + body = dict_fold(map, body, (body, key, value) => { + if (!first) body = body + ", "; + first = false; + return body + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")"; + }); + + return body + "])"; + } + + #customType(record) { + const props = Object.keys(record) + .map((label) => { + const value = this.inspect(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value}` : value; + }) + .join(", "); + return props + ? `${record.constructor.name}(${props})` + : record.constructor.name; + } + + #list(list) { + if (List$isEmpty(list)) { + return "[]"; + } + + let char_out = 'charlist.from_string("'; + let list_out = "["; + + let current = list; + while (List$isNonEmpty(current)) { + let element = current.head; + current = current.tail; + + if (list_out !== "[") { + list_out += ", "; + } + list_out += this.inspect(element); + + if (char_out) { + if (Number.isInteger(element) && element >= 32 && element <= 126) { + char_out += String.fromCharCode(element); + } else { + char_out = null; + } + } + } + + if (char_out) { + return char_out + '")'; + } else { + return list_out + "]"; + } + } + + #string(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + switch (char) { + case "\n": + new_str += "\\n"; + break; + case "\r": + new_str += "\\r"; + break; + case "\t": + new_str += "\\t"; + break; + case "\f": + new_str += "\\f"; + break; + case "\\": + new_str += "\\\\"; + break; + case '"': + new_str += '\\"'; + break; + default: + if (char < " " || (char > "~" && char < "\u{00A0}")) { + new_str += + "\\u{" + + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + + "}"; + } else { + new_str += char; + } + } + } + new_str += '"'; + return new_str; + } + + #utfCodepoint(codepoint) { + return `//utfcodepoint(${String.fromCodePoint(codepoint.value)})`; + } + + #bit_array(bits) { + if (bits.bitSize === 0) { + return "<<>>"; + } + + let acc = "<<"; + + for (let i = 0; i < bits.byteSize - 1; i++) { + acc += bits.byteAt(i).toString(); + acc += ", "; + } + + if (bits.byteSize * 8 === bits.bitSize) { + acc += bits.byteAt(bits.byteSize - 1).toString(); + } else { + const trailingBitsCount = bits.bitSize % 8; + acc += bits.byteAt(bits.byteSize - 1) >> (8 - trailingBitsCount); + acc += `:size(${trailingBitsCount})`; + } + + acc += ">>"; + return acc; + } +} + +export function base16_encode(bit_array) { + const trailingBitsCount = bit_array.bitSize % 8; + + let result = ""; + + for (let i = 0; i < bit_array.byteSize; i++) { + let byte = bit_array.byteAt(i); + + if (i === bit_array.byteSize - 1 && trailingBitsCount !== 0) { + const unusedBitsCount = 8 - trailingBitsCount; + byte = (byte >> unusedBitsCount) << unusedBitsCount; + } + + result += byte.toString(16).padStart(2, "0").toUpperCase(); + } + + return result; +} + +export function base16_decode(string) { + const bytes = new Uint8Array(string.length / 2); + for (let i = 0; i < string.length; i += 2) { + const a = parseInt(string[i], 16); + const b = parseInt(string[i + 1], 16); + if (isNaN(a) || isNaN(b)) return Result$Error(Nil); + bytes[i / 2] = a * 16 + b; + } + return Result$Ok(new BitArray(bytes)); +} + +export function bit_array_to_int_and_size(bits) { + const trailingBitsCount = bits.bitSize % 8; + const unusedBitsCount = trailingBitsCount === 0 ? 0 : 8 - trailingBitsCount; + + return [bits.byteAt(0) >> unusedBitsCount, bits.bitSize]; +} + +export function bit_array_starts_with(bits, prefix) { + if (prefix.bitSize > bits.bitSize) { + return false; + } + + // Check any whole bytes + const byteCount = Math.trunc(prefix.bitSize / 8); + for (let i = 0; i < byteCount; i++) { + if (bits.byteAt(i) !== prefix.byteAt(i)) { + return false; + } + } + + // Check any trailing bits at the end of the prefix + if (prefix.bitSize % 8 !== 0) { + const unusedBitsCount = 8 - (prefix.bitSize % 8); + if ( + bits.byteAt(byteCount) >> unusedBitsCount !== + prefix.byteAt(byteCount) >> unusedBitsCount + ) { + return false; + } + } + + return true; +} + +export function log(x) { + // It is checked in Gleam that: + // - The input is strictly positive (x > 0) + // - This ensures that Math.log will never return NaN or -Infinity + // The function can thus safely pass the input to Math.log + // and a valid finite float will always be produced. + return Math.log(x); +} + +export function exp(x) { + return Math.exp(x); +} + +export function list_to_array(list) { + let current = list; + let array = []; + while (List$isNonEmpty(current)) { + array.push(current.head); + current = current.tail; + } + return array; +} + +export function index(data, key) { + // Dictionaries and dictionary-like objects can be indexed + if (data instanceof Dict) { + const result = dict_get(data, key); + return Result$Ok(result.isOk() ? new Some(result[0]) : new None()); + } + + if (data instanceof WeakMap || data instanceof Map) { + const token = {}; + const entry = data.get(key, token); + if (entry === token) return Result$Ok(new None()); + return Result$Ok(new Some(entry)); + } + + const key_is_int = Number.isInteger(key); + + // Only elements 0-7 of lists can be indexed, negative indices are not allowed + if (key_is_int && key >= 0 && key < 8 && isList(data)) { + let i = 0; + for (const value of data) { + if (i === key) return Result$Ok(new Some(value)); + i++; + } + return Result$Error("Indexable"); + } + + // Arrays and objects can be indexed + if ( + (key_is_int && Array.isArray(data)) || + (data && typeof data === "object") || + (data && Object.getPrototypeOf(data) === Object.prototype) + ) { + if (key in data) return Result$Ok(new Some(data[key])); + return Result$Ok(new None()); + } + + return Result$Error(key_is_int ? "Indexable" : "Dict"); +} + +export function list(data, decode, pushPath, index, emptyList) { + if (!(isList(data) || Array.isArray(data))) { + const error = DecodeError$DecodeError("List", classify(data), emptyList); + return [emptyList, arrayToList([error])]; + } + + const decoded = []; + + for (const element of data) { + const layer = decode(element); + const [out, errors] = layer; + + if (List$isNonEmpty(errors)) { + const [_, errors] = pushPath(layer, index.toString()); + return [emptyList, errors]; + } + decoded.push(out); + index++; + } + + return [arrayToList(decoded), emptyList]; +} + +export function dict(data) { + if (data instanceof Dict) { + return Result$Ok(data); + } + if (data instanceof Map || data instanceof WeakMap) { + return Result$Ok(dict_from_iterable(data)); + } + if (data == null) { + return Result$Error("Dict"); + } + if (typeof data !== "object") { + return Result$Error("Dict"); + } + const proto = Object.getPrototypeOf(data); + if (proto === Object.prototype || proto === null) { + return Result$Ok(dict_from_iterable(Object.entries(data))); + } + return Result$Error("Dict"); +} + +export function bit_array(data) { + if (data instanceof BitArray) return Result$Ok(data); + if (data instanceof Uint8Array) return Result$Ok(new BitArray(data)); + return Result$Error(new BitArray(new Uint8Array())); +} + +export function float(data) { + if (typeof data === "number") return Result$Ok(data); + return Result$Error(0.0); +} + +export function int(data) { + if (Number.isInteger(data)) return Result$Ok(data); + return Result$Error(0); +} + +export function string(data) { + if (typeof data === "string") return Result$Ok(data); + return Result$Error(""); +} + +export function is_null(data) { + return data === null || data === undefined; +} + +function arrayToList(array) { + let list = List$Empty(); + let i = array.length; + while (i--) { + list = List$NonEmpty(array[i], list); + } + return list; +} + +function isList(data) { + return List$isEmpty(data) || List$isNonEmpty(data); +} + +function isResult(data) { + return Result$isOk(data) || Result$isError(data); +} + +export function string_remove_prefix(string, prefix) { + if (string.startsWith(prefix)) { + return string.slice(prefix.length); + } + return string; +} + +export function string_remove_suffix(string, suffix) { + if (string.endsWith(suffix)) { + return string.slice(0, string.length - suffix.length); + } + return string; +} diff --git a/examples/http_interactions/build/packages/gleam_time/README.md b/examples/http_interactions/build/packages/gleam_time/README.md new file mode 100644 index 0000000..4c4360f --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/README.md @@ -0,0 +1,101 @@ +# Time 🕰️ + +Work with time in Gleam! + +[![Package Version](https://img.shields.io/hexpm/v/gleam_time)](https://hex.pm/packages/gleam_time) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_time/) + +```sh +gleam add gleam_time +``` + +This package is the foundation of all code that works with time in Gleam. If +your program uses time then you should be using the types in this package, and +you might choose to add other packages to provide additional functionality. + +## How not to have time related bugs + +Time is famously difficult to work with! It's a very complex area, and there's +many approaches that seem reasonable or obvious, but then commonly result in +bugs. This package is carefully designed to help you avoid these problems, so +it is wise to read this documentation before continuing. + +It is important to understand there are two main ways that time is represented: + +- **Calendar time**: This is how humans commonly think and communicate about + time. For example, "10pm on the 5th of January". This is easy for a human to + read, but is it typically ambiguous and hard to work with! 10pm in + Killorglin, Ireland is not the same point in time as 10pm in Harare, + Zimbabwe. The exact meaning of calendar time depends on daylight savings + time, leap years, leap seconds, and continuously changing national and + political declarations. To make calendar time unambiguous you will need to + know what time zone it is for, and to have an up-to-date time zone database. + +- **Epoch time**: Epoch time is defined as an exact amount of since some fixed + point in time. It is always unambiguous as is not impacted by geopolitics, + time zones, etc. It is efficient for computers to work with, and it is less + likely to result in buggy code. + +In this package epoch time is provided by the `gleam/time/timestamp` module, +and calendar time is provided by the `gleam/time/calendar` module. + +Time zone information has to be loaded from elsewhere, but which approch is +best will depend on your application. User interfaces may want to read current +time zone information from the user's web browser or operating system. Server +side applications may want to embed or downloads a full copy of the time zone +database and then ask clients which time zone they want to use. + +For an entertaining overview of some of the problems of calendar time view this +video: ["The Problem with Time & Timezones" by Computerphile](https://www.youtube.com/watch?v=-5wpm-gesOY). + +### Which time representation should you use? + +> **tldr**: Use `gleam/time/timestamp`. + +The longer, more detailed answer: + +- Default to `gleam/time/timestamp`, which is epoch time. It is + unambiguous, efficient, and significantly less likely to result in logic + bugs. + +- When writing time to a database or other data storage use epoch time, + using whatever epoch format it supports. For example, PostgreSQL + `timestamp` and `timestampz` are both epoch time, and `timestamp` is + preferred as it is more straightforward to use as your application is + also using epoch time. + +- When communicating with other computer systems continue to use epoch + time. For example, when sending times to another program you could + encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January + 1970). + +- When communicating with humans use epoch time internally, and convert + to-and-from calendar time at the last moment, when iteracting with the + human user. It may also help the users to also show the time as a fuzzy + duration from the present time, such as "about 4 days ago". + +- When representing "fuzzy" human time concepts that don't exact periods + in time, such as "one month" (varies depending on which month, which + year, and in which time zone) and "Christmas Day" (varies depending on + which year and time zone) then use calendar time. + +Any time you do use calendar time you should be extra careful! It is very +easy to make mistake with. Avoid it where possible. + +## Special thanks + +This package was created with great help from several kind contributors. In +alphabetical order: + +- [Hayleigh Thompson](https://github.com/hayleigh-dot-dev) +- [John Strunk](https://github.com/jrstrunk) +- [Ryan Moore](https://github.com/mooreryan) +- [Shayan Javani](https://github.com/massivefermion) + +These non-Gleam projects where highly influential on the design of this +package: + +- Elm's `elm/time` package. +- Go's `time` module. +- Rust's `std::time` module. +- Elixir's standard library time modules and `timex` package. diff --git a/examples/http_interactions/build/packages/gleam_time/gleam.toml b/examples/http_interactions/build/packages/gleam_time/gleam.toml new file mode 100644 index 0000000..3b347a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/gleam.toml @@ -0,0 +1,19 @@ +name = "gleam_time" +version = "1.8.0" +description = "Work with time in Gleam!" +gleam = ">= 1.11.0" +licences = ["Apache-2.0"] +repository = { type = "github", user = "gleam-lang", repo = "time" } +links = [ + { title = "Sponsor", href = "https://github.com/sponsors/lpil" } +] + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +qcheck = ">= 1.0.0 and < 2.0.0" +simplifile = ">= 2.2.0 and < 3.0.0" +gleam_regexp = ">= 1.0.0 and < 2.0.0" +prng = ">= 4.0.1 and < 5.0.0" diff --git a/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_Date.hrl b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_Date.hrl new file mode 100644 index 0000000..b746fad --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_Date.hrl @@ -0,0 +1,5 @@ +-record(date, { + year :: integer(), + month :: gleam@time@calendar:month(), + day :: integer() +}). diff --git a/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_TimeOfDay.hrl b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_TimeOfDay.hrl new file mode 100644 index 0000000..b5a103d --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@calendar_TimeOfDay.hrl @@ -0,0 +1,6 @@ +-record(time_of_day, { + hours :: integer(), + minutes :: integer(), + seconds :: integer(), + nanoseconds :: integer() +}). diff --git a/examples/http_interactions/build/packages/gleam_time/include/gleam@time@duration_Duration.hrl b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@duration_Duration.hrl new file mode 100644 index 0000000..5477733 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@duration_Duration.hrl @@ -0,0 +1 @@ +-record(duration, {seconds :: integer(), nanoseconds :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_time/include/gleam@time@timestamp_Timestamp.hrl b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@timestamp_Timestamp.hrl new file mode 100644 index 0000000..b05bafe --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/include/gleam@time@timestamp_Timestamp.hrl @@ -0,0 +1 @@ +-record(timestamp, {seconds :: integer(), nanoseconds :: integer()}). diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam/time/calendar.gleam b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/calendar.gleam new file mode 100644 index 0000000..ac81c11 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/calendar.gleam @@ -0,0 +1,346 @@ +//// This module is for working with the Gregorian calendar, established by +//// Pope Gregory XIII in 1582! +//// +//// ## When should you use this module? +//// +//// > **tldr:** You probably want to use [`gleam/time/timestamp`](./timestamp.html) +//// > instead! +//// +//// Calendar time is difficult to work with programmatically, it is the source +//// of most time-related bugs in software. Compared to _epoch time_, which the +//// `gleam/time/timestamp` module uses, there are many disadvantages to +//// calendar time: +//// +//// - They are ambiguous if you don't know what time-zone is being used. +//// +//// - A time-zone database is required to understand calendar time even when +//// you have the time zone. These are large and your program has to +//// continously be updated as new versions of the database are published. +//// +//// - The type permits invalid states. e.g. `days` could be set to the number +//// 32, but this should not be possible! +//// +//// - There is not a single unique canonical value for each point in time, +//// thanks to time zones. Two different `Date` + `TimeOfDay` value pairs +//// could represent the same point in time. This means that you can't check +//// for time equality with `==` when using calendar types. +//// +//// - They are computationally complex, using a more memory to represent and +//// requiring a lot more CPU time to manipulate. +//// +//// There are also advantages to calendar time: +//// +//// - Calendar time is how human's talk about time, so if you want to show a +//// time or take a time from a human user then calendar time will make it +//// easier for them. +//// +//// - They can represent more abstract time periods such as "New Year's Day". +//// This may seem like an exact window of time at first, but really the +//// definition of "New Year's Day" is more fuzzy than that. When it starts +//// and ends will depend where in the world you are, so if you want to refer +//// to a day as a global concept instead of a fixed window of time for that +//// day in a specific location, then calendar time can represent that. +//// +//// So when should you use calendar time? These are our recommendations: +//// +//// - Default to `gleam/time/timestamp`, which is epoch time. It is +//// unambiguous, efficient, and significantly less likely to result in logic +//// bugs. +//// +//// - When writing time to a database or other data storage use epoch time, +//// using whatever epoch format it supports. For example, PostgreSQL +//// `timestamp` and `timestampz` are both epoch time, and `timestamp` is +//// preferred as it is more straightforward to use as your application is +//// also using epoch time. +//// +//// - When communicating with other computer systems continue to use epoch +//// time. For example, when sending times to another program you could +//// encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January +//// 1970). +//// +//// - When communicating with humans use epoch time internally, and convert +//// to-and-from calendar time at the last moment, when iteracting with the +//// human user. It may also help the users to also show the time as a fuzzy +//// duration from the present time, such as "about 4 days ago". +//// +//// - When representing "fuzzy" human time concepts that don't exact periods +//// in time, such as "one month" (varies depending on which month, which +//// year, and in which time zone) and "Christmas Day" (varies depending on +//// which year and time zone) then use calendar time. +//// +//// Any time you do use calendar time you should be extra careful! It is very +//// easy to make mistake with. Avoid it where possible. +//// +//// ## Time zone offsets +//// +//// This package includes the `utc_offset` value and the `local_offset` +//// function, which are the offset for the UTC time zone and get the time +//// offset the computer running the program is configured to respectively. +//// +//// If you need to use other offsets in your program then you will need to get +//// them from somewhere else, such as from a package which loads the +//// [IANA Time Zone Database](https://www.iana.org/time-zones), or from the +//// website visitor's web browser, which your frontend can send for you. +//// +//// ## Use in APIs +//// +//// If you are making an API such as a HTTP JSON API you are encouraged to use +//// Unix timestamps instead of calendar times. + +import gleam/int +import gleam/order.{type Order} +import gleam/time/duration + +/// The Gregorian calendar date. Ambiguous without a time zone. +/// +/// Prefer to represent your time using the `Timestamp` type, and convert it +/// only to calendar types when you need to display them. See the documentation +/// for this module for more information. +/// +pub type Date { + Date(year: Int, month: Month, day: Int) +} + +/// The time of day. Ambiguous without a date and time zone. +/// +pub type TimeOfDay { + TimeOfDay(hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) +} + +/// The 12 months of the year. +pub type Month { + January + February + March + April + May + June + July + August + September + October + November + December +} + +/// The offset for the [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) +/// time zone. +/// +/// The utc zone has no time adjustments, it is always zero. It never observes +/// daylight-saving time and it never shifts around based on political +/// restructuring. +/// +pub const utc_offset = duration.empty + +/// Get the offset for the computer's currently configured time zone. +/// +/// Note this may not be the time zone that is correct to use for your user. +/// For example, if you are making a web application that runs on a server you +/// want _their_ computer's time zone, not yours. +/// +/// This is the _current local_ offset, not the current local time zone. This +/// means that while it will result in the expected outcome for the current +/// time, it may result in unexpected output if used with other timestamps. For +/// example: a timestamp that would locally be during daylight savings time if +/// is it not currently daylight savings time when this function is called. +/// +pub fn local_offset() -> duration.Duration { + duration.seconds(local_time_offset_seconds()) +} + +@external(erlang, "gleam_time_ffi", "local_time_offset_seconds") +@external(javascript, "../../gleam_time_ffi.mjs", "local_time_offset_seconds") +fn local_time_offset_seconds() -> Int + +/// Returns the English name for a month. +/// +/// # Examples +/// +/// ```gleam +/// month_to_string(April) +/// // -> "April" +/// ``` +pub fn month_to_string(month: Month) -> String { + case month { + January -> "January" + February -> "February" + March -> "March" + April -> "April" + May -> "May" + June -> "June" + July -> "July" + August -> "August" + September -> "September" + October -> "October" + November -> "November" + December -> "December" + } +} + +/// Returns the number for the month, where January is 1 and December is 12. +/// +/// # Examples +/// +/// ```gleam +/// month_to_int(January) +/// // -> 1 +/// ``` +pub fn month_to_int(month: Month) -> Int { + case month { + January -> 1 + February -> 2 + March -> 3 + April -> 4 + May -> 5 + June -> 6 + July -> 7 + August -> 8 + September -> 9 + October -> 10 + November -> 11 + December -> 12 + } +} + +/// Returns the month for a given number, where January is 1 and December is 12. +/// +/// # Examples +/// +/// ```gleam +/// month_from_int(1) +/// // -> Ok(January) +/// ``` +pub fn month_from_int(month: Int) -> Result(Month, Nil) { + case month { + 1 -> Ok(January) + 2 -> Ok(February) + 3 -> Ok(March) + 4 -> Ok(April) + 5 -> Ok(May) + 6 -> Ok(June) + 7 -> Ok(July) + 8 -> Ok(August) + 9 -> Ok(September) + 10 -> Ok(October) + 11 -> Ok(November) + 12 -> Ok(December) + _ -> Error(Nil) + } +} + +/// Checks if a given date is valid. +/// +/// This function properly accounts for leap years when validating February days. +/// A leap year occurs every 4 years, except for years divisible by 100, +/// unless they are also divisible by 400. +/// +/// # Examples +/// +/// ```gleam +/// is_valid_date(Date(2023, April, 15)) +/// // -> True +/// ``` +/// +/// ```gleam +/// is_valid_date(Date(2023, April, 31)) +/// // -> False +/// ``` +/// +/// ```gleam +/// is_valid_date(Date(2024, February, 29)) +/// // -> True (2024 is a leap year) +/// ``` +/// +pub fn is_valid_date(date: Date) -> Bool { + let Date(year:, month:, day:) = date + case day < 1 { + True -> False + False -> + case month { + January | March | May | July | August | October | December -> day <= 31 + April | June | September | November -> day <= 30 + February -> { + let max_february_days = case is_leap_year(year) { + True -> 29 + False -> 28 + } + day <= max_february_days + } + } + } +} + +/// Determines if a given year is a leap year. +/// +/// A leap year occurs every 4 years, except for years divisible by 100, +/// unless they are also divisible by 400. +/// +/// # Examples +/// +/// ```gleam +/// is_leap_year(2024) +/// // -> True +/// ``` +/// +/// ```gleam +/// is_leap_year(2023) +/// // -> False +/// ``` +/// +pub fn is_leap_year(year: Int) -> Bool { + case year % 400 == 0 { + True -> True + False -> + case year % 100 == 0 { + True -> False + False -> year % 4 == 0 + } + } +} + +/// Checks if a time of day is valid. +/// +/// Validates that hours are 0-23, minutes are 0-59, seconds are 0-59, +/// and nanoseconds are 0-999,999,999. +/// +/// # Examples +/// +/// ```gleam +/// is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789)) +/// // -> True +/// ``` +/// +pub fn is_valid_time_of_day(time: TimeOfDay) -> Bool { + let TimeOfDay(hours:, minutes:, seconds:, nanoseconds:) = time + + hours >= 0 + && hours <= 23 + && minutes >= 0 + && minutes <= 59 + && seconds >= 0 + && seconds <= 59 + && nanoseconds >= 0 + && nanoseconds <= 999_999_999 +} + +/// Naively compares two dates without any time zone information, returning an +/// order. +/// +/// ## Correctness +/// +/// This function compares dates without any time zone information, only using +/// the rules for the gregorian calendar. This is typically sufficient, but be +/// aware that in reality some time zones will change their calendar date +/// occasionally. This can result in days being skipped, out of order, or +/// happening multiple times. +/// +/// If you need real-world correct time ordering then use the +/// `gleam/time/timestamp` module instead. +/// +pub fn naive_date_compare(one: Date, other: Date) -> Order { + int.compare(one.year, other.year) + |> order.lazy_break_tie(fn() { + int.compare(month_to_int(one.month), month_to_int(other.month)) + }) + |> order.lazy_break_tie(fn() { int.compare(one.day, other.day) }) +} diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam/time/duration.gleam b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/duration.gleam new file mode 100644 index 0000000..14fb4e5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/duration.gleam @@ -0,0 +1,308 @@ +import gleam/bool +import gleam/int +import gleam/order +import gleam/string + +/// An amount of time, with up to nanosecond precision. +/// +/// This type does not represent calendar periods such as "1 month" or "2 +/// days". Those periods will be different lengths of time depending on which +/// month or day they apply to. For example, January is longer than February. +/// A different type should be used for calendar periods. +/// +pub opaque type Duration { + // When compiling to JavaScript ints have limited precision and size. This + // means that if we were to store the the timestamp in a single int the + // duration would not be able to represent very large or small durations. + // Durations are instead represented as a number of seconds and a number of + // nanoseconds. + // + // If you have manually adjusted the seconds and nanoseconds values the + // `normalise` function can be used to ensure the time is represented the + // intended way, with `nanoseconds` being positive and less than 1 second. + // + // The duration is the sum of the seconds and the nanoseconds. + Duration(seconds: Int, nanoseconds: Int) +} + +/// A division of time. +/// +/// Note that not all months and years are the same length, so a reasonable +/// average length is used by this module. +/// +pub type Unit { + Nanosecond + /// 1000 nanoseconds. + Microsecond + /// 1000 microseconds. + Millisecond + /// 1000 milliseconds. + Second + /// 60 seconds. + Minute + /// 60 minutes. + Hour + /// 24 hours. + Day + /// 7 days. + Week + /// About 30.4375 days. Real calendar months vary in length. + Month + /// About 365.25 days. Real calendar years vary in length. + Year +} + +/// Convert a duration to a number of the largest number of a unit, serving as +/// a rough description of the duration that a human can understand. +/// +/// The size used for each unit are described in the documentation for the +/// `Unit` type. +/// +/// ```gleam +/// seconds(125) +/// |> approximate +/// // -> #(2, Minute) +/// ``` +/// +/// This function rounds _towards zero_. This means that if a duration is just +/// short of 2 days then it will approximate to 1 day. +/// +/// ```gleam +/// hours(47) +/// |> approximate +/// // -> #(1, Day) +/// ``` +/// +pub fn approximate(duration: Duration) -> #(Int, Unit) { + let Duration(seconds: s, nanoseconds: ns) = duration + let minute = 60 + let hour = minute * 60 + let day = hour * 24 + let week = day * 7 + let year = day * 365 + hour * 6 + let month = year / 12 + let microsecond = 1000 + let millisecond = microsecond * 1000 + case Nil { + _ if s < 0 -> { + let #(amount, unit) = Duration(-s, -ns) |> normalise |> approximate + #(-amount, unit) + } + _ if s >= year -> #(s / year, Year) + _ if s >= month -> #(s / month, Month) + _ if s >= week -> #(s / week, Week) + _ if s >= day -> #(s / day, Day) + _ if s >= hour -> #(s / hour, Hour) + _ if s >= minute -> #(s / minute, Minute) + _ if s > 0 -> #(s, Second) + _ if ns >= millisecond -> #(ns / millisecond, Millisecond) + _ if ns >= microsecond -> #(ns / microsecond, Microsecond) + _ -> #(ns, Nanosecond) + } +} + +/// Ensure the duration is represented with `nanoseconds` being positive and +/// less than 1 second. +/// +/// This function does not change the amount of time that the duration refers +/// to, it only adjusts the values used to represent the time. +/// +fn normalise(duration: Duration) -> Duration { + let multiplier = 1_000_000_000 + let nanoseconds = duration.nanoseconds % multiplier + let overflow = duration.nanoseconds - nanoseconds + let seconds = duration.seconds + overflow / multiplier + case nanoseconds >= 0 { + True -> Duration(seconds, nanoseconds) + False -> Duration(seconds - 1, multiplier + nanoseconds) + } +} + +/// Compare one duration to another, indicating whether the first spans a +/// larger amount of time (and so is greater) or smaller amount of time (and so +/// is lesser) than the second. +/// +/// # Examples +/// +/// ```gleam +/// compare(seconds(1), seconds(2)) +/// // -> order.Lt +/// ``` +/// +/// Whether a duration is negative or positive doesn't matter for comparing +/// them, only the amount of time spanned matters. +/// +/// ```gleam +/// compare(seconds(-2), seconds(1)) +/// // -> order.Gt +/// ``` +/// +pub fn compare(left: Duration, right: Duration) -> order.Order { + let parts = fn(x: Duration) { + case x.seconds >= 0 { + True -> #(x.seconds, x.nanoseconds) + False -> #(x.seconds * -1 - 1, 1_000_000_000 - x.nanoseconds) + } + } + let #(ls, lns) = parts(left) + let #(rs, rns) = parts(right) + int.compare(ls, rs) + |> order.break_tie(int.compare(lns, rns)) +} + +/// Calculate the difference between two durations. +/// +/// This is effectively substracting the first duration from the second. +/// +/// # Examples +/// +/// ```gleam +/// difference(seconds(1), seconds(5)) +/// // -> seconds(4) +/// ``` +/// +pub fn difference(left: Duration, right: Duration) -> Duration { + Duration(right.seconds - left.seconds, right.nanoseconds - left.nanoseconds) + |> normalise +} + +/// Add two durations together. +/// +/// # Examples +/// +/// ```gleam +/// add(seconds(1), seconds(5)) +/// // -> seconds(6) +/// ``` +/// +pub fn add(left: Duration, right: Duration) -> Duration { + Duration(left.seconds + right.seconds, left.nanoseconds + right.nanoseconds) + |> normalise +} + +/// Convert the duration to an [ISO8601][1] formatted duration string. +/// +/// The ISO8601 duration format is ambiguous without context due to months and +/// years having different lengths, and because of leap seconds. This function +/// encodes the duration as days, hours, and seconds without any leap seconds. +/// Be sure to take this into account when using the duration strings. +/// +/// [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations +/// +pub fn to_iso8601_string(duration: Duration) -> String { + use <- bool.guard(duration == empty, "PT0S") + let split = fn(total, limit) { + let amount = total % limit + let remainder = { total - amount } / limit + #(amount, remainder) + } + let #(seconds, rest) = split(duration.seconds, 60) + let #(minutes, rest) = split(rest, 60) + let #(hours, rest) = split(rest, 24) + let days = rest + let add = fn(out, value, unit) { + case value { + 0 -> out + _ -> out <> int.to_string(value) <> unit + } + } + let output = + "P" + |> add(days, "D") + |> string.append("T") + |> add(hours, "H") + |> add(minutes, "M") + case seconds, duration.nanoseconds { + 0, 0 -> output + _, 0 -> output <> int.to_string(seconds) <> "S" + _, _ -> { + let f = nanosecond_digits(duration.nanoseconds, 0, "") + output <> int.to_string(seconds) <> "." <> f <> "S" + } + } +} + +fn nanosecond_digits(n: Int, position: Int, acc: String) -> String { + case position { + 9 -> acc + _ if acc == "" && n % 10 == 0 -> { + nanosecond_digits(n / 10, position + 1, acc) + } + _ -> { + let acc = int.to_string(n % 10) <> acc + nanosecond_digits(n / 10, position + 1, acc) + } + } +} + +/// Create a duration of a number of seconds. +pub fn seconds(amount: Int) -> Duration { + Duration(amount, 0) +} + +/// Create a duration of a number of minutes. +pub fn minutes(amount: Int) -> Duration { + seconds(amount * 60) +} + +/// Create a duration of a number of hours. +pub fn hours(amount: Int) -> Duration { + seconds(amount * 60 * 60) +} + +/// Create a duration of a number of milliseconds. +pub fn milliseconds(amount: Int) -> Duration { + let remainder = amount % 1000 + let overflow = amount - remainder + let nanoseconds = remainder * 1_000_000 + let seconds = overflow / 1000 + Duration(seconds, nanoseconds) + |> normalise +} + +/// Create a duration of a number of nanoseconds. +/// +/// # JavaScript int limitations +/// +/// Remember that JavaScript can only perfectly represent ints between positive +/// and negative 9,007,199,254,740,991! If you use a single call to this +/// function to create durations larger than that number of nanoseconds then +/// you will likely not get exactly the value you expect. Use `seconds` and +/// `milliseconds` as much as possible for large durations. +/// +pub fn nanoseconds(amount: Int) -> Duration { + Duration(0, amount) + |> normalise +} + +/// Convert the duration to a number of seconds. +/// +/// There may be some small loss of precision due to `Duration` being +/// nanosecond accurate and `Float` not being able to represent this. +/// +pub fn to_seconds(duration: Duration) -> Float { + let seconds = int.to_float(duration.seconds) + let nanoseconds = int.to_float(duration.nanoseconds) + seconds +. { nanoseconds /. 1_000_000_000.0 } +} + +/// Convert the duration to a number of seconds and nanoseconds. There is no +/// loss of precision with this conversion on any target. +/// +pub fn to_seconds_and_nanoseconds(duration: Duration) -> #(Int, Int) { + #(duration.seconds, duration.nanoseconds) +} + +/// Convert the duration to a number of milliseconds. +/// +/// This conversion truncates any sub-millisecond precision. If you need +/// the full precision, use `to_seconds_and_nanoseconds` instead. +/// +pub fn to_milliseconds(duration: Duration) -> Int { + let seconds_ms = duration.seconds * 1000 + let nanos_ms = duration.nanoseconds / 1_000_000 + seconds_ms + nanos_ms +} + +@internal +pub const empty = Duration(0, 0) diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam/time/timestamp.gleam b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/timestamp.gleam new file mode 100644 index 0000000..eae1169 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam/time/timestamp.gleam @@ -0,0 +1,914 @@ +//// Welcome to the timestamp module! This module and its `Timestamp` type are +//// what you will be using most commonly when working with time in Gleam. +//// +//// A timestamp represents a moment in time, represented as an amount of time +//// since the calendar time 00:00:00 UTC on 1 January 1970, also known as the +//// _Unix epoch_. +//// +//// # Wall clock time and monotonicity +//// +//// Time is very complicated, especially on computers! While they generally do +//// a good job of keeping track of what the time is, computers can get +//// out-of-sync and start to report a time that is too late or too early. Most +//// computers use "network time protocol" to tell each other what they think +//// the time is, and computers that realise they are running too fast or too +//// slow will adjust their clock to correct it. When this happens it can seem +//// to your program that the current time has changed, and it may have even +//// jumped backwards in time! +//// +//// This measure of time is called _wall clock time_, and it is what people +//// commonly think of when they think of time. It is important to be aware that +//// it can go backwards, and your program must not rely on it only ever going +//// forwards at a steady rate. For example, for tracking what order events happen +//// in. +//// +//// This module uses wall clock time. If your program needs time values to always +//// increase you will need a _monotonic_ time instead. It's uncommon that you +//// would need monotonic time, one example might be if you're making a +//// benchmarking framework. +//// +//// The exact way that time works will depend on what runtime you use. The +//// Erlang documentation on time has a lot of detail about time generally as well +//// as how it works on the BEAM, it is worth reading. +//// . +//// +//// # Converting to local calendar time +//// +//// Timestamps don't take into account time zones, so a moment in time will +//// have the same timestamp value regardless of where you are in the world. To +//// convert them to local time you will need to know the offset for the time +//// zone you wish to use, likely from a time zone database. See the +//// `gleam/time/calendar` module for more information. +//// + +import gleam/bit_array +import gleam/float +import gleam/int +import gleam/list +import gleam/order +import gleam/result +import gleam/string +import gleam/time/calendar +import gleam/time/duration.{type Duration} + +const seconds_per_day: Int = 86_400 + +const seconds_per_hour: Int = 3600 + +const seconds_per_minute: Int = 60 + +const nanoseconds_per_second: Int = 1_000_000_000 + +/// The `:` character as a byte +const byte_colon: Int = 0x3A + +/// The `-` character as a byte +const byte_minus: Int = 0x2D + +/// The `0` character as a byte +const byte_zero: Int = 0x30 + +/// The `9` character as a byte +const byte_nine: Int = 0x39 + +/// The `t` character as a byte +const byte_t_lowercase: Int = 0x74 + +/// The `T` character as a byte +const byte_t_uppercase: Int = 0x54 + +/// The `T` character as a byte +const byte_space: Int = 0x20 + +/// The Julian seconds of the UNIX epoch (Julian day is 2_440_588) +const julian_seconds_unix_epoch: Int = 210_866_803_200 + +/// The main time type, which you should favour over other types such as +/// calendar time types. It is efficient, unambiguous, and it is not possible +/// to construct an invalid timestamp. +/// +/// The most common situation in which you may need a different time data +/// structure is when you need to display time to human for them to read. When +/// you need to do this convert the timestamp to calendar time when presenting +/// it, but internally always keep the time as a timestamp. +/// +pub opaque type Timestamp { + // When compiling to JavaScript ints have limited precision and size. This + // means that if we were to store the the timestamp in a single int the + // timestamp would not be able to represent times far in the future or in the + // past, or distinguish between two times that are close together. Timestamps + // are instead represented as a number of seconds and a number of nanoseconds. + // + // If you have manually adjusted the seconds and nanoseconds values the + // `normalise` function can be used to ensure the time is represented the + // intended way, with `nanoseconds` being positive and less than 1 second. + // + // The timestamp is the sum of the seconds and the nanoseconds. + Timestamp(seconds: Int, nanoseconds: Int) +} + +/// The epoch of Unix time, which is 00:00:00 UTC on 1 January 1970. +pub const unix_epoch = Timestamp(0, 0) + +/// Ensure the time is represented with `nanoseconds` being positive and less +/// than 1 second. +/// +/// This function does not change the time that the timestamp refers to, it +/// only adjusts the values used to represent the time. +/// +fn normalise(timestamp: Timestamp) -> Timestamp { + let multiplier = 1_000_000_000 + let nanoseconds = timestamp.nanoseconds % multiplier + let overflow = timestamp.nanoseconds - nanoseconds + let seconds = timestamp.seconds + overflow / multiplier + case nanoseconds >= 0 { + True -> Timestamp(seconds, nanoseconds) + False -> Timestamp(seconds - 1, multiplier + nanoseconds) + } +} + +/// Compare one timestamp to another, indicating whether the first is further +/// into the future (greater) or further into the past (lesser) than the +/// second. +/// +/// # Examples +/// +/// ```gleam +/// compare(from_unix_seconds(1), from_unix_seconds(2)) +/// // -> order.Lt +/// ``` +/// +pub fn compare(left: Timestamp, right: Timestamp) -> order.Order { + order.break_tie( + int.compare(left.seconds, right.seconds), + int.compare(left.nanoseconds, right.nanoseconds), + ) +} + +/// Get the current system time. +/// +/// Note this time is not unique or monotonic, it could change at any time or +/// even go backwards! The exact behaviour will depend on the runtime used. See +/// the module documentation for more information. +/// +/// On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses +/// [`Date.now`][2]. +/// +/// [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1 +/// [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now +/// +pub fn system_time() -> Timestamp { + let #(seconds, nanoseconds) = get_system_time() + normalise(Timestamp(seconds, nanoseconds)) +} + +@external(erlang, "gleam_time_ffi", "system_time") +@external(javascript, "../../gleam_time_ffi.mjs", "system_time") +fn get_system_time() -> #(Int, Int) + +/// Calculate the difference between two timestamps. +/// +/// This is effectively substracting the first timestamp from the second. +/// +/// # Examples +/// +/// ```gleam +/// difference(from_unix_seconds(1), from_unix_seconds(5)) +/// // -> duration.seconds(4) +/// ``` +/// +pub fn difference(left: Timestamp, right: Timestamp) -> Duration { + let seconds = duration.seconds(right.seconds - left.seconds) + let nanoseconds = duration.nanoseconds(right.nanoseconds - left.nanoseconds) + duration.add(seconds, nanoseconds) +} + +/// Add a duration to a timestamp. +/// +/// # Examples +/// +/// ```gleam +/// add(from_unix_seconds(1000), duration.seconds(5)) +/// // -> from_unix_seconds(1005) +/// ``` +/// +pub fn add(timestamp: Timestamp, duration: Duration) -> Timestamp { + let #(seconds, nanoseconds) = duration.to_seconds_and_nanoseconds(duration) + Timestamp(timestamp.seconds + seconds, timestamp.nanoseconds + nanoseconds) + |> normalise +} + +/// Subtract a duration from a timestamp. +/// +/// # Examples +/// +/// ```gleam +/// subtract(from_unix_seconds(1000), duration.seconds(5)) +/// // -> from_unix_seconds(955) +/// ``` +/// +pub fn subtract(timestamp: Timestamp, duration: Duration) -> Timestamp { + let #(seconds, nanoseconds) = duration.to_seconds_and_nanoseconds(duration) + Timestamp(timestamp.seconds - seconds, timestamp.nanoseconds - nanoseconds) + |> normalise +} + +/// Convert a timestamp to a RFC 3339 formatted time string, with an offset +/// supplied as an additional argument. +/// +/// The output of this function is also ISO 8601 compatible so long as the +/// offset not negative. Offsets have at-most minute precision, so an offset +/// with higher precision will be rounded to the nearest minute. +/// +/// If you are making an API such as a HTTP JSON API you are encouraged to use +/// Unix timestamps instead of this format or ISO 8601. Unix timestamps are a +/// better choice as they don't contain offset information. Consider: +/// +/// - UTC offsets are not time zones. This does not and cannot tell us the time +/// zone in which the date was recorded. So what are we supposed to do with +/// this information? +/// - Users typically want dates formatted according to their local time zone. +/// What if the provided UTC offset is different from the current user's time +/// zone? What are we supposed to do with it then? +/// - Despite it being useless (or worse, a source of bugs), the UTC offset +/// creates a larger payload to transfer. +/// +/// They also uses more memory than a unix timestamp. The way they are better +/// than Unix timestamp is that it is easier for a human to read them, but +/// this is a hinderance that tooling can remedy, and APIs are not primarily +/// for humans. +/// +/// # Examples +/// +/// ```gleam +/// timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000) +/// |> to_rfc3339(calendar.utc_offset) +/// // -> "1970-01-01T00:16:40.123Z" +/// ``` +/// +/// ```gleam +/// timestamp.from_unix_seconds(1000) +/// |> to_rfc3339(duration.seconds(3600)) +/// // -> "1970-01-01T01:16:40+01:00" +/// ``` +/// +pub fn to_rfc3339(timestamp: Timestamp, offset: Duration) -> String { + let offset = duration_to_minutes(offset) + let #(years, months, days, hours, minutes, seconds) = + to_calendar_from_offset(timestamp, offset) + + let offset_minutes = modulo(offset, 60) + let offset_hours = int.absolute_value(floored_div(offset, 60.0)) + + let n2 = pad_digit(_, to: 2) + let n4 = pad_digit(_, to: 4) + let out = "" + let out = out <> n4(years) <> "-" <> n2(months) <> "-" <> n2(days) + let out = out <> "T" + let out = out <> n2(hours) <> ":" <> n2(minutes) <> ":" <> n2(seconds) + let out = out <> show_second_fraction(timestamp.nanoseconds) + case int.compare(offset, 0) { + order.Eq -> out <> "Z" + order.Gt -> out <> "+" <> n2(offset_hours) <> ":" <> n2(offset_minutes) + order.Lt -> out <> "-" <> n2(offset_hours) <> ":" <> n2(offset_minutes) + } +} + +fn pad_digit(digit: Int, to desired_length: Int) -> String { + int.to_string(digit) |> string.pad_start(desired_length, "0") +} + +/// Convert a `Timestamp` to calendar time, suitable for presenting to a human +/// to read. +/// +/// If you want a machine to use the time value then you should not use this +/// function and should instead keep it as a timestamp. See the documentation +/// for the `gleam/time/calendar` module for more information. +/// +/// # Examples +/// +/// ```gleam +/// timestamp.from_unix_seconds(0) +/// |> timestamp.to_calendar(calendar.utc_offset) +/// // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0)) +/// ``` +/// +pub fn to_calendar( + timestamp: Timestamp, + offset: Duration, +) -> #(calendar.Date, calendar.TimeOfDay) { + let offset = duration_to_minutes(offset) + let #(year, month, day, hours, minutes, seconds) = + to_calendar_from_offset(timestamp, offset) + let month = case month { + 1 -> calendar.January + 2 -> calendar.February + 3 -> calendar.March + 4 -> calendar.April + 5 -> calendar.May + 6 -> calendar.June + 7 -> calendar.July + 8 -> calendar.August + 9 -> calendar.September + 10 -> calendar.October + 11 -> calendar.November + _ -> calendar.December + } + let nanoseconds = timestamp.nanoseconds + let date = calendar.Date(year:, month:, day:) + let time = calendar.TimeOfDay(hours:, minutes:, seconds:, nanoseconds:) + #(date, time) +} + +fn duration_to_minutes(duration: duration.Duration) -> Int { + float.round(duration.to_seconds(duration) /. 60.0) +} + +fn to_calendar_from_offset( + timestamp: Timestamp, + offset: Int, +) -> #(Int, Int, Int, Int, Int, Int) { + let total = timestamp.seconds + { offset * 60 } + let seconds = modulo(total, 60) + let total_minutes = floored_div(total, 60.0) + let minutes = modulo(total, 60 * 60) / 60 + let hours = modulo(total, 24 * 60 * 60) / { 60 * 60 } + let #(year, month, day) = to_civil(total_minutes) + #(year, month, day, hours, minutes, seconds) +} + +/// Create a `Timestamp` from a human-readable calendar time. +/// +/// # Examples +/// +/// ```gleam +/// timestamp.from_calendar( +/// date: calendar.Date(2024, calendar.December, 25), +/// time: calendar.TimeOfDay(12, 30, 50, 0), +/// offset: calendar.utc_offset, +/// ) +/// |> timestamp.to_rfc3339(calendar.utc_offset) +/// // -> "2024-12-25T12:30:50Z" +/// ``` +/// +pub fn from_calendar( + date date: calendar.Date, + time time: calendar.TimeOfDay, + offset offset: Duration, +) -> Timestamp { + let month = case date.month { + calendar.January -> 1 + calendar.February -> 2 + calendar.March -> 3 + calendar.April -> 4 + calendar.May -> 5 + calendar.June -> 6 + calendar.July -> 7 + calendar.August -> 8 + calendar.September -> 9 + calendar.October -> 10 + calendar.November -> 11 + calendar.December -> 12 + } + from_date_time( + year: date.year, + month:, + day: date.day, + hours: time.hours, + minutes: time.minutes, + seconds: time.seconds, + second_fraction_as_nanoseconds: time.nanoseconds, + offset_seconds: float.round(duration.to_seconds(offset)), + ) +} + +fn modulo(n: Int, m: Int) -> Int { + case int.modulo(n, m) { + Ok(n) -> n + Error(_) -> 0 + } +} + +fn floored_div(numerator: Int, denominator: Float) -> Int { + let n = int.to_float(numerator) /. denominator + float.round(float.floor(n)) +} + +// Adapted from Elm's Time module +fn to_civil(minutes: Int) -> #(Int, Int, Int) { + let raw_day = floored_div(minutes, { 60.0 *. 24.0 }) + 719_468 + let era = case raw_day >= 0 { + True -> raw_day / 146_097 + False -> { raw_day - 146_096 } / 146_097 + } + let day_of_era = raw_day - era * 146_097 + let year_of_era = + { + day_of_era + - { day_of_era / 1460 } + + { day_of_era / 36_524 } + - { day_of_era / 146_096 } + } + / 365 + let year = year_of_era + era * 400 + let day_of_year = + day_of_era + - { 365 * year_of_era + { year_of_era / 4 } - { year_of_era / 100 } } + let mp = { 5 * day_of_year + 2 } / 153 + let month = case mp < 10 { + True -> mp + 3 + False -> mp - 9 + } + let day = day_of_year - { 153 * mp + 2 } / 5 + 1 + let year = case month <= 2 { + True -> year + 1 + False -> year + } + #(year, month, day) +} + +/// Converts nanoseconds into a `String` representation of fractional seconds. +/// +/// Assumes that `nanoseconds < 1_000_000_000`, which will be true for any +/// normalised timestamp. +/// +fn show_second_fraction(nanoseconds: Int) -> String { + case int.compare(nanoseconds, 0) { + // Zero fractional seconds are not shown. + order.Lt | order.Eq -> "" + order.Gt -> { + let second_fraction_part = { + nanoseconds + |> get_zero_padded_digits + |> remove_trailing_zeros + |> list.map(int.to_string) + |> string.join("") + } + + "." <> second_fraction_part + } + } +} + +/// Given a list of digits, return new list with any trailing zeros removed. +/// +fn remove_trailing_zeros(digits: List(Int)) -> List(Int) { + let reversed_digits = list.reverse(digits) + + do_remove_trailing_zeros(reversed_digits) +} + +fn do_remove_trailing_zeros(reversed_digits) { + case reversed_digits { + [] -> [] + [digit, ..digits] if digit == 0 -> do_remove_trailing_zeros(digits) + reversed_digits -> list.reverse(reversed_digits) + } +} + +/// Returns the list of digits of `number`. If the number of digits is less +/// than 9, the result is zero-padded at the front. +/// +fn get_zero_padded_digits(number: Int) -> List(Int) { + do_get_zero_padded_digits(number, [], 0) +} + +fn do_get_zero_padded_digits( + number: Int, + digits: List(Int), + count: Int, +) -> List(Int) { + case number { + number if number <= 0 && count >= 9 -> digits + number if number <= 0 -> + // Zero-pad the digits at the front until we have at least 9 digits. + do_get_zero_padded_digits(number, [0, ..digits], count + 1) + number -> { + let digit = number % 10 + let number = floored_div(number, 10.0) + do_get_zero_padded_digits(number, [digit, ..digits], count + 1) + } + } +} + +/// Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +/// +/// # Examples +/// +/// ```gleam +/// let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z") +/// timestamp.to_unix_seconds_and_nanoseconds(ts) +/// // -> #(1, 0) +/// ``` +/// +/// Parsing an invalid timestamp returns an error. +/// +/// ```gleam +/// let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31") +/// ``` +/// +/// ## Time zones +/// +/// It may at first seem that the RFC 3339 format includes timezone +/// information, as it can specify an offset such as `Z` or `+3`, so why does +/// this function not return calendar time with a time zone? There are multiple +/// reasons: +/// +/// - RFC 3339's timestamp format is based on calendar time, but it is +/// unambigous, so it can be converted into epoch time when being parsed. It +/// is always better to internally use epoch time to represent unambiguous +/// points in time, so we perform that conversion as a convenience and to +/// ensure that programmers with less time experience don't accidentally use +/// a less suitable time representation. +/// +/// - RFC 3339's contains _calendar time offset_ information, not time zone +/// information. This is enough to convert it to an unambiguous timestamp, +/// but it is not enough information to reliably work with calendar time. +/// Without the time zone and the time zone database it's not possible to +/// know what time period that offset is valid for, so it cannot be used +/// without risk of bugs. +/// +/// ## Behaviour details +/// +/// - Follows the grammar specified in section 5.6 Internet Date/Time Format of +/// RFC 3339 . +/// - The `T` and `Z` characters may alternatively be lower case `t` or `z`, +/// respectively. +/// - Full dates and full times must be separated by `T` or `t`. A space is also +/// permitted. +/// - Leap seconds rules are not considered. That is, any timestamp may +/// specify digts `00` - `60` for the seconds. +/// - Any part of a fractional second that cannot be represented in the +/// nanosecond precision is tructated. That is, for the time string, +/// `"1970-01-01T00:00:00.1234567899Z"`, the fractional second `.1234567899` +/// will be represented as `123_456_789` in the `Timestamp`. +/// +pub fn parse_rfc3339(input: String) -> Result(Timestamp, Nil) { + let bytes = bit_array.from_string(input) + + // Date + use #(year, bytes) <- result.try(parse_year(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) + use #(month, bytes) <- result.try(parse_month(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) + use #(day, bytes) <- result.try(parse_day(from: bytes, year:, month:)) + + use bytes <- result.try(accept_date_time_separator(from: bytes)) + + // Time + use #(hours, bytes) <- result.try(parse_hours(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) + use #(minutes, bytes) <- result.try(parse_minutes(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) + use #(seconds, bytes) <- result.try(parse_seconds(from: bytes)) + use #(second_fraction_as_nanoseconds, bytes) <- result.try( + parse_second_fraction_as_nanoseconds(from: bytes), + ) + + // Offset + use #(offset_seconds, bytes) <- result.try(parse_offset(from: bytes)) + + // Done + use Nil <- result.try(accept_empty(bytes)) + + Ok(from_date_time( + year:, + month:, + day:, + hours:, + minutes:, + seconds:, + second_fraction_as_nanoseconds:, + offset_seconds:, + )) +} + +fn parse_year(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + parse_digits(from: bytes, count: 4) +} + +fn parse_month(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(month, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 1 <= month && month <= 12 { + True -> Ok(#(month, bytes)) + False -> Error(Nil) + } +} + +fn parse_day( + from bytes: BitArray, + year year, + month month, +) -> Result(#(Int, BitArray), Nil) { + use #(day, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + + use max_day <- result.try(case month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 -> Ok(31) + 4 | 6 | 9 | 11 -> Ok(30) + 2 -> { + case is_leap_year(year) { + True -> Ok(29) + False -> Ok(28) + } + } + _ -> Error(Nil) + }) + + case 1 <= day && day <= max_day { + True -> Ok(#(day, bytes)) + False -> Error(Nil) + } +} + +// Implementation from RFC 3339 Appendix C +fn is_leap_year(year: Int) -> Bool { + year % 4 == 0 && { year % 100 != 0 || year % 400 == 0 } +} + +fn parse_hours(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(hours, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 0 <= hours && hours <= 23 { + True -> Ok(#(hours, bytes)) + False -> Error(Nil) + } +} + +fn parse_minutes(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(minutes, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + case 0 <= minutes && minutes <= 59 { + True -> Ok(#(minutes, bytes)) + False -> Error(Nil) + } +} + +fn parse_seconds(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(seconds, bytes) <- result.try(parse_digits(from: bytes, count: 2)) + // Max of 60 for leap seconds. We don't bother to check if this leap second + // actually occurred in the past or not. + case 0 <= seconds && seconds <= 60 { + True -> Ok(#(seconds, bytes)) + False -> Error(Nil) + } +} + +// Truncates any part of the fraction that is beyond the nanosecond precision. +fn parse_second_fraction_as_nanoseconds(from bytes: BitArray) { + case bytes { + <<".", byte, remaining_bytes:bytes>> + if byte_zero <= byte && byte <= byte_nine + -> { + do_parse_second_fraction_as_nanoseconds( + from: <>, + acc: 0, + power: nanoseconds_per_second, + ) + } + // bytes starts with a ".", which should introduce a fraction, but it does + // not, and so it is an ill-formed input. + <<".", _:bytes>> -> Error(Nil) + // bytes does not start with a "." so there is no fraction. Call this 0 + // nanoseconds. + _ -> Ok(#(0, bytes)) + } +} + +fn do_parse_second_fraction_as_nanoseconds( + from bytes: BitArray, + acc acc: Int, + power power: Int, +) -> Result(#(Int, BitArray), a) { + // Each digit place to the left in the fractional second is 10x fewer + // nanoseconds. + let power = power / 10 + + case bytes { + <> + if byte_zero <= byte && byte <= byte_nine && power < 1 + -> { + // We already have the max precision for nanoseconds. Truncate any + // remaining digits. + do_parse_second_fraction_as_nanoseconds( + from: remaining_bytes, + acc:, + power:, + ) + } + <> if byte_zero <= byte && byte <= byte_nine -> { + // We have not yet reached the precision limit. Parse the next digit. + let digit = byte - 0x30 + do_parse_second_fraction_as_nanoseconds( + from: remaining_bytes, + acc: acc + digit * power, + power:, + ) + } + _ -> Ok(#(acc, bytes)) + } +} + +fn parse_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + case bytes { + <<"Z", remaining_bytes:bytes>> | <<"z", remaining_bytes:bytes>> -> + Ok(#(0, remaining_bytes)) + _ -> parse_numeric_offset(bytes) + } +} + +fn parse_numeric_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + use #(sign, bytes) <- result.try(parse_sign(from: bytes)) + use #(hours, bytes) <- result.try(parse_hours(from: bytes)) + use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) + use #(minutes, bytes) <- result.try(parse_minutes(from: bytes)) + + let offset_seconds = offset_to_seconds(sign, hours:, minutes:) + + Ok(#(offset_seconds, bytes)) +} + +fn parse_sign(from bytes) { + case bytes { + <<"+", remaining_bytes:bytes>> -> Ok(#("+", remaining_bytes)) + <<"-", remaining_bytes:bytes>> -> Ok(#("-", remaining_bytes)) + _ -> Error(Nil) + } +} + +fn offset_to_seconds(sign, hours hours, minutes minutes) { + let abs_seconds = hours * seconds_per_hour + minutes * seconds_per_minute + + case sign { + "-" -> -abs_seconds + _ -> abs_seconds + } +} + +/// Parse and return the given number of digits from the given bytes. +/// +fn parse_digits( + from bytes: BitArray, + count count: Int, +) -> Result(#(Int, BitArray), Nil) { + do_parse_digits(from: bytes, count:, acc: 0, k: 0) +} + +fn do_parse_digits( + from bytes: BitArray, + count count: Int, + acc acc: Int, + k k: Int, +) -> Result(#(Int, BitArray), Nil) { + case bytes { + _ if k >= count -> Ok(#(acc, bytes)) + <> if byte_zero <= byte && byte <= byte_nine -> + do_parse_digits( + from: remaining_bytes, + count:, + acc: acc * 10 + { byte - 0x30 }, + k: k + 1, + ) + _ -> Error(Nil) + } +} + +/// Accept the given value from `bytes` and move past it if found. +/// +fn accept_byte(from bytes: BitArray, value value: Int) -> Result(BitArray, Nil) { + case bytes { + <> if byte == value -> Ok(remaining_bytes) + _ -> Error(Nil) + } +} + +fn accept_date_time_separator(from bytes: BitArray) -> Result(BitArray, Nil) { + case bytes { + <> + if byte == byte_t_uppercase + || byte == byte_t_lowercase + || byte == byte_space + -> Ok(remaining_bytes) + _ -> Error(Nil) + } +} + +fn accept_empty(from bytes: BitArray) -> Result(Nil, Nil) { + case bytes { + <<>> -> Ok(Nil) + _ -> Error(Nil) + } +} + +/// Note: The caller of this function must ensure that all inputs are valid. +/// +fn from_date_time( + year year: Int, + month month: Int, + day day: Int, + hours hours: Int, + minutes minutes: Int, + seconds seconds: Int, + second_fraction_as_nanoseconds second_fraction_as_nanoseconds: Int, + offset_seconds offset_seconds: Int, +) -> Timestamp { + let julian_seconds = + julian_seconds_from_parts(year:, month:, day:, hours:, minutes:, seconds:) + + let julian_seconds_since_epoch = julian_seconds - julian_seconds_unix_epoch + + Timestamp( + seconds: julian_seconds_since_epoch - offset_seconds, + nanoseconds: second_fraction_as_nanoseconds, + ) + |> normalise +} + +/// `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` +/// returns the number of Julian +/// seconds represented by the given arguments. +/// +/// Note: It is the callers responsibility to ensure the inputs are valid. +/// +/// See https://www.tondering.dk/claus/cal/julperiod.php#formula +/// +fn julian_seconds_from_parts( + year year: Int, + month month: Int, + day day: Int, + hours hours: Int, + minutes minutes: Int, + seconds seconds: Int, +) { + let julian_day_seconds = + julian_day_from_ymd(year:, month:, day:) * seconds_per_day + + julian_day_seconds + + { hours * seconds_per_hour } + + { minutes * seconds_per_minute } + + seconds +} + +/// Note: It is the callers responsibility to ensure the inputs are valid. +/// +/// See https://www.tondering.dk/claus/cal/julperiod.php#formula +/// +fn julian_day_from_ymd(year year: Int, month month: Int, day day: Int) -> Int { + let adjustment = { 14 - month } / 12 + let adjusted_year = year + 4800 - adjustment + let adjusted_month = month + 12 * adjustment - 3 + + day + + { { 153 * adjusted_month } + 2 } + / 5 + + 365 + * adjusted_year + + { adjusted_year / 4 } + - { adjusted_year / 100 } + + { adjusted_year / 400 } + - 32_045 +} + +/// Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January +/// 1970. +/// +pub fn from_unix_seconds(seconds: Int) -> Timestamp { + Timestamp(seconds, 0) +} + +/// Create a timestamp from a number of seconds and nanoseconds since 00:00:00 +/// UTC on 1 January 1970. +/// +/// # JavaScript int limitations +/// +/// Remember that JavaScript can only perfectly represent ints between positive +/// and negative 9,007,199,254,740,991! If you only use the nanosecond field +/// then you will almost certainly not get the date value you want due to this +/// loss of precision. Always use seconds primarily and then use nanoseconds +/// for the final sub-second adjustment. +/// +pub fn from_unix_seconds_and_nanoseconds( + seconds seconds: Int, + nanoseconds nanoseconds: Int, +) -> Timestamp { + Timestamp(seconds, nanoseconds) + |> normalise +} + +/// Convert the timestamp to a number of seconds since 00:00:00 UTC on 1 +/// January 1970. +/// +/// There may be some small loss of precision due to `Timestamp` being +/// nanosecond accurate and `Float` not being able to represent this. +/// +pub fn to_unix_seconds(timestamp: Timestamp) -> Float { + let seconds = int.to_float(timestamp.seconds) + let nanoseconds = int.to_float(timestamp.nanoseconds) + seconds +. { nanoseconds /. 1_000_000_000.0 } +} + +/// Convert the timestamp to a number of seconds and nanoseconds since 00:00:00 +/// UTC on 1 January 1970. There is no loss of precision with this conversion +/// on any target. +pub fn to_unix_seconds_and_nanoseconds(timestamp: Timestamp) -> #(Int, Int) { + #(timestamp.seconds, timestamp.nanoseconds) +} diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam@time@calendar.erl b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@calendar.erl new file mode 100644 index 0000000..e295d09 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@calendar.erl @@ -0,0 +1,468 @@ +-module(gleam@time@calendar). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/time/calendar.gleam"). +-export([local_offset/0, month_to_string/1, month_to_int/1, month_from_int/1, is_leap_year/1, is_valid_date/1, is_valid_time_of_day/1, naive_date_compare/2]). +-export_type([date/0, time_of_day/0, month/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " This module is for working with the Gregorian calendar, established by\n" + " Pope Gregory XIII in 1582!\n" + "\n" + " ## When should you use this module?\n" + "\n" + " > **tldr:** You probably want to use [`gleam/time/timestamp`](./timestamp.html)\n" + " > instead!\n" + "\n" + " Calendar time is difficult to work with programmatically, it is the source\n" + " of most time-related bugs in software. Compared to _epoch time_, which the\n" + " `gleam/time/timestamp` module uses, there are many disadvantages to\n" + " calendar time:\n" + "\n" + " - They are ambiguous if you don't know what time-zone is being used.\n" + "\n" + " - A time-zone database is required to understand calendar time even when\n" + " you have the time zone. These are large and your program has to\n" + " continously be updated as new versions of the database are published.\n" + "\n" + " - The type permits invalid states. e.g. `days` could be set to the number\n" + " 32, but this should not be possible!\n" + "\n" + " - There is not a single unique canonical value for each point in time,\n" + " thanks to time zones. Two different `Date` + `TimeOfDay` value pairs\n" + " could represent the same point in time. This means that you can't check\n" + " for time equality with `==` when using calendar types.\n" + "\n" + " - They are computationally complex, using a more memory to represent and\n" + " requiring a lot more CPU time to manipulate.\n" + "\n" + " There are also advantages to calendar time:\n" + "\n" + " - Calendar time is how human's talk about time, so if you want to show a\n" + " time or take a time from a human user then calendar time will make it\n" + " easier for them.\n" + "\n" + " - They can represent more abstract time periods such as \"New Year's Day\".\n" + " This may seem like an exact window of time at first, but really the\n" + " definition of \"New Year's Day\" is more fuzzy than that. When it starts\n" + " and ends will depend where in the world you are, so if you want to refer\n" + " to a day as a global concept instead of a fixed window of time for that\n" + " day in a specific location, then calendar time can represent that.\n" + "\n" + " So when should you use calendar time? These are our recommendations:\n" + "\n" + " - Default to `gleam/time/timestamp`, which is epoch time. It is\n" + " unambiguous, efficient, and significantly less likely to result in logic\n" + " bugs.\n" + "\n" + " - When writing time to a database or other data storage use epoch time,\n" + " using whatever epoch format it supports. For example, PostgreSQL\n" + " `timestamp` and `timestampz` are both epoch time, and `timestamp` is\n" + " preferred as it is more straightforward to use as your application is\n" + " also using epoch time.\n" + "\n" + " - When communicating with other computer systems continue to use epoch\n" + " time. For example, when sending times to another program you could\n" + " encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January\n" + " 1970).\n" + "\n" + " - When communicating with humans use epoch time internally, and convert\n" + " to-and-from calendar time at the last moment, when iteracting with the\n" + " human user. It may also help the users to also show the time as a fuzzy\n" + " duration from the present time, such as \"about 4 days ago\".\n" + "\n" + " - When representing \"fuzzy\" human time concepts that don't exact periods\n" + " in time, such as \"one month\" (varies depending on which month, which\n" + " year, and in which time zone) and \"Christmas Day\" (varies depending on\n" + " which year and time zone) then use calendar time.\n" + "\n" + " Any time you do use calendar time you should be extra careful! It is very\n" + " easy to make mistake with. Avoid it where possible.\n" + "\n" + " ## Time zone offsets\n" + "\n" + " This package includes the `utc_offset` value and the `local_offset`\n" + " function, which are the offset for the UTC time zone and get the time\n" + " offset the computer running the program is configured to respectively.\n" + "\n" + " If you need to use other offsets in your program then you will need to get\n" + " them from somewhere else, such as from a package which loads the\n" + " [IANA Time Zone Database](https://www.iana.org/time-zones), or from the\n" + " website visitor's web browser, which your frontend can send for you.\n" + "\n" + " ## Use in APIs\n" + "\n" + " If you are making an API such as a HTTP JSON API you are encouraged to use\n" + " Unix timestamps instead of calendar times.\n" +). + +-type date() :: {date, integer(), month(), integer()}. + +-type time_of_day() :: {time_of_day, integer(), integer(), integer(), integer()}. + +-type month() :: january | + february | + march | + april | + may | + june | + july | + august | + september | + october | + november | + december. + +-file("src/gleam/time/calendar.gleam", 147). +?DOC( + " Get the offset for the computer's currently configured time zone.\n" + "\n" + " Note this may not be the time zone that is correct to use for your user.\n" + " For example, if you are making a web application that runs on a server you\n" + " want _their_ computer's time zone, not yours.\n" + "\n" + " This is the _current local_ offset, not the current local time zone. This\n" + " means that while it will result in the expected outcome for the current\n" + " time, it may result in unexpected output if used with other timestamps. For\n" + " example: a timestamp that would locally be during daylight savings time if\n" + " is it not currently daylight savings time when this function is called.\n" +). +-spec local_offset() -> gleam@time@duration:duration(). +local_offset() -> + gleam@time@duration:seconds(gleam_time_ffi:local_time_offset_seconds()). + +-file("src/gleam/time/calendar.gleam", 163). +?DOC( + " Returns the English name for a month.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " month_to_string(April)\n" + " // -> \"April\"\n" + " ```\n" +). +-spec month_to_string(month()) -> binary(). +month_to_string(Month) -> + case Month of + january -> + <<"January"/utf8>>; + + february -> + <<"February"/utf8>>; + + march -> + <<"March"/utf8>>; + + april -> + <<"April"/utf8>>; + + may -> + <<"May"/utf8>>; + + june -> + <<"June"/utf8>>; + + july -> + <<"July"/utf8>>; + + august -> + <<"August"/utf8>>; + + september -> + <<"September"/utf8>>; + + october -> + <<"October"/utf8>>; + + november -> + <<"November"/utf8>>; + + december -> + <<"December"/utf8>> + end. + +-file("src/gleam/time/calendar.gleam", 188). +?DOC( + " Returns the number for the month, where January is 1 and December is 12.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " month_to_int(January)\n" + " // -> 1\n" + " ```\n" +). +-spec month_to_int(month()) -> integer(). +month_to_int(Month) -> + case Month of + january -> + 1; + + february -> + 2; + + march -> + 3; + + april -> + 4; + + may -> + 5; + + june -> + 6; + + july -> + 7; + + august -> + 8; + + september -> + 9; + + october -> + 10; + + november -> + 11; + + december -> + 12 + end. + +-file("src/gleam/time/calendar.gleam", 213). +?DOC( + " Returns the month for a given number, where January is 1 and December is 12.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " month_from_int(1)\n" + " // -> Ok(January)\n" + " ```\n" +). +-spec month_from_int(integer()) -> {ok, month()} | {error, nil}. +month_from_int(Month) -> + case Month of + 1 -> + {ok, january}; + + 2 -> + {ok, february}; + + 3 -> + {ok, march}; + + 4 -> + {ok, april}; + + 5 -> + {ok, may}; + + 6 -> + {ok, june}; + + 7 -> + {ok, july}; + + 8 -> + {ok, august}; + + 9 -> + {ok, september}; + + 10 -> + {ok, october}; + + 11 -> + {ok, november}; + + 12 -> + {ok, december}; + + _ -> + {error, nil} + end. + +-file("src/gleam/time/calendar.gleam", 290). +?DOC( + " Determines if a given year is a leap year.\n" + "\n" + " A leap year occurs every 4 years, except for years divisible by 100,\n" + " unless they are also divisible by 400.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " is_leap_year(2024)\n" + " // -> True\n" + " ```\n" + "\n" + " ```gleam\n" + " is_leap_year(2023)\n" + " // -> False\n" + " ```\n" +). +-spec is_leap_year(integer()) -> boolean(). +is_leap_year(Year) -> + case (Year rem 400) =:= 0 of + true -> + true; + + false -> + case (Year rem 100) =:= 0 of + true -> + false; + + false -> + (Year rem 4) =:= 0 + end + end. + +-file("src/gleam/time/calendar.gleam", 254). +?DOC( + " Checks if a given date is valid.\n" + "\n" + " This function properly accounts for leap years when validating February days.\n" + " A leap year occurs every 4 years, except for years divisible by 100,\n" + " unless they are also divisible by 400.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " is_valid_date(Date(2023, April, 15))\n" + " // -> True\n" + " ```\n" + "\n" + " ```gleam\n" + " is_valid_date(Date(2023, April, 31))\n" + " // -> False\n" + " ```\n" + "\n" + " ```gleam\n" + " is_valid_date(Date(2024, February, 29))\n" + " // -> True (2024 is a leap year)\n" + " ```\n" +). +-spec is_valid_date(date()) -> boolean(). +is_valid_date(Date) -> + {date, Year, Month, Day} = Date, + case Day < 1 of + true -> + false; + + false -> + case Month of + january -> + Day =< 31; + + march -> + Day =< 31; + + may -> + Day =< 31; + + july -> + Day =< 31; + + august -> + Day =< 31; + + october -> + Day =< 31; + + december -> + Day =< 31; + + april -> + Day =< 30; + + june -> + Day =< 30; + + september -> + Day =< 30; + + november -> + Day =< 30; + + february -> + Max_february_days = case is_leap_year(Year) of + true -> + 29; + + false -> + 28 + end, + Day =< Max_february_days + end + end. + +-file("src/gleam/time/calendar.gleam", 313). +?DOC( + " Checks if a time of day is valid.\n" + "\n" + " Validates that hours are 0-23, minutes are 0-59, seconds are 0-59,\n" + " and nanoseconds are 0-999,999,999.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789))\n" + " // -> True\n" + " ```\n" +). +-spec is_valid_time_of_day(time_of_day()) -> boolean(). +is_valid_time_of_day(Time) -> + {time_of_day, Hours, Minutes, Seconds, Nanoseconds} = Time, + (((((((Hours >= 0) andalso (Hours =< 23)) andalso (Minutes >= 0)) andalso (Minutes + =< 59)) + andalso (Seconds >= 0)) + andalso (Seconds =< 59)) + andalso (Nanoseconds >= 0)) + andalso (Nanoseconds =< 999999999). + +-file("src/gleam/time/calendar.gleam", 340). +?DOC( + " Naively compares two dates without any time zone information, returning an\n" + " order.\n" + "\n" + " ## Correctness\n" + "\n" + " This function compares dates without any time zone information, only using\n" + " the rules for the gregorian calendar. This is typically sufficient, but be\n" + " aware that in reality some time zones will change their calendar date\n" + " occasionally. This can result in days being skipped, out of order, or\n" + " happening multiple times.\n" + "\n" + " If you need real-world correct time ordering then use the\n" + " `gleam/time/timestamp` module instead.\n" +). +-spec naive_date_compare(date(), date()) -> gleam@order:order(). +naive_date_compare(One, Other) -> + _pipe = gleam@int:compare(erlang:element(2, One), erlang:element(2, Other)), + _pipe@1 = gleam@order:lazy_break_tie( + _pipe, + fun() -> + gleam@int:compare( + month_to_int(erlang:element(3, One)), + month_to_int(erlang:element(3, Other)) + ) + end + ), + gleam@order:lazy_break_tie( + _pipe@1, + fun() -> + gleam@int:compare(erlang:element(4, One), erlang:element(4, Other)) + end + ). diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam@time@duration.erl b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@duration.erl new file mode 100644 index 0000000..f560d78 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@duration.erl @@ -0,0 +1,394 @@ +-module(gleam@time@duration). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/time/duration.gleam"). +-export([approximate/1, compare/2, difference/2, add/2, seconds/1, minutes/1, hours/1, milliseconds/1, nanoseconds/1, to_seconds/1, to_seconds_and_nanoseconds/1, to_milliseconds/1, to_iso8601_string/1]). +-export_type([duration/0, unit/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-opaque duration() :: {duration, integer(), integer()}. + +-type unit() :: nanosecond | + microsecond | + millisecond | + second | + minute | + hour | + day | + week | + month | + year. + +-file("src/gleam/time/duration.gleam", 110). +?DOC( + " Ensure the duration is represented with `nanoseconds` being positive and\n" + " less than 1 second.\n" + "\n" + " This function does not change the amount of time that the duration refers\n" + " to, it only adjusts the values used to represent the time.\n" +). +-spec normalise(duration()) -> duration(). +normalise(Duration) -> + Multiplier = 1000000000, + Nanoseconds = case Multiplier of + 0 -> 0; + Gleam@denominator -> erlang:element(3, Duration) rem Gleam@denominator + end, + Overflow = erlang:element(3, Duration) - Nanoseconds, + Seconds = erlang:element(2, Duration) + (case Multiplier of + 0 -> 0; + Gleam@denominator@1 -> Overflow div Gleam@denominator@1 + end), + case Nanoseconds >= 0 of + true -> + {duration, Seconds, Nanoseconds}; + + false -> + {duration, Seconds - 1, Multiplier + Nanoseconds} + end. + +-file("src/gleam/time/duration.gleam", 76). +?DOC( + " Convert a duration to a number of the largest number of a unit, serving as\n" + " a rough description of the duration that a human can understand.\n" + "\n" + " The size used for each unit are described in the documentation for the\n" + " `Unit` type.\n" + "\n" + " ```gleam\n" + " seconds(125)\n" + " |> approximate\n" + " // -> #(2, Minute)\n" + " ```\n" + "\n" + " This function rounds _towards zero_. This means that if a duration is just\n" + " short of 2 days then it will approximate to 1 day.\n" + "\n" + " ```gleam\n" + " hours(47)\n" + " |> approximate\n" + " // -> #(1, Day)\n" + " ```\n" +). +-spec approximate(duration()) -> {integer(), unit()}. +approximate(Duration) -> + {duration, S, Ns} = Duration, + Minute = 60, + Hour = Minute * 60, + Day = Hour * 24, + Week = Day * 7, + Year = (Day * 365) + (Hour * 6), + Month = Year div 12, + Microsecond = 1000, + Millisecond = Microsecond * 1000, + case nil of + _ when S < 0 -> + {Amount, Unit} = begin + _pipe = {duration, - S, - Ns}, + _pipe@1 = normalise(_pipe), + approximate(_pipe@1) + end, + {- Amount, Unit}; + + _ when S >= Year -> + {case Year of + 0 -> 0; + Gleam@denominator -> S div Gleam@denominator + end, year}; + + _ when S >= Month -> + {case Month of + 0 -> 0; + Gleam@denominator@1 -> S div Gleam@denominator@1 + end, month}; + + _ when S >= Week -> + {case Week of + 0 -> 0; + Gleam@denominator@2 -> S div Gleam@denominator@2 + end, week}; + + _ when S >= Day -> + {case Day of + 0 -> 0; + Gleam@denominator@3 -> S div Gleam@denominator@3 + end, day}; + + _ when S >= Hour -> + {case Hour of + 0 -> 0; + Gleam@denominator@4 -> S div Gleam@denominator@4 + end, hour}; + + _ when S >= Minute -> + {case Minute of + 0 -> 0; + Gleam@denominator@5 -> S div Gleam@denominator@5 + end, minute}; + + _ when S > 0 -> + {S, second}; + + _ when Ns >= Millisecond -> + {case Millisecond of + 0 -> 0; + Gleam@denominator@6 -> Ns div Gleam@denominator@6 + end, millisecond}; + + _ when Ns >= Microsecond -> + {case Microsecond of + 0 -> 0; + Gleam@denominator@7 -> Ns div Gleam@denominator@7 + end, microsecond}; + + _ -> + {Ns, nanosecond} + end. + +-file("src/gleam/time/duration.gleam", 140). +?DOC( + " Compare one duration to another, indicating whether the first spans a\n" + " larger amount of time (and so is greater) or smaller amount of time (and so\n" + " is lesser) than the second.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " compare(seconds(1), seconds(2))\n" + " // -> order.Lt\n" + " ```\n" + "\n" + " Whether a duration is negative or positive doesn't matter for comparing\n" + " them, only the amount of time spanned matters.\n" + "\n" + " ```gleam\n" + " compare(seconds(-2), seconds(1))\n" + " // -> order.Gt\n" + " ```\n" +). +-spec compare(duration(), duration()) -> gleam@order:order(). +compare(Left, Right) -> + Parts = fun(X) -> case erlang:element(2, X) >= 0 of + true -> + {erlang:element(2, X), erlang:element(3, X)}; + + false -> + {(erlang:element(2, X) * -1) - 1, + 1000000000 - erlang:element(3, X)} + end end, + {Ls, Lns} = Parts(Left), + {Rs, Rns} = Parts(Right), + _pipe = gleam@int:compare(Ls, Rs), + gleam@order:break_tie(_pipe, gleam@int:compare(Lns, Rns)). + +-file("src/gleam/time/duration.gleam", 164). +?DOC( + " Calculate the difference between two durations.\n" + "\n" + " This is effectively substracting the first duration from the second.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " difference(seconds(1), seconds(5))\n" + " // -> seconds(4)\n" + " ```\n" +). +-spec difference(duration(), duration()) -> duration(). +difference(Left, Right) -> + _pipe = {duration, + erlang:element(2, Right) - erlang:element(2, Left), + erlang:element(3, Right) - erlang:element(3, Left)}, + normalise(_pipe). + +-file("src/gleam/time/duration.gleam", 178). +?DOC( + " Add two durations together.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " add(seconds(1), seconds(5))\n" + " // -> seconds(6)\n" + " ```\n" +). +-spec add(duration(), duration()) -> duration(). +add(Left, Right) -> + _pipe = {duration, + erlang:element(2, Left) + erlang:element(2, Right), + erlang:element(3, Left) + erlang:element(3, Right)}, + normalise(_pipe). + +-file("src/gleam/time/duration.gleam", 225). +-spec nanosecond_digits(integer(), integer(), binary()) -> binary(). +nanosecond_digits(N, Position, Acc) -> + case Position of + 9 -> + Acc; + + _ when (Acc =:= <<""/utf8>>) andalso ((N rem 10) =:= 0) -> + nanosecond_digits(N div 10, Position + 1, Acc); + + _ -> + Acc@1 = <<(erlang:integer_to_binary(N rem 10))/binary, Acc/binary>>, + nanosecond_digits(N div 10, Position + 1, Acc@1) + end. + +-file("src/gleam/time/duration.gleam", 239). +?DOC(" Create a duration of a number of seconds.\n"). +-spec seconds(integer()) -> duration(). +seconds(Amount) -> + {duration, Amount, 0}. + +-file("src/gleam/time/duration.gleam", 244). +?DOC(" Create a duration of a number of minutes.\n"). +-spec minutes(integer()) -> duration(). +minutes(Amount) -> + seconds(Amount * 60). + +-file("src/gleam/time/duration.gleam", 249). +?DOC(" Create a duration of a number of hours.\n"). +-spec hours(integer()) -> duration(). +hours(Amount) -> + seconds((Amount * 60) * 60). + +-file("src/gleam/time/duration.gleam", 254). +?DOC(" Create a duration of a number of milliseconds.\n"). +-spec milliseconds(integer()) -> duration(). +milliseconds(Amount) -> + Remainder = Amount rem 1000, + Overflow = Amount - Remainder, + Nanoseconds = Remainder * 1000000, + Seconds = Overflow div 1000, + _pipe = {duration, Seconds, Nanoseconds}, + normalise(_pipe). + +-file("src/gleam/time/duration.gleam", 273). +?DOC( + " Create a duration of a number of nanoseconds.\n" + "\n" + " # JavaScript int limitations\n" + "\n" + " Remember that JavaScript can only perfectly represent ints between positive\n" + " and negative 9,007,199,254,740,991! If you use a single call to this\n" + " function to create durations larger than that number of nanoseconds then\n" + " you will likely not get exactly the value you expect. Use `seconds` and\n" + " `milliseconds` as much as possible for large durations.\n" +). +-spec nanoseconds(integer()) -> duration(). +nanoseconds(Amount) -> + _pipe = {duration, 0, Amount}, + normalise(_pipe). + +-file("src/gleam/time/duration.gleam", 283). +?DOC( + " Convert the duration to a number of seconds.\n" + "\n" + " There may be some small loss of precision due to `Duration` being\n" + " nanosecond accurate and `Float` not being able to represent this.\n" +). +-spec to_seconds(duration()) -> float(). +to_seconds(Duration) -> + Seconds = erlang:float(erlang:element(2, Duration)), + Nanoseconds = erlang:float(erlang:element(3, Duration)), + Seconds + (Nanoseconds / 1000000000.0). + +-file("src/gleam/time/duration.gleam", 292). +?DOC( + " Convert the duration to a number of seconds and nanoseconds. There is no\n" + " loss of precision with this conversion on any target.\n" +). +-spec to_seconds_and_nanoseconds(duration()) -> {integer(), integer()}. +to_seconds_and_nanoseconds(Duration) -> + {erlang:element(2, Duration), erlang:element(3, Duration)}. + +-file("src/gleam/time/duration.gleam", 301). +?DOC( + " Convert the duration to a number of milliseconds.\n" + "\n" + " This conversion truncates any sub-millisecond precision. If you need\n" + " the full precision, use `to_seconds_and_nanoseconds` instead.\n" +). +-spec to_milliseconds(duration()) -> integer(). +to_milliseconds(Duration) -> + Seconds_ms = erlang:element(2, Duration) * 1000, + Nanos_ms = erlang:element(3, Duration) div 1000000, + Seconds_ms + Nanos_ms. + +-file("src/gleam/time/duration.gleam", 192). +?DOC( + " Convert the duration to an [ISO8601][1] formatted duration string.\n" + "\n" + " The ISO8601 duration format is ambiguous without context due to months and\n" + " years having different lengths, and because of leap seconds. This function\n" + " encodes the duration as days, hours, and seconds without any leap seconds.\n" + " Be sure to take this into account when using the duration strings.\n" + "\n" + " [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations\n" +). +-spec to_iso8601_string(duration()) -> binary(). +to_iso8601_string(Duration) -> + gleam@bool:guard( + Duration =:= {duration, 0, 0}, + <<"PT0S"/utf8>>, + fun() -> + Split = fun(Total, Limit) -> + Amount = case Limit of + 0 -> 0; + Gleam@denominator -> Total rem Gleam@denominator + end, + Remainder = case Limit of + 0 -> 0; + Gleam@denominator@1 -> (Total - Amount) div Gleam@denominator@1 + end, + {Amount, Remainder} + end, + {Seconds, Rest} = Split(erlang:element(2, Duration), 60), + {Minutes, Rest@1} = Split(Rest, 60), + {Hours, Rest@2} = Split(Rest@1, 24), + Days = Rest@2, + Add = fun(Out, Value, Unit) -> case Value of + 0 -> + Out; + + _ -> + <<<>/binary, + Unit/binary>> + end end, + Output = begin + _pipe = <<"P"/utf8>>, + _pipe@1 = Add(_pipe, Days, <<"D"/utf8>>), + _pipe@2 = gleam@string:append(_pipe@1, <<"T"/utf8>>), + _pipe@3 = Add(_pipe@2, Hours, <<"H"/utf8>>), + Add(_pipe@3, Minutes, <<"M"/utf8>>) + end, + case {Seconds, erlang:element(3, Duration)} of + {0, 0} -> + Output; + + {_, 0} -> + <<<>/binary, + "S"/utf8>>; + + {_, _} -> + F = nanosecond_digits( + erlang:element(3, Duration), + 0, + <<""/utf8>> + ), + <<<<<<<>/binary, + "."/utf8>>/binary, + F/binary>>/binary, + "S"/utf8>> + end + end + ). diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam@time@timestamp.erl b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@timestamp.erl new file mode 100644 index 0000000..70e15b5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam@time@timestamp.erl @@ -0,0 +1,1209 @@ +-module(gleam@time@timestamp). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleam/time/timestamp.gleam"). +-export([compare/2, system_time/0, difference/2, add/2, subtract/2, to_calendar/2, to_rfc3339/2, from_unix_seconds/1, from_unix_seconds_and_nanoseconds/2, to_unix_seconds/1, to_unix_seconds_and_nanoseconds/1, from_calendar/3, parse_rfc3339/1]). +-export_type([timestamp/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Welcome to the timestamp module! This module and its `Timestamp` type are\n" + " what you will be using most commonly when working with time in Gleam.\n" + "\n" + " A timestamp represents a moment in time, represented as an amount of time\n" + " since the calendar time 00:00:00 UTC on 1 January 1970, also known as the\n" + " _Unix epoch_.\n" + "\n" + " # Wall clock time and monotonicity\n" + "\n" + " Time is very complicated, especially on computers! While they generally do\n" + " a good job of keeping track of what the time is, computers can get\n" + " out-of-sync and start to report a time that is too late or too early. Most\n" + " computers use \"network time protocol\" to tell each other what they think\n" + " the time is, and computers that realise they are running too fast or too\n" + " slow will adjust their clock to correct it. When this happens it can seem\n" + " to your program that the current time has changed, and it may have even\n" + " jumped backwards in time!\n" + "\n" + " This measure of time is called _wall clock time_, and it is what people\n" + " commonly think of when they think of time. It is important to be aware that\n" + " it can go backwards, and your program must not rely on it only ever going\n" + " forwards at a steady rate. For example, for tracking what order events happen\n" + " in. \n" + "\n" + " This module uses wall clock time. If your program needs time values to always\n" + " increase you will need a _monotonic_ time instead. It's uncommon that you\n" + " would need monotonic time, one example might be if you're making a\n" + " benchmarking framework.\n" + "\n" + " The exact way that time works will depend on what runtime you use. The\n" + " Erlang documentation on time has a lot of detail about time generally as well\n" + " as how it works on the BEAM, it is worth reading.\n" + " .\n" + "\n" + " # Converting to local calendar time\n" + "\n" + " Timestamps don't take into account time zones, so a moment in time will\n" + " have the same timestamp value regardless of where you are in the world. To\n" + " convert them to local time you will need to know the offset for the time\n" + " zone you wish to use, likely from a time zone database. See the\n" + " `gleam/time/calendar` module for more information.\n" + "\n" +). + +-opaque timestamp() :: {timestamp, integer(), integer()}. + +-file("src/gleam/time/timestamp.gleam", 119). +?DOC( + " Ensure the time is represented with `nanoseconds` being positive and less\n" + " than 1 second.\n" + "\n" + " This function does not change the time that the timestamp refers to, it\n" + " only adjusts the values used to represent the time.\n" +). +-spec normalise(timestamp()) -> timestamp(). +normalise(Timestamp) -> + Multiplier = 1000000000, + Nanoseconds = case Multiplier of + 0 -> 0; + Gleam@denominator -> erlang:element(3, Timestamp) rem Gleam@denominator + end, + Overflow = erlang:element(3, Timestamp) - Nanoseconds, + Seconds = erlang:element(2, Timestamp) + (case Multiplier of + 0 -> 0; + Gleam@denominator@1 -> Overflow div Gleam@denominator@1 + end), + case Nanoseconds >= 0 of + true -> + {timestamp, Seconds, Nanoseconds}; + + false -> + {timestamp, Seconds - 1, Multiplier + Nanoseconds} + end. + +-file("src/gleam/time/timestamp.gleam", 141). +?DOC( + " Compare one timestamp to another, indicating whether the first is further\n" + " into the future (greater) or further into the past (lesser) than the\n" + " second.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " compare(from_unix_seconds(1), from_unix_seconds(2))\n" + " // -> order.Lt\n" + " ```\n" +). +-spec compare(timestamp(), timestamp()) -> gleam@order:order(). +compare(Left, Right) -> + gleam@order:break_tie( + gleam@int:compare(erlang:element(2, Left), erlang:element(2, Right)), + gleam@int:compare(erlang:element(3, Left), erlang:element(3, Right)) + ). + +-file("src/gleam/time/timestamp.gleam", 160). +?DOC( + " Get the current system time.\n" + "\n" + " Note this time is not unique or monotonic, it could change at any time or\n" + " even go backwards! The exact behaviour will depend on the runtime used. See\n" + " the module documentation for more information.\n" + "\n" + " On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses\n" + " [`Date.now`][2].\n" + "\n" + " [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1\n" + " [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now\n" +). +-spec system_time() -> timestamp(). +system_time() -> + {Seconds, Nanoseconds} = gleam_time_ffi:system_time(), + normalise({timestamp, Seconds, Nanoseconds}). + +-file("src/gleam/time/timestamp.gleam", 180). +?DOC( + " Calculate the difference between two timestamps.\n" + "\n" + " This is effectively substracting the first timestamp from the second.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " difference(from_unix_seconds(1), from_unix_seconds(5))\n" + " // -> duration.seconds(4)\n" + " ```\n" +). +-spec difference(timestamp(), timestamp()) -> gleam@time@duration:duration(). +difference(Left, Right) -> + Seconds = gleam@time@duration:seconds( + erlang:element(2, Right) - erlang:element(2, Left) + ), + Nanoseconds = gleam@time@duration:nanoseconds( + erlang:element(3, Right) - erlang:element(3, Left) + ), + gleam@time@duration:add(Seconds, Nanoseconds). + +-file("src/gleam/time/timestamp.gleam", 195). +?DOC( + " Add a duration to a timestamp.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " add(from_unix_seconds(1000), duration.seconds(5))\n" + " // -> from_unix_seconds(1005)\n" + " ```\n" +). +-spec add(timestamp(), gleam@time@duration:duration()) -> timestamp(). +add(Timestamp, Duration) -> + {Seconds, Nanoseconds} = gleam@time@duration:to_seconds_and_nanoseconds( + Duration + ), + _pipe = {timestamp, + erlang:element(2, Timestamp) + Seconds, + erlang:element(3, Timestamp) + Nanoseconds}, + normalise(_pipe). + +-file("src/gleam/time/timestamp.gleam", 210). +?DOC( + " Subtract a duration from a timestamp.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " subtract(from_unix_seconds(1000), duration.seconds(5))\n" + " // -> from_unix_seconds(955)\n" + " ```\n" +). +-spec subtract(timestamp(), gleam@time@duration:duration()) -> timestamp(). +subtract(Timestamp, Duration) -> + {Seconds, Nanoseconds} = gleam@time@duration:to_seconds_and_nanoseconds( + Duration + ), + _pipe = {timestamp, + erlang:element(2, Timestamp) - Seconds, + erlang:element(3, Timestamp) - Nanoseconds}, + normalise(_pipe). + +-file("src/gleam/time/timestamp.gleam", 277). +-spec pad_digit(integer(), integer()) -> binary(). +pad_digit(Digit, Desired_length) -> + _pipe = erlang:integer_to_binary(Digit), + gleam@string:pad_start(_pipe, Desired_length, <<"0"/utf8>>). + +-file("src/gleam/time/timestamp.gleam", 323). +-spec duration_to_minutes(gleam@time@duration:duration()) -> integer(). +duration_to_minutes(Duration) -> + erlang:round(gleam@time@duration:to_seconds(Duration) / 60.0). + +-file("src/gleam/time/timestamp.gleam", 385). +-spec modulo(integer(), integer()) -> integer(). +modulo(N, M) -> + case gleam@int:modulo(N, M) of + {ok, N@1} -> + N@1; + + {error, _} -> + 0 + end. + +-file("src/gleam/time/timestamp.gleam", 392). +-spec floored_div(integer(), float()) -> integer(). +floored_div(Numerator, Denominator) -> + N = case Denominator of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> erlang:float(Numerator) / Gleam@denominator + end, + erlang:round(math:floor(N)). + +-file("src/gleam/time/timestamp.gleam", 398). +-spec to_civil(integer()) -> {integer(), integer(), integer()}. +to_civil(Minutes) -> + Raw_day = floored_div(Minutes, (60.0 * 24.0)) + 719468, + Era = case Raw_day >= 0 of + true -> + Raw_day div 146097; + + false -> + (Raw_day - 146096) div 146097 + end, + Day_of_era = Raw_day - (Era * 146097), + Year_of_era = (((Day_of_era - (Day_of_era div 1460)) + (Day_of_era div 36524)) + - (Day_of_era div 146096)) + div 365, + Year = Year_of_era + (Era * 400), + Day_of_year = Day_of_era - (((365 * Year_of_era) + (Year_of_era div 4)) - (Year_of_era + div 100)), + Mp = ((5 * Day_of_year) + 2) div 153, + Month = case Mp < 10 of + true -> + Mp + 3; + + false -> + Mp - 9 + end, + Day = (Day_of_year - (((153 * Mp) + 2) div 5)) + 1, + Year@1 = case Month =< 2 of + true -> + Year + 1; + + false -> + Year + end, + {Year@1, Month, Day}. + +-file("src/gleam/time/timestamp.gleam", 327). +-spec to_calendar_from_offset(timestamp(), integer()) -> {integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. +to_calendar_from_offset(Timestamp, Offset) -> + Total = erlang:element(2, Timestamp) + (Offset * 60), + Seconds = modulo(Total, 60), + Total_minutes = floored_div(Total, 60.0), + Minutes = modulo(Total, 60 * 60) div 60, + Hours = case (60 * 60) of + 0 -> 0; + Gleam@denominator -> modulo(Total, (24 * 60) * 60) div Gleam@denominator + end, + {Year, Month, Day} = to_civil(Total_minutes), + {Year, Month, Day, Hours, Minutes, Seconds}. + +-file("src/gleam/time/timestamp.gleam", 296). +?DOC( + " Convert a `Timestamp` to calendar time, suitable for presenting to a human\n" + " to read.\n" + "\n" + " If you want a machine to use the time value then you should not use this\n" + " function and should instead keep it as a timestamp. See the documentation\n" + " for the `gleam/time/calendar` module for more information.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " timestamp.from_unix_seconds(0)\n" + " |> timestamp.to_calendar(calendar.utc_offset)\n" + " // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))\n" + " ```\n" +). +-spec to_calendar(timestamp(), gleam@time@duration:duration()) -> {gleam@time@calendar:date(), + gleam@time@calendar:time_of_day()}. +to_calendar(Timestamp, Offset) -> + Offset@1 = duration_to_minutes(Offset), + {Year, Month, Day, Hours, Minutes, Seconds} = to_calendar_from_offset( + Timestamp, + Offset@1 + ), + Month@1 = case Month of + 1 -> + january; + + 2 -> + february; + + 3 -> + march; + + 4 -> + april; + + 5 -> + may; + + 6 -> + june; + + 7 -> + july; + + 8 -> + august; + + 9 -> + september; + + 10 -> + october; + + 11 -> + november; + + _ -> + december + end, + Nanoseconds = erlang:element(3, Timestamp), + Date = {date, Year, Month@1, Day}, + Time = {time_of_day, Hours, Minutes, Seconds, Nanoseconds}, + {Date, Time}. + +-file("src/gleam/time/timestamp.gleam", 461). +-spec do_remove_trailing_zeros(list(integer())) -> list(integer()). +do_remove_trailing_zeros(Reversed_digits) -> + case Reversed_digits of + [] -> + []; + + [Digit | Digits] when Digit =:= 0 -> + do_remove_trailing_zeros(Digits); + + Reversed_digits@1 -> + lists:reverse(Reversed_digits@1) + end. + +-file("src/gleam/time/timestamp.gleam", 455). +?DOC(" Given a list of digits, return new list with any trailing zeros removed.\n"). +-spec remove_trailing_zeros(list(integer())) -> list(integer()). +remove_trailing_zeros(Digits) -> + Reversed_digits = lists:reverse(Digits), + do_remove_trailing_zeros(Reversed_digits). + +-file("src/gleam/time/timestamp.gleam", 476). +-spec do_get_zero_padded_digits(integer(), list(integer()), integer()) -> list(integer()). +do_get_zero_padded_digits(Number, Digits, Count) -> + case Number of + Number@1 when (Number@1 =< 0) andalso (Count >= 9) -> + Digits; + + Number@2 when Number@2 =< 0 -> + do_get_zero_padded_digits(Number@2, [0 | Digits], Count + 1); + + Number@3 -> + Digit = Number@3 rem 10, + Number@4 = floored_div(Number@3, 10.0), + do_get_zero_padded_digits(Number@4, [Digit | Digits], Count + 1) + end. + +-file("src/gleam/time/timestamp.gleam", 472). +?DOC( + " Returns the list of digits of `number`. If the number of digits is less \n" + " than 9, the result is zero-padded at the front.\n" +). +-spec get_zero_padded_digits(integer()) -> list(integer()). +get_zero_padded_digits(Number) -> + do_get_zero_padded_digits(Number, [], 0). + +-file("src/gleam/time/timestamp.gleam", 435). +?DOC( + " Converts nanoseconds into a `String` representation of fractional seconds.\n" + " \n" + " Assumes that `nanoseconds < 1_000_000_000`, which will be true for any \n" + " normalised timestamp.\n" +). +-spec show_second_fraction(integer()) -> binary(). +show_second_fraction(Nanoseconds) -> + case gleam@int:compare(Nanoseconds, 0) of + lt -> + <<""/utf8>>; + + eq -> + <<""/utf8>>; + + gt -> + Second_fraction_part = begin + _pipe = Nanoseconds, + _pipe@1 = get_zero_padded_digits(_pipe), + _pipe@2 = remove_trailing_zeros(_pipe@1), + _pipe@3 = gleam@list:map( + _pipe@2, + fun erlang:integer_to_binary/1 + ), + gleam@string:join(_pipe@3, <<""/utf8>>) + end, + <<"."/utf8, Second_fraction_part/binary>> + end. + +-file("src/gleam/time/timestamp.gleam", 255). +?DOC( + " Convert a timestamp to a RFC 3339 formatted time string, with an offset\n" + " supplied as an additional argument.\n" + "\n" + " The output of this function is also ISO 8601 compatible so long as the\n" + " offset not negative. Offsets have at-most minute precision, so an offset\n" + " with higher precision will be rounded to the nearest minute.\n" + "\n" + " If you are making an API such as a HTTP JSON API you are encouraged to use\n" + " Unix timestamps instead of this format or ISO 8601. Unix timestamps are a\n" + " better choice as they don't contain offset information. Consider:\n" + "\n" + " - UTC offsets are not time zones. This does not and cannot tell us the time\n" + " zone in which the date was recorded. So what are we supposed to do with\n" + " this information?\n" + " - Users typically want dates formatted according to their local time zone.\n" + " What if the provided UTC offset is different from the current user's time\n" + " zone? What are we supposed to do with it then?\n" + " - Despite it being useless (or worse, a source of bugs), the UTC offset\n" + " creates a larger payload to transfer.\n" + "\n" + " They also uses more memory than a unix timestamp. The way they are better\n" + " than Unix timestamp is that it is easier for a human to read them, but\n" + " this is a hinderance that tooling can remedy, and APIs are not primarily\n" + " for humans.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000)\n" + " |> to_rfc3339(calendar.utc_offset)\n" + " // -> \"1970-01-01T00:16:40.123Z\"\n" + " ```\n" + "\n" + " ```gleam\n" + " timestamp.from_unix_seconds(1000)\n" + " |> to_rfc3339(duration.seconds(3600))\n" + " // -> \"1970-01-01T01:16:40+01:00\"\n" + " ```\n" +). +-spec to_rfc3339(timestamp(), gleam@time@duration:duration()) -> binary(). +to_rfc3339(Timestamp, Offset) -> + Offset@1 = duration_to_minutes(Offset), + {Years, Months, Days, Hours, Minutes, Seconds} = to_calendar_from_offset( + Timestamp, + Offset@1 + ), + Offset_minutes = modulo(Offset@1, 60), + Offset_hours = gleam@int:absolute_value(floored_div(Offset@1, 60.0)), + N2 = fun(_capture) -> pad_digit(_capture, 2) end, + N4 = fun(_capture@1) -> pad_digit(_capture@1, 4) end, + Out = <<""/utf8>>, + Out@1 = <<<<<<<<<>/binary, "-"/utf8>>/binary, + (N2(Months))/binary>>/binary, + "-"/utf8>>/binary, + (N2(Days))/binary>>, + Out@2 = <>, + Out@3 = <<<<<<<<<>/binary, ":"/utf8>>/binary, + (N2(Minutes))/binary>>/binary, + ":"/utf8>>/binary, + (N2(Seconds))/binary>>, + Out@4 = <>, + case gleam@int:compare(Offset@1, 0) of + eq -> + <>; + + gt -> + <<<<<<<>/binary, (N2(Offset_hours))/binary>>/binary, + ":"/utf8>>/binary, + (N2(Offset_minutes))/binary>>; + + lt -> + <<<<<<<>/binary, (N2(Offset_hours))/binary>>/binary, + ":"/utf8>>/binary, + (N2(Offset_minutes))/binary>> + end. + +-file("src/gleam/time/timestamp.gleam", 626). +-spec is_leap_year(integer()) -> boolean(). +is_leap_year(Year) -> + ((Year rem 4) =:= 0) andalso (((Year rem 100) /= 0) orelse ((Year rem 400) + =:= 0)). + +-file("src/gleam/time/timestamp.gleam", 730). +-spec parse_sign(bitstring()) -> {ok, {binary(), bitstring()}} | {error, nil}. +parse_sign(Bytes) -> + case Bytes of + <<"+"/utf8, Remaining_bytes/binary>> -> + {ok, {<<"+"/utf8>>, Remaining_bytes}}; + + <<"-"/utf8, Remaining_bytes@1/binary>> -> + {ok, {<<"-"/utf8>>, Remaining_bytes@1}}; + + _ -> + {error, nil} + end. + +-file("src/gleam/time/timestamp.gleam", 777). +?DOC(" Accept the given value from `bytes` and move past it if found.\n"). +-spec accept_byte(bitstring(), integer()) -> {ok, bitstring()} | {error, nil}. +accept_byte(Bytes, Value) -> + case Bytes of + <> when Byte =:= Value -> + {ok, Remaining_bytes}; + + _ -> + {error, nil} + end. + +-file("src/gleam/time/timestamp.gleam", 795). +-spec accept_empty(bitstring()) -> {ok, nil} | {error, nil}. +accept_empty(Bytes) -> + case Bytes of + <<>> -> + {ok, nil}; + + _ -> + {error, nil} + end. + +-file("src/gleam/time/timestamp.gleam", 855). +?DOC( + " Note: It is the callers responsibility to ensure the inputs are valid.\n" + " \n" + " See https://www.tondering.dk/claus/cal/julperiod.php#formula\n" +). +-spec julian_day_from_ymd(integer(), integer(), integer()) -> integer(). +julian_day_from_ymd(Year, Month, Day) -> + Adjustment = (14 - Month) div 12, + Adjusted_year = (Year + 4800) - Adjustment, + Adjusted_month = (Month + (12 * Adjustment)) - 3, + (((((Day + (((153 * Adjusted_month) + 2) div 5)) + (365 * Adjusted_year)) + (Adjusted_year + div 4)) + - (Adjusted_year div 100)) + + (Adjusted_year div 400)) + - 32045. + +-file("src/gleam/time/timestamp.gleam", 874). +?DOC( + " Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January\n" + " 1970.\n" +). +-spec from_unix_seconds(integer()) -> timestamp(). +from_unix_seconds(Seconds) -> + {timestamp, Seconds, 0}. + +-file("src/gleam/time/timestamp.gleam", 889). +?DOC( + " Create a timestamp from a number of seconds and nanoseconds since 00:00:00\n" + " UTC on 1 January 1970.\n" + "\n" + " # JavaScript int limitations\n" + "\n" + " Remember that JavaScript can only perfectly represent ints between positive\n" + " and negative 9,007,199,254,740,991! If you only use the nanosecond field\n" + " then you will almost certainly not get the date value you want due to this\n" + " loss of precision. Always use seconds primarily and then use nanoseconds\n" + " for the final sub-second adjustment.\n" +). +-spec from_unix_seconds_and_nanoseconds(integer(), integer()) -> timestamp(). +from_unix_seconds_and_nanoseconds(Seconds, Nanoseconds) -> + _pipe = {timestamp, Seconds, Nanoseconds}, + normalise(_pipe). + +-file("src/gleam/time/timestamp.gleam", 903). +?DOC( + " Convert the timestamp to a number of seconds since 00:00:00 UTC on 1\n" + " January 1970.\n" + "\n" + " There may be some small loss of precision due to `Timestamp` being\n" + " nanosecond accurate and `Float` not being able to represent this.\n" +). +-spec to_unix_seconds(timestamp()) -> float(). +to_unix_seconds(Timestamp) -> + Seconds = erlang:float(erlang:element(2, Timestamp)), + Nanoseconds = erlang:float(erlang:element(3, Timestamp)), + Seconds + (Nanoseconds / 1000000000.0). + +-file("src/gleam/time/timestamp.gleam", 912). +?DOC( + " Convert the timestamp to a number of seconds and nanoseconds since 00:00:00\n" + " UTC on 1 January 1970. There is no loss of precision with this conversion\n" + " on any target.\n" +). +-spec to_unix_seconds_and_nanoseconds(timestamp()) -> {integer(), integer()}. +to_unix_seconds_and_nanoseconds(Timestamp) -> + {erlang:element(2, Timestamp), erlang:element(3, Timestamp)}. + +-file("src/gleam/time/timestamp.gleam", 738). +-spec offset_to_seconds(binary(), integer(), integer()) -> integer(). +offset_to_seconds(Sign, Hours, Minutes) -> + Abs_seconds = (Hours * 3600) + (Minutes * 60), + case Sign of + <<"-"/utf8>> -> + - Abs_seconds; + + _ -> + Abs_seconds + end. + +-file("src/gleam/time/timestamp.gleam", 834). +?DOC( + " `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` \n" + " returns the number of Julian \n" + " seconds represented by the given arguments.\n" + " \n" + " Note: It is the callers responsibility to ensure the inputs are valid.\n" + " \n" + " See https://www.tondering.dk/claus/cal/julperiod.php#formula\n" +). +-spec julian_seconds_from_parts( + integer(), + integer(), + integer(), + integer(), + integer(), + integer() +) -> integer(). +julian_seconds_from_parts(Year, Month, Day, Hours, Minutes, Seconds) -> + Julian_day_seconds = julian_day_from_ymd(Year, Month, Day) * 86400, + ((Julian_day_seconds + (Hours * 3600)) + (Minutes * 60)) + Seconds. + +-file("src/gleam/time/timestamp.gleam", 677). +-spec do_parse_second_fraction_as_nanoseconds(bitstring(), integer(), integer()) -> {ok, + {integer(), bitstring()}} | + {error, any()}. +do_parse_second_fraction_as_nanoseconds(Bytes, Acc, Power) -> + Power@1 = Power div 10, + case Bytes of + <> when ((16#30 =< Byte) andalso (Byte =< 16#39)) andalso (Power@1 < 1) -> + do_parse_second_fraction_as_nanoseconds( + Remaining_bytes, + Acc, + Power@1 + ); + + <> when (16#30 =< Byte@1) andalso (Byte@1 =< 16#39) -> + Digit = Byte@1 - 16#30, + do_parse_second_fraction_as_nanoseconds( + Remaining_bytes@1, + Acc + (Digit * Power@1), + Power@1 + ); + + _ -> + {ok, {Acc, Bytes}} + end. + +-file("src/gleam/time/timestamp.gleam", 657). +-spec parse_second_fraction_as_nanoseconds(bitstring()) -> {ok, + {integer(), bitstring()}} | + {error, nil}. +parse_second_fraction_as_nanoseconds(Bytes) -> + case Bytes of + <<"."/utf8, Byte, Remaining_bytes/binary>> when (16#30 =< Byte) andalso (Byte =< 16#39) -> + do_parse_second_fraction_as_nanoseconds( + <>, + 0, + 1000000000 + ); + + <<"."/utf8, _/binary>> -> + {error, nil}; + + _ -> + {ok, {0, Bytes}} + end. + +-file("src/gleam/time/timestamp.gleam", 756). +-spec do_parse_digits(bitstring(), integer(), integer(), integer()) -> {ok, + {integer(), bitstring()}} | + {error, nil}. +do_parse_digits(Bytes, Count, Acc, K) -> + case Bytes of + _ when K >= Count -> + {ok, {Acc, Bytes}}; + + <> when (16#30 =< Byte) andalso (Byte =< 16#39) -> + do_parse_digits( + Remaining_bytes, + Count, + (Acc * 10) + (Byte - 16#30), + K + 1 + ); + + _ -> + {error, nil} + end. + +-file("src/gleam/time/timestamp.gleam", 749). +?DOC(" Parse and return the given number of digits from the given bytes.\n"). +-spec parse_digits(bitstring(), integer()) -> {ok, {integer(), bitstring()}} | + {error, nil}. +parse_digits(Bytes, Count) -> + do_parse_digits(Bytes, Count, 0, 0). + +-file("src/gleam/time/timestamp.gleam", 588). +-spec parse_year(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. +parse_year(Bytes) -> + parse_digits(Bytes, 4). + +-file("src/gleam/time/timestamp.gleam", 592). +-spec parse_month(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. +parse_month(Bytes) -> + gleam@result:'try'( + parse_digits(Bytes, 2), + fun(_use0) -> + {Month, Bytes@1} = _use0, + case (1 =< Month) andalso (Month =< 12) of + true -> + {ok, {Month, Bytes@1}}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/time/timestamp.gleam", 600). +-spec parse_day(bitstring(), integer(), integer()) -> {ok, + {integer(), bitstring()}} | + {error, nil}. +parse_day(Bytes, Year, Month) -> + gleam@result:'try'( + parse_digits(Bytes, 2), + fun(_use0) -> + {Day, Bytes@1} = _use0, + gleam@result:'try'(case Month of + 1 -> + {ok, 31}; + + 3 -> + {ok, 31}; + + 5 -> + {ok, 31}; + + 7 -> + {ok, 31}; + + 8 -> + {ok, 31}; + + 10 -> + {ok, 31}; + + 12 -> + {ok, 31}; + + 4 -> + {ok, 30}; + + 6 -> + {ok, 30}; + + 9 -> + {ok, 30}; + + 11 -> + {ok, 30}; + + 2 -> + case is_leap_year(Year) of + true -> + {ok, 29}; + + false -> + {ok, 28} + end; + + _ -> + {error, nil} + end, fun(Max_day) -> case (1 =< Day) andalso (Day =< Max_day) of + true -> + {ok, {Day, Bytes@1}}; + + false -> + {error, nil} + end end) + end + ). + +-file("src/gleam/time/timestamp.gleam", 630). +-spec parse_hours(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. +parse_hours(Bytes) -> + gleam@result:'try'( + parse_digits(Bytes, 2), + fun(_use0) -> + {Hours, Bytes@1} = _use0, + case (0 =< Hours) andalso (Hours =< 23) of + true -> + {ok, {Hours, Bytes@1}}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/time/timestamp.gleam", 638). +-spec parse_minutes(bitstring()) -> {ok, {integer(), bitstring()}} | + {error, nil}. +parse_minutes(Bytes) -> + gleam@result:'try'( + parse_digits(Bytes, 2), + fun(_use0) -> + {Minutes, Bytes@1} = _use0, + case (0 =< Minutes) andalso (Minutes =< 59) of + true -> + {ok, {Minutes, Bytes@1}}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/time/timestamp.gleam", 646). +-spec parse_seconds(bitstring()) -> {ok, {integer(), bitstring()}} | + {error, nil}. +parse_seconds(Bytes) -> + gleam@result:'try'( + parse_digits(Bytes, 2), + fun(_use0) -> + {Seconds, Bytes@1} = _use0, + case (0 =< Seconds) andalso (Seconds =< 60) of + true -> + {ok, {Seconds, Bytes@1}}; + + false -> + {error, nil} + end + end + ). + +-file("src/gleam/time/timestamp.gleam", 719). +-spec parse_numeric_offset(bitstring()) -> {ok, {integer(), bitstring()}} | + {error, nil}. +parse_numeric_offset(Bytes) -> + gleam@result:'try'( + parse_sign(Bytes), + fun(_use0) -> + {Sign, Bytes@1} = _use0, + gleam@result:'try'( + parse_hours(Bytes@1), + fun(_use0@1) -> + {Hours, Bytes@2} = _use0@1, + gleam@result:'try'( + accept_byte(Bytes@2, 16#3A), + fun(Bytes@3) -> + gleam@result:'try'( + parse_minutes(Bytes@3), + fun(_use0@2) -> + {Minutes, Bytes@4} = _use0@2, + Offset_seconds = offset_to_seconds( + Sign, + Hours, + Minutes + ), + {ok, {Offset_seconds, Bytes@4}} + end + ) + end + ) + end + ) + end + ). + +-file("src/gleam/time/timestamp.gleam", 711). +-spec parse_offset(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. +parse_offset(Bytes) -> + case Bytes of + <<"Z"/utf8, Remaining_bytes/binary>> -> + {ok, {0, Remaining_bytes}}; + + <<"z"/utf8, Remaining_bytes/binary>> -> + {ok, {0, Remaining_bytes}}; + + _ -> + parse_numeric_offset(Bytes) + end. + +-file("src/gleam/time/timestamp.gleam", 784). +-spec accept_date_time_separator(bitstring()) -> {ok, bitstring()} | + {error, nil}. +accept_date_time_separator(Bytes) -> + case Bytes of + <> when ((Byte =:= 16#54) orelse (Byte =:= 16#74)) orelse (Byte =:= 16#20) -> + {ok, Remaining_bytes}; + + _ -> + {error, nil} + end. + +-file("src/gleam/time/timestamp.gleam", 804). +?DOC(" Note: The caller of this function must ensure that all inputs are valid.\n"). +-spec from_date_time( + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer() +) -> timestamp(). +from_date_time( + Year, + Month, + Day, + Hours, + Minutes, + Seconds, + Second_fraction_as_nanoseconds, + Offset_seconds +) -> + Julian_seconds = julian_seconds_from_parts( + Year, + Month, + Day, + Hours, + Minutes, + Seconds + ), + Julian_seconds_since_epoch = Julian_seconds - 210866803200, + _pipe = {timestamp, + Julian_seconds_since_epoch - Offset_seconds, + Second_fraction_as_nanoseconds}, + normalise(_pipe). + +-file("src/gleam/time/timestamp.gleam", 354). +?DOC( + " Create a `Timestamp` from a human-readable calendar time.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " timestamp.from_calendar(\n" + " date: calendar.Date(2024, calendar.December, 25),\n" + " time: calendar.TimeOfDay(12, 30, 50, 0),\n" + " offset: calendar.utc_offset,\n" + " )\n" + " |> timestamp.to_rfc3339(calendar.utc_offset)\n" + " // -> \"2024-12-25T12:30:50Z\"\n" + " ```\n" +). +-spec from_calendar( + gleam@time@calendar:date(), + gleam@time@calendar:time_of_day(), + gleam@time@duration:duration() +) -> timestamp(). +from_calendar(Date, Time, Offset) -> + Month = case erlang:element(3, Date) of + january -> + 1; + + february -> + 2; + + march -> + 3; + + april -> + 4; + + may -> + 5; + + june -> + 6; + + july -> + 7; + + august -> + 8; + + september -> + 9; + + october -> + 10; + + november -> + 11; + + december -> + 12 + end, + from_date_time( + erlang:element(2, Date), + Month, + erlang:element(4, Date), + erlang:element(2, Time), + erlang:element(3, Time), + erlang:element(4, Time), + erlang:element(5, Time), + erlang:round(gleam@time@duration:to_seconds(Offset)) + ). + +-file("src/gleam/time/timestamp.gleam", 548). +?DOC( + " Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`.\n" + "\n" + " [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n" + " \n" + " # Examples\n" + "\n" + " ```gleam\n" + " let assert Ok(ts) = timestamp.parse_rfc3339(\"1970-01-01T00:00:01Z\")\n" + " timestamp.to_unix_seconds_and_nanoseconds(ts)\n" + " // -> #(1, 0)\n" + " ```\n" + " \n" + " Parsing an invalid timestamp returns an error.\n" + " \n" + " ```gleam\n" + " let assert Error(Nil) = timestamp.parse_rfc3339(\"1995-10-31\")\n" + " ```\n" + "\n" + " ## Time zones\n" + "\n" + " It may at first seem that the RFC 3339 format includes timezone\n" + " information, as it can specify an offset such as `Z` or `+3`, so why does\n" + " this function not return calendar time with a time zone? There are multiple\n" + " reasons:\n" + "\n" + " - RFC 3339's timestamp format is based on calendar time, but it is\n" + " unambigous, so it can be converted into epoch time when being parsed. It\n" + " is always better to internally use epoch time to represent unambiguous\n" + " points in time, so we perform that conversion as a convenience and to\n" + " ensure that programmers with less time experience don't accidentally use\n" + " a less suitable time representation.\n" + "\n" + " - RFC 3339's contains _calendar time offset_ information, not time zone\n" + " information. This is enough to convert it to an unambiguous timestamp,\n" + " but it is not enough information to reliably work with calendar time.\n" + " Without the time zone and the time zone database it's not possible to\n" + " know what time period that offset is valid for, so it cannot be used\n" + " without risk of bugs.\n" + "\n" + " ## Behaviour details\n" + " \n" + " - Follows the grammar specified in section 5.6 Internet Date/Time Format of \n" + " RFC 3339 .\n" + " - The `T` and `Z` characters may alternatively be lower case `t` or `z`, \n" + " respectively.\n" + " - Full dates and full times must be separated by `T` or `t`. A space is also \n" + " permitted.\n" + " - Leap seconds rules are not considered. That is, any timestamp may \n" + " specify digts `00` - `60` for the seconds.\n" + " - Any part of a fractional second that cannot be represented in the \n" + " nanosecond precision is tructated. That is, for the time string, \n" + " `\"1970-01-01T00:00:00.1234567899Z\"`, the fractional second `.1234567899` \n" + " will be represented as `123_456_789` in the `Timestamp`.\n" +). +-spec parse_rfc3339(binary()) -> {ok, timestamp()} | {error, nil}. +parse_rfc3339(Input) -> + Bytes = gleam_stdlib:identity(Input), + gleam@result:'try'( + parse_year(Bytes), + fun(_use0) -> + {Year, Bytes@1} = _use0, + gleam@result:'try'( + accept_byte(Bytes@1, 16#2D), + fun(Bytes@2) -> + gleam@result:'try'( + parse_month(Bytes@2), + fun(_use0@1) -> + {Month, Bytes@3} = _use0@1, + gleam@result:'try'( + accept_byte(Bytes@3, 16#2D), + fun(Bytes@4) -> + gleam@result:'try'( + parse_day(Bytes@4, Year, Month), + fun(_use0@2) -> + {Day, Bytes@5} = _use0@2, + gleam@result:'try'( + accept_date_time_separator( + Bytes@5 + ), + fun(Bytes@6) -> + gleam@result:'try'( + parse_hours(Bytes@6), + fun(_use0@3) -> + {Hours, Bytes@7} = _use0@3, + gleam@result:'try'( + accept_byte( + Bytes@7, + 16#3A + ), + fun(Bytes@8) -> + gleam@result:'try'( + parse_minutes( + Bytes@8 + ), + fun( + _use0@4 + ) -> + {Minutes, + Bytes@9} = _use0@4, + gleam@result:'try'( + accept_byte( + Bytes@9, + 16#3A + ), + fun( + Bytes@10 + ) -> + gleam@result:'try'( + parse_seconds( + Bytes@10 + ), + fun( + _use0@5 + ) -> + {Seconds, + Bytes@11} = _use0@5, + gleam@result:'try'( + parse_second_fraction_as_nanoseconds( + Bytes@11 + ), + fun( + _use0@6 + ) -> + {Second_fraction_as_nanoseconds, + Bytes@12} = _use0@6, + gleam@result:'try'( + parse_offset( + Bytes@12 + ), + fun( + _use0@7 + ) -> + {Offset_seconds, + Bytes@13} = _use0@7, + gleam@result:'try'( + accept_empty( + Bytes@13 + ), + fun( + _use0@8 + ) -> + nil = _use0@8, + {ok, + from_date_time( + Year, + Month, + Day, + Hours, + Minutes, + Seconds, + Second_fraction_as_nanoseconds, + Offset_seconds + )} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam_time.app.src b/examples/http_interactions/build/packages/gleam_time/src/gleam_time.app.src new file mode 100644 index 0000000..6b6c52e --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam_time.app.src @@ -0,0 +1,12 @@ +{application, gleam_time, [ + {vsn, "1.8.0"}, + {applications, [gleam_stdlib]}, + {description, "Work with time in Gleam!"}, + {modules, [gleam@time@calendar, + gleam@time@duration, + gleam@time@timestamp, + gleam_time@@main, + gleam_time_ffi, + gleam_time_test_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.erl b/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.erl new file mode 100644 index 0000000..34d8c88 --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.erl @@ -0,0 +1,12 @@ +-module(gleam_time_ffi). +-export([system_time/0, local_time_offset_seconds/0]). + +system_time() -> + {0, erlang:system_time(nanosecond)}. + +local_time_offset_seconds() -> + Utc = calendar:universal_time(), + Local = calendar:local_time(), + UtcSeconds = calendar:datetime_to_gregorian_seconds(Utc), + LocalSeconds = calendar:datetime_to_gregorian_seconds(Local), + LocalSeconds - UtcSeconds. diff --git a/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.mjs b/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.mjs new file mode 100644 index 0000000..27d09aa --- /dev/null +++ b/examples/http_interactions/build/packages/gleam_time/src/gleam_time_ffi.mjs @@ -0,0 +1,11 @@ +export function system_time() { + const now = Date.now(); + const milliseconds = now % 1_000; + const nanoseconds = milliseconds * 1000_000; + const seconds = (now - milliseconds) / 1_000; + return [seconds, nanoseconds]; +} + +export function local_time_offset_seconds() { + return new Date().getTimezoneOffset() * -60; +} diff --git a/examples/http_interactions/build/packages/gleeunit/LICENCE b/examples/http_interactions/build/packages/gleeunit/LICENCE new file mode 100644 index 0000000..c7967c3 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/gleeunit/README.md b/examples/http_interactions/build/packages/gleeunit/README.md new file mode 100644 index 0000000..4d81364 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/README.md @@ -0,0 +1,44 @@ +# gleeunit + +A simple test runner for Gleam, using EUnit on Erlang and a custom runner on JS. + +[![Package Version](https://img.shields.io/hexpm/v/gleeunit)](https://hex.pm/packages/gleeunit) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleeunit/) + + +```sh +gleam add gleeunit@1 --dev +``` +```gleam +// In test/yourapp_test.gleam +import gleeunit + +pub fn main() { + gleeunit.main() +} +``` + +Now any public function with a name ending in `_test` in the `test` directory +will be found and run as a test. + +```gleam +pub fn some_function_test() { + assert some_function() == "Hello!" +} +``` + +Run the tests by entering `gleam test` in the command line. + +### Deno + +If using the Deno JavaScript runtime, you will need to add the following to your +`gleam.toml`. + +```toml +[javascript.deno] +allow_read = [ + "gleam.toml", + "test", + "build", +] +``` diff --git a/examples/http_interactions/build/packages/gleeunit/gleam.toml b/examples/http_interactions/build/packages/gleeunit/gleam.toml new file mode 100644 index 0000000..fea4efe --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/gleam.toml @@ -0,0 +1,16 @@ +name = "gleeunit" +version = "1.10.0" +licences = ["Apache-2.0"] +description = "A simple test runner for Gleam, using EUnit on Erlang" +repository = { type = "github", user = "lpil", repo = "gleeunit" } +links = [{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }] +gleam = ">= 1.13.0" + +[javascript.deno] +allow_read = ["gleam.toml", "test", "build"] + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +testhelper = { "path" = "./testhelper" } diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl new file mode 100644 index 0000000..9360941 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Assert.hrl @@ -0,0 +1,6 @@ +-record(assert, { + start :: integer(), + 'end' :: integer(), + expression_start :: integer(), + kind :: gleeunit@internal@gleam_panic:assert_kind() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl new file mode 100644 index 0000000..812663c --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_AssertedExpression.hrl @@ -0,0 +1,5 @@ +-record(asserted_expression, { + start :: integer(), + 'end' :: integer(), + kind :: gleeunit@internal@gleam_panic:expression_kind() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl new file mode 100644 index 0000000..eee44c9 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_BinaryOperator.hrl @@ -0,0 +1,5 @@ +-record(binary_operator, { + operator :: binary(), + left :: gleeunit@internal@gleam_panic:asserted_expression(), + right :: gleeunit@internal@gleam_panic:asserted_expression() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl new file mode 100644 index 0000000..e7ffaa0 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Expression.hrl @@ -0,0 +1 @@ +-record(expression, {value :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl new file mode 100644 index 0000000..9d55488 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_FunctionCall.hrl @@ -0,0 +1,3 @@ +-record(function_call, { + arguments :: list(gleeunit@internal@gleam_panic:asserted_expression()) +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl new file mode 100644 index 0000000..cf36764 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_GleamPanic.hrl @@ -0,0 +1,8 @@ +-record(gleam_panic, { + message :: binary(), + file :: binary(), + module :: binary(), + function :: binary(), + line :: integer(), + kind :: gleeunit@internal@gleam_panic:panic_kind() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl new file mode 100644 index 0000000..11f865e --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_LetAssert.hrl @@ -0,0 +1,7 @@ +-record(let_assert, { + start :: integer(), + 'end' :: integer(), + pattern_start :: integer(), + pattern_end :: integer(), + value :: gleam@dynamic:dynamic_() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl new file mode 100644 index 0000000..2396489 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_Literal.hrl @@ -0,0 +1 @@ +-record(literal, {value :: gleam@dynamic:dynamic_()}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl new file mode 100644 index 0000000..0424990 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@gleam_panic_OtherExpression.hrl @@ -0,0 +1,3 @@ +-record(other_expression, { + expression :: gleeunit@internal@gleam_panic:asserted_expression() +}). diff --git a/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl new file mode 100644 index 0000000..575ccce --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/include/gleeunit@internal@reporting_State.hrl @@ -0,0 +1 @@ +-record(state, {passed :: integer(), failed :: integer(), skipped :: integer()}). diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit.app.src b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.app.src new file mode 100644 index 0000000..519beb8 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.app.src @@ -0,0 +1,16 @@ +{application, gleeunit, [ + {vsn, "1.10.0"}, + {applications, [gleam_stdlib]}, + {description, "A simple test runner for Gleam, using EUnit on Erlang"}, + {modules, [erlang_test_module, + gleeunit, + gleeunit@@main, + gleeunit@internal@gleam_panic, + gleeunit@internal@reporting, + gleeunit@should, + gleeunit_ffi, + gleeunit_gleam_panic_ffi, + gleeunit_progress, + gleeunit_test_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.erl new file mode 100644 index 0000000..bcb58b6 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.erl @@ -0,0 +1,89 @@ +-module(gleeunit). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit.gleam"). +-export([main/0]). +-export_type([atom_/0, encoding/0, report_module_name/0, gleeunit_progress_option/0, eunit_option/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type atom_() :: any(). + +-type encoding() :: utf8. + +-type report_module_name() :: gleeunit_progress. + +-type gleeunit_progress_option() :: {colored, boolean()}. + +-type eunit_option() :: verbose | + no_tty | + {report, {report_module_name(), list(gleeunit_progress_option())}} | + {scale_timeouts, integer()}. + +-file("src/gleeunit.gleam", 42). +-spec gleam_to_erlang_module_name(binary()) -> binary(). +gleam_to_erlang_module_name(Path) -> + case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of + true -> + _pipe = Path, + _pipe@1 = gleam@string:replace( + _pipe, + <<".gleam"/utf8>>, + <<""/utf8>> + ), + gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"@"/utf8>>); + + false -> + _pipe@2 = Path, + _pipe@3 = gleam@string:split(_pipe@2, <<"/"/utf8>>), + _pipe@4 = gleam@list:last(_pipe@3), + _pipe@5 = gleam@result:unwrap(_pipe@4, Path), + gleam@string:replace(_pipe@5, <<".erl"/utf8>>, <<""/utf8>>) + end. + +-file("src/gleeunit.gleam", 18). +-spec do_main() -> nil. +do_main() -> + Options = [verbose, + no_tty, + {report, {gleeunit_progress, [{colored, true}]}}, + {scale_timeouts, 10}], + Result = begin + _pipe = gleeunit_ffi:find_files( + <<"**/*.{erl,gleam}"/utf8>>, + <<"test"/utf8>> + ), + _pipe@1 = gleam@list:map(_pipe, fun gleam_to_erlang_module_name/1), + _pipe@2 = gleam@list:map( + _pipe@1, + fun(_capture) -> erlang:binary_to_atom(_capture, utf8) end + ), + gleeunit_ffi:run_eunit(_pipe@2, Options) + end, + Code = case Result of + {ok, _} -> + 0; + + {error, _} -> + 1 + end, + erlang:halt(Code). + +-file("src/gleeunit.gleam", 13). +?DOC( + " Find and run all test functions for the current project using Erlang's EUnit\n" + " test framework, or a custom JavaScript test runner.\n" + "\n" + " Any Erlang or Gleam function in the `test` directory with a name ending in\n" + " `_test` is considered a test function and will be run.\n" + "\n" + " A test that panics is considered a failure.\n" +). +-spec main() -> nil. +main() -> + do_main(). diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit.gleam b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.gleam new file mode 100644 index 0000000..7d3155e --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit.gleam @@ -0,0 +1,86 @@ +import gleam/list +import gleam/result +import gleam/string + +/// Find and run all test functions for the current project using Erlang's EUnit +/// test framework, or a custom JavaScript test runner. +/// +/// Any Erlang or Gleam function in the `test` directory with a name ending in +/// `_test` is considered a test function and will be run. +/// +/// A test that panics is considered a failure. +/// +pub fn main() -> Nil { + do_main() +} + +@external(javascript, "./gleeunit_ffi.mjs", "main") +fn do_main() -> Nil { + let options = [ + Verbose, + NoTty, + Report(#(GleeunitProgress, [Colored(True)])), + ScaleTimeouts(10), + ] + + let result = + find_files(matching: "**/*.{erl,gleam}", in: "test") + |> list.map(gleam_to_erlang_module_name) + |> list.map(dangerously_convert_string_to_atom(_, Utf8)) + |> run_eunit(options) + + let code = case result { + Ok(_) -> 0 + Error(_) -> 1 + } + halt(code) +} + +@external(erlang, "erlang", "halt") +fn halt(a: Int) -> Nil + +fn gleam_to_erlang_module_name(path: String) -> String { + case string.ends_with(path, ".gleam") { + True -> + path + |> string.replace(".gleam", "") + |> string.replace("/", "@") + + False -> + path + |> string.split("/") + |> list.last + |> result.unwrap(path) + |> string.replace(".erl", "") + } +} + +@external(erlang, "gleeunit_ffi", "find_files") +fn find_files(matching matching: String, in in: String) -> List(String) + +type Atom + +type Encoding { + Utf8 +} + +@external(erlang, "erlang", "binary_to_atom") +fn dangerously_convert_string_to_atom(a: String, b: Encoding) -> Atom + +type ReportModuleName { + GleeunitProgress +} + +type GleeunitProgressOption { + Colored(Bool) +} + +type EunitOption { + Verbose + NoTty + Report(#(ReportModuleName, List(GleeunitProgressOption))) + ScaleTimeouts(Int) +} + +@external(erlang, "gleeunit_ffi", "run_eunit") +fn run_eunit(a: List(Atom), b: List(EunitOption)) -> Result(Nil, a) diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam new file mode 100644 index 0000000..6a5d309 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleam_panic.gleam @@ -0,0 +1,49 @@ +import gleam/dynamic + +pub type GleamPanic { + GleamPanic( + message: String, + file: String, + module: String, + function: String, + line: Int, + kind: PanicKind, + ) +} + +pub type PanicKind { + Todo + Panic + LetAssert( + start: Int, + end: Int, + pattern_start: Int, + pattern_end: Int, + value: dynamic.Dynamic, + ) + Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind) +} + +pub type AssertKind { + BinaryOperator( + operator: String, + left: AssertedExpression, + right: AssertedExpression, + ) + FunctionCall(arguments: List(AssertedExpression)) + OtherExpression(expression: AssertedExpression) +} + +pub type AssertedExpression { + AssertedExpression(start: Int, end: Int, kind: ExpressionKind) +} + +pub type ExpressionKind { + Literal(value: dynamic.Dynamic) + Expression(value: dynamic.Dynamic) + Unevaluated +} + +@external(erlang, "gleeunit_gleam_panic_ffi", "from_dynamic") +@external(javascript, "./gleeunit_gleam_panic_ffi.mjs", "from_dynamic") +pub fn from_dynamic(data: dynamic.Dynamic) -> Result(GleamPanic, Nil) diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl new file mode 100644 index 0000000..d78f5e5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl @@ -0,0 +1,49 @@ +-module(gleeunit_gleam_panic_ffi). +-export([from_dynamic/1]). + +from_dynamic(#{ + gleam_error := assert, + start := Start, + 'end' := End, + expression_start := EStart +} = E) -> + wrap(E, {assert, Start, End, EStart, assert_kind(E)}); +from_dynamic(#{ + gleam_error := let_assert, + start := Start, + 'end' := End, + pattern_start := PStart, + pattern_end := PEnd, + value := Value +} = E) -> + wrap(E, {let_assert, Start, End, PStart, PEnd, Value}); +from_dynamic(#{gleam_error := panic} = E) -> + wrap(E, panic); +from_dynamic(#{gleam_error := todo} = E) -> + wrap(E, todo); +from_dynamic(_) -> + {error, nil}. + +assert_kind(#{kind := binary_operator, left := L, right := R, operator := O}) -> + {binary_operator, atom_to_binary(O), expression(L), expression(R)}; +assert_kind(#{kind := function_call, arguments := Arguments}) -> + {function_call, lists:map(fun expression/1, Arguments)}; +assert_kind(#{kind := expression, expression := Expression}) -> + {other_expression, expression(Expression)}. + +expression(#{start := S, 'end' := E, kind := literal, value := Value}) -> + {asserted_expression, S, E, {literal, Value}}; +expression(#{start := S, 'end' := E, kind := expression, value := Value}) -> + {asserted_expression, S, E, {expression, Value}}; +expression(#{start := S, 'end' := E, kind := unevaluated}) -> + {asserted_expression, S, E, unevaluated}. + +wrap(#{ + gleam_error := _, + file := File, + message := Message, + module := Module, + function := Function, + line := Line +}, Kind) -> + {ok, {gleam_panic, Message, File, Module, Function, Line, Kind}}. diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs new file mode 100644 index 0000000..03f6025 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs @@ -0,0 +1,91 @@ +import { Result$Ok, Result$Error, List$Empty, List$NonEmpty } from "../../gleam.mjs"; +import { + GleamPanic$GleamPanic, + PanicKind$Todo, + PanicKind$Panic, + PanicKind$LetAssert, + PanicKind$Assert, + AssertKind$BinaryOperator, + AssertKind$FunctionCall, + AssertKind$OtherExpression, + AssertedExpression$AssertedExpression, + ExpressionKind$Literal, + ExpressionKind$Expression, + ExpressionKind$Unevaluated, +} from "./gleam_panic.mjs"; + +export function from_dynamic(error) { + if (!(error instanceof globalThis.Error) || !error.gleam_error) { + return Result$Error(undefined); + } + + if (error.gleam_error === "todo") { + return wrap(error, PanicKind$Todo()); + } + + if (error.gleam_error === "panic") { + return wrap(error, PanicKind$Panic()); + } + + if (error.gleam_error === "let_assert") { + let kind = PanicKind$LetAssert( + error.start, + error.end, + error.pattern_start, + error.pattern_end, + error.value, + ); + return wrap(error, kind); + } + + if (error.gleam_error === "assert") { + let kind = PanicKind$Assert( + error.start, + error.end, + error.expression_start, + assert_kind(error), + ); + return wrap(error, kind); + } + + return Result$Error(undefined); +} + +function assert_kind(error) { + if (error.kind == "binary_operator") { + return AssertKind$BinaryOperator( + error.operator, + expression(error.left), + expression(error.right), + ); + } + + if (error.kind == "function_call") { + let list = List$Empty(); + let i = error.arguments.length; + while (i--) { + list = List$NonEmpty(expression(error.arguments[i]), list); + } + return AssertKind$FunctionCall(list); + } + + return AssertKind$OtherExpression(expression(error.expression)); +} + +function expression(data) { + const expression = AssertedExpression$AssertedExpression(data.start, data.end, undefined); + if (data.kind == "literal") { + expression.kind = ExpressionKind$Literal(data.value); + } else if (data.kind == "expression") { + expression.kind = ExpressionKind$Expression(data.value); + } else { + expression.kind = ExpressionKind$Unevaluated(); + } + return expression; +} + +function wrap(e, kind) { + return Result$Ok( + GleamPanic$GleamPanic(e.message, e.file, e.module, e.function, e.line, kind), + ); +} diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam new file mode 100644 index 0000000..72f766f --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/internal/reporting.gleam @@ -0,0 +1,240 @@ +import gleam/bit_array +import gleam/dynamic +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string +import gleeunit/internal/gleam_panic.{type GleamPanic} + +pub type State { + State(passed: Int, failed: Int, skipped: Int) +} + +pub fn new_state() -> State { + State(passed: 0, failed: 0, skipped: 0) +} + +pub fn finished(state: State) -> Int { + case state { + State(passed: 0, failed: 0, skipped: 0) -> { + io.println("\nNo tests found!") + 1 + } + State(failed: 0, skipped: 0, ..) -> { + let message = + "\n" <> int.to_string(state.passed) <> " passed, no failures" + io.println(green(message)) + 0 + } + State(skipped: 0, ..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, " + <> int.to_string(state.failed) + <> " failures" + io.println(red(message)) + 1 + } + State(failed: 0, ..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, 0 failures, " + <> int.to_string(state.skipped) + <> " skipped" + io.println(yellow(message)) + 1 + } + State(..) -> { + let message = + "\n" + <> int.to_string(state.passed) + <> " passed, " + <> int.to_string(state.failed) + <> " failures, " + <> int.to_string(state.skipped) + <> " skipped" + io.println(red(message)) + 1 + } + } +} + +pub fn test_passed(state: State) -> State { + io.print(green(".")) + State(..state, passed: state.passed + 1) +} + +pub fn test_failed( + state: State, + module: String, + function: String, + error: dynamic.Dynamic, +) -> State { + let message = case gleam_panic.from_dynamic(error) { + Ok(error) -> { + let src = option.from_result(read_file(error.file)) + format_gleam_error(error, module, function, src) + } + Error(_) -> format_unknown(module, function, error) + } + + io.print("\n" <> message) + State(..state, failed: state.failed + 1) +} + +pub fn eunit_missing() -> Result(never, Nil) { + let message = bold(red("Error")) <> ": EUnit libraries not found. + +Your Erlang installation seems to be incomplete. If you installed Erlang using +a package manager ensure that you have installed the full Erlang +distribution instead of a stripped-down version. +" + io.print_error(message) + Error(Nil) +} + +fn format_unknown( + module: String, + function: String, + error: dynamic.Dynamic, +) -> String { + string.concat([ + grey(module <> "." <> function) <> "\n", + "An unexpected error occurred:\n", + "\n", + " " <> string.inspect(error) <> "\n", + ]) +} + +fn format_gleam_error( + error: GleamPanic, + module: String, + function: String, + src: Option(BitArray), +) -> String { + let location = grey(error.file <> ":" <> int.to_string(error.line)) + + case error.kind { + gleam_panic.Panic -> { + string.concat([ + bold(red("panic")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.Todo -> { + string.concat([ + bold(yellow("todo")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.Assert(start:, end:, kind:, ..) -> { + string.concat([ + bold(red("assert")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + code_snippet(src, start, end), + assert_info(kind), + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + + gleam_panic.LetAssert(start:, end:, value:, ..) -> { + string.concat([ + bold(red("let assert")) <> " " <> location <> "\n", + cyan(" test") <> ": " <> module <> "." <> function <> "\n", + code_snippet(src, start, end), + cyan("value") <> ": " <> string.inspect(value) <> "\n", + cyan(" info") <> ": " <> error.message <> "\n", + ]) + } + } +} + +fn assert_info(kind: gleam_panic.AssertKind) -> String { + case kind { + gleam_panic.BinaryOperator(left:, right:, ..) -> { + string.concat([assert_value(" left", left), assert_value("right", right)]) + } + + gleam_panic.FunctionCall(arguments:) -> { + arguments + |> list.index_map(fn(e, i) { + let number = string.pad_start(int.to_string(i), 5, " ") + assert_value(number, e) + }) + |> string.concat + } + + gleam_panic.OtherExpression(..) -> "" + } +} + +fn assert_value(name: String, value: gleam_panic.AssertedExpression) -> String { + cyan(name) <> ": " <> inspect_value(value) <> "\n" +} + +fn inspect_value(value: gleam_panic.AssertedExpression) -> String { + case value.kind { + gleam_panic.Unevaluated -> grey("unevaluated") + gleam_panic.Literal(..) -> grey("literal") + gleam_panic.Expression(value:) -> string.inspect(value) + } +} + +fn code_snippet(src: Option(BitArray), start: Int, end: Int) -> String { + { + use src <- result.try(option.to_result(src, Nil)) + use snippet <- result.try(bit_array.slice(src, start, end - start)) + use snippet <- result.try(bit_array.to_string(snippet)) + let snippet = cyan(" code") <> ": " <> snippet <> "\n" + Ok(snippet) + } + |> result.unwrap("") +} + +pub fn test_skipped(state: State, module: String, function: String) -> State { + io.print("\n" <> module <> "." <> function <> yellow(" skipped")) + State(..state, skipped: state.skipped + 1) +} + +fn bold(text: String) -> String { + "\u{001b}[1m" <> text <> "\u{001b}[22m" +} + +fn cyan(text: String) -> String { + "\u{001b}[36m" <> text <> "\u{001b}[39m" +} + +fn yellow(text: String) -> String { + "\u{001b}[33m" <> text <> "\u{001b}[39m" +} + +fn green(text: String) -> String { + "\u{001b}[32m" <> text <> "\u{001b}[39m" +} + +fn red(text: String) -> String { + "\u{001b}[31m" <> text <> "\u{001b}[39m" +} + +fn grey(text: String) -> String { + "\u{001b}[90m" <> text <> "\u{001b}[39m" +} + +@external(erlang, "file", "read_file") +fn read_file(path: String) -> Result(BitArray, dynamic.Dynamic) { + case read_file_text(path) { + Ok(text) -> Ok(bit_array.from_string(text)) + Error(e) -> Error(e) + } +} + +@external(javascript, "../../gleeunit_ffi.mjs", "read_file") +fn read_file_text(path: String) -> Result(String, dynamic.Dynamic) diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit/should.gleam b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/should.gleam new file mode 100644 index 0000000..99cd16c --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit/should.gleam @@ -0,0 +1,72 @@ +//// Use the `assert` keyword instead of this module. + +import gleam/option.{type Option, None, Some} +import gleam/string + +pub fn equal(a: t, b: t) -> Nil { + case a == b { + True -> Nil + _ -> + panic as string.concat([ + "\n", + string.inspect(a), + "\nshould equal\n", + string.inspect(b), + ]) + } +} + +pub fn not_equal(a: t, b: t) -> Nil { + case a != b { + True -> Nil + _ -> + panic as string.concat([ + "\n", + string.inspect(a), + "\nshould not equal\n", + string.inspect(b), + ]) + } +} + +pub fn be_ok(a: Result(a, e)) -> a { + case a { + Ok(value) -> value + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be ok"]) + } +} + +pub fn be_error(a: Result(a, e)) -> e { + case a { + Error(error) -> error + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be error"]) + } +} + +pub fn be_some(a: Option(a)) -> a { + case a { + Some(value) -> value + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be some"]) + } +} + +pub fn be_none(a: Option(a)) -> Nil { + case a { + None -> Nil + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be none"]) + } +} + +pub fn be_true(actual: Bool) -> Nil { + actual + |> equal(True) +} + +pub fn be_false(actual: Bool) -> Nil { + actual + |> equal(False) +} + +pub fn fail() -> Nil { + be_true(False) +} diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl new file mode 100644 index 0000000..398ea7d --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@gleam_panic.erl @@ -0,0 +1,56 @@ +-module(gleeunit@internal@gleam_panic). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/internal/gleam_panic.gleam"). +-export([from_dynamic/1]). +-export_type([gleam_panic/0, panic_kind/0, assert_kind/0, asserted_expression/0, expression_kind/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type gleam_panic() :: {gleam_panic, + binary(), + binary(), + binary(), + binary(), + integer(), + panic_kind()}. + +-type panic_kind() :: todo | + panic | + {let_assert, + integer(), + integer(), + integer(), + integer(), + gleam@dynamic:dynamic_()} | + {assert, integer(), integer(), integer(), assert_kind()}. + +-type assert_kind() :: {binary_operator, + binary(), + asserted_expression(), + asserted_expression()} | + {function_call, list(asserted_expression())} | + {other_expression, asserted_expression()}. + +-type asserted_expression() :: {asserted_expression, + integer(), + integer(), + expression_kind()}. + +-type expression_kind() :: {literal, gleam@dynamic:dynamic_()} | + {expression, gleam@dynamic:dynamic_()} | + unevaluated. + +-file("src/gleeunit/internal/gleam_panic.gleam", 49). +?DOC(false). +-spec from_dynamic(gleam@dynamic:dynamic_()) -> {ok, gleam_panic()} | + {error, nil}. +from_dynamic(Data) -> + gleeunit_gleam_panic_ffi:from_dynamic(Data). diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@reporting.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@reporting.erl new file mode 100644 index 0000000..8c37c79 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@internal@reporting.erl @@ -0,0 +1,343 @@ +-module(gleeunit@internal@reporting). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/internal/reporting.gleam"). +-export([new_state/0, test_skipped/3, test_passed/1, finished/1, eunit_missing/0, test_failed/4]). +-export_type([state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type state() :: {state, integer(), integer(), integer()}. + +-file("src/gleeunit/internal/reporting.gleam", 15). +?DOC(false). +-spec new_state() -> state(). +new_state() -> + {state, 0, 0, 0}. + +-file("src/gleeunit/internal/reporting.gleam", 207). +?DOC(false). +-spec bold(binary()) -> binary(). +bold(Text) -> + <<<<"\x{001b}[1m"/utf8, Text/binary>>/binary, "\x{001b}[22m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 211). +?DOC(false). +-spec cyan(binary()) -> binary(). +cyan(Text) -> + <<<<"\x{001b}[36m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 191). +?DOC(false). +-spec code_snippet(gleam@option:option(bitstring()), integer(), integer()) -> binary(). +code_snippet(Src, Start, End) -> + _pipe = begin + gleam@result:'try'( + gleam@option:to_result(Src, nil), + fun(Src@1) -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Src@1, Start, End - Start), + fun(Snippet) -> + gleam@result:'try'( + gleam@bit_array:to_string(Snippet), + fun(Snippet@1) -> + Snippet@2 = <<<<<<(cyan(<<" code"/utf8>>))/binary, + ": "/utf8>>/binary, + Snippet@1/binary>>/binary, + "\n"/utf8>>, + {ok, Snippet@2} + end + ) + end + ) + end + ) + end, + gleam@result:unwrap(_pipe, <<""/utf8>>). + +-file("src/gleeunit/internal/reporting.gleam", 215). +?DOC(false). +-spec yellow(binary()) -> binary(). +yellow(Text) -> + <<<<"\x{001b}[33m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 202). +?DOC(false). +-spec test_skipped(state(), binary(), binary()) -> state(). +test_skipped(State, Module, Function) -> + gleam_stdlib:print( + <<<<<<<<"\n"/utf8, Module/binary>>/binary, "."/utf8>>/binary, + Function/binary>>/binary, + (yellow(<<" skipped"/utf8>>))/binary>> + ), + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State) + 1}. + +-file("src/gleeunit/internal/reporting.gleam", 219). +?DOC(false). +-spec green(binary()) -> binary(). +green(Text) -> + <<<<"\x{001b}[32m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 66). +?DOC(false). +-spec test_passed(state()) -> state(). +test_passed(State) -> + gleam_stdlib:print(green(<<"."/utf8>>)), + {state, + erlang:element(2, State) + 1, + erlang:element(3, State), + erlang:element(4, State)}. + +-file("src/gleeunit/internal/reporting.gleam", 223). +?DOC(false). +-spec red(binary()) -> binary(). +red(Text) -> + <<<<"\x{001b}[31m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 19). +?DOC(false). +-spec finished(state()) -> integer(). +finished(State) -> + case State of + {state, 0, 0, 0} -> + gleam_stdlib:println(<<"\nNo tests found!"/utf8>>), + 1; + + {state, _, 0, 0} -> + Message = <<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, no failures"/utf8>>, + gleam_stdlib:println(green(Message)), + 0; + + {state, _, _, 0} -> + Message@1 = <<<<<<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary, + " failures"/utf8>>, + gleam_stdlib:println(red(Message@1)), + 1; + + {state, _, 0, _} -> + Message@2 = <<<<<<<<"\n"/utf8, + (erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary, + " passed, 0 failures, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary, + " skipped"/utf8>>, + gleam_stdlib:println(yellow(Message@2)), + 1; + + {state, _, _, _} -> + Message@3 = <<<<<<<<<<<<"\n"/utf8, + (erlang:integer_to_binary( + erlang:element(2, State) + ))/binary>>/binary, + " passed, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary, + " failures, "/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary, + " skipped"/utf8>>, + gleam_stdlib:println(red(Message@3)), + 1 + end. + +-file("src/gleeunit/internal/reporting.gleam", 89). +?DOC(false). +-spec eunit_missing() -> {ok, any()} | {error, nil}. +eunit_missing() -> + Message = <<(bold(red(<<"Error"/utf8>>)))/binary, + ": EUnit libraries not found. + +Your Erlang installation seems to be incomplete. If you installed Erlang using +a package manager ensure that you have installed the full Erlang +distribution instead of a stripped-down version. +"/utf8>>, + gleam_stdlib:print_error(Message), + {error, nil}. + +-file("src/gleeunit/internal/reporting.gleam", 227). +?DOC(false). +-spec grey(binary()) -> binary(). +grey(Text) -> + <<<<"\x{001b}[90m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 100). +?DOC(false). +-spec format_unknown(binary(), binary(), gleam@dynamic:dynamic_()) -> binary(). +format_unknown(Module, Function, Error) -> + erlang:list_to_binary( + [<<(grey(<<<>/binary, Function/binary>>))/binary, + "\n"/utf8>>, + <<"An unexpected error occurred:\n"/utf8>>, + <<"\n"/utf8>>, + <<<<" "/utf8, (gleam@string:inspect(Error))/binary>>/binary, + "\n"/utf8>>] + ). + +-file("src/gleeunit/internal/reporting.gleam", 183). +?DOC(false). +-spec inspect_value(gleeunit@internal@gleam_panic:asserted_expression()) -> binary(). +inspect_value(Value) -> + case erlang:element(4, Value) of + unevaluated -> + grey(<<"unevaluated"/utf8>>); + + {literal, _} -> + grey(<<"literal"/utf8>>); + + {expression, Value@1} -> + gleam@string:inspect(Value@1) + end. + +-file("src/gleeunit/internal/reporting.gleam", 179). +?DOC(false). +-spec assert_value( + binary(), + gleeunit@internal@gleam_panic:asserted_expression() +) -> binary(). +assert_value(Name, Value) -> + <<<<<<(cyan(Name))/binary, ": "/utf8>>/binary, + (inspect_value(Value))/binary>>/binary, + "\n"/utf8>>. + +-file("src/gleeunit/internal/reporting.gleam", 160). +?DOC(false). +-spec assert_info(gleeunit@internal@gleam_panic:assert_kind()) -> binary(). +assert_info(Kind) -> + case Kind of + {binary_operator, _, Left, Right} -> + erlang:list_to_binary( + [assert_value(<<" left"/utf8>>, Left), + assert_value(<<"right"/utf8>>, Right)] + ); + + {function_call, Arguments} -> + _pipe = Arguments, + _pipe@1 = gleam@list:index_map( + _pipe, + fun(E, I) -> + Number = gleam@string:pad_start( + erlang:integer_to_binary(I), + 5, + <<" "/utf8>> + ), + assert_value(Number, E) + end + ), + erlang:list_to_binary(_pipe@1); + + {other_expression, _} -> + <<""/utf8>> + end. + +-file("src/gleeunit/internal/reporting.gleam", 113). +?DOC(false). +-spec format_gleam_error( + gleeunit@internal@gleam_panic:gleam_panic(), + binary(), + binary(), + gleam@option:option(bitstring()) +) -> binary(). +format_gleam_error(Error, Module, Function, Src) -> + Location = grey( + <<<<(erlang:element(3, Error))/binary, ":"/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(6, Error)))/binary>> + ), + case erlang:element(7, Error) of + panic -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"panic"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + todo -> + erlang:list_to_binary( + [<<<<<<(bold(yellow(<<"todo"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + {assert, Start, End, _, Kind} -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"assert"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + code_snippet(Src, Start, End), + assert_info(Kind), + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ); + + {let_assert, Start@1, End@1, _, _, Value} -> + erlang:list_to_binary( + [<<<<<<(bold(red(<<"let assert"/utf8>>)))/binary, " "/utf8>>/binary, + Location/binary>>/binary, + "\n"/utf8>>, + <<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary, + Module/binary>>/binary, + "."/utf8>>/binary, + Function/binary>>/binary, + "\n"/utf8>>, + code_snippet(Src, Start@1, End@1), + <<<<<<(cyan(<<"value"/utf8>>))/binary, ": "/utf8>>/binary, + (gleam@string:inspect(Value))/binary>>/binary, + "\n"/utf8>>, + <<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary, + (erlang:element(2, Error))/binary>>/binary, + "\n"/utf8>>] + ) + end. + +-file("src/gleeunit/internal/reporting.gleam", 71). +?DOC(false). +-spec test_failed(state(), binary(), binary(), gleam@dynamic:dynamic_()) -> state(). +test_failed(State, Module, Function, Error) -> + Message = case gleeunit_gleam_panic_ffi:from_dynamic(Error) of + {ok, Error@1} -> + Src = gleam@option:from_result( + file:read_file(erlang:element(3, Error@1)) + ), + format_gleam_error(Error@1, Module, Function, Src); + + {error, _} -> + format_unknown(Module, Function, Error) + end, + gleam_stdlib:print(<<"\n"/utf8, Message/binary>>), + {state, + erlang:element(2, State), + erlang:element(3, State) + 1, + erlang:element(4, State)}. diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit@should.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@should.erl new file mode 100644 index 0000000..dd0f07a --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit@should.erl @@ -0,0 +1,153 @@ +-module(gleeunit@should). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gleeunit/should.gleam"). +-export([equal/2, not_equal/2, be_ok/1, be_error/1, be_some/1, be_none/1, be_true/1, be_false/1, fail/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(" Use the `assert` keyword instead of this module.\n"). + +-file("src/gleeunit/should.gleam", 6). +-spec equal(DOJ, DOJ) -> nil. +equal(A, B) -> + case A =:= B of + true -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould equal\n"/utf8>>, + gleam@string:inspect(B)] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"equal"/utf8>>, + line => 10}) + end. + +-file("src/gleeunit/should.gleam", 19). +-spec not_equal(DOK, DOK) -> nil. +not_equal(A, B) -> + case A /= B of + true -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould not equal\n"/utf8>>, + gleam@string:inspect(B)] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"not_equal"/utf8>>, + line => 23}) + end. + +-file("src/gleeunit/should.gleam", 32). +-spec be_ok({ok, DOL} | {error, any()}) -> DOL. +be_ok(A) -> + case A of + {ok, Value} -> + Value; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be ok"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_ok"/utf8>>, + line => 35}) + end. + +-file("src/gleeunit/should.gleam", 39). +-spec be_error({ok, any()} | {error, DOQ}) -> DOQ. +be_error(A) -> + case A of + {error, Error} -> + Error; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be error"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_error"/utf8>>, + line => 42}) + end. + +-file("src/gleeunit/should.gleam", 46). +-spec be_some(gleam@option:option(DOT)) -> DOT. +be_some(A) -> + case A of + {some, Value} -> + Value; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be some"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_some"/utf8>>, + line => 49}) + end. + +-file("src/gleeunit/should.gleam", 53). +-spec be_none(gleam@option:option(any())) -> nil. +be_none(A) -> + case A of + none -> + nil; + + _ -> + erlang:error(#{gleam_error => panic, + message => erlang:list_to_binary( + [<<"\n"/utf8>>, + gleam@string:inspect(A), + <<"\nshould be none"/utf8>>] + ), + file => <>, + module => <<"gleeunit/should"/utf8>>, + function => <<"be_none"/utf8>>, + line => 56}) + end. + +-file("src/gleeunit/should.gleam", 60). +-spec be_true(boolean()) -> nil. +be_true(Actual) -> + _pipe = Actual, + equal(_pipe, true). + +-file("src/gleeunit/should.gleam", 65). +-spec be_false(boolean()) -> nil. +be_false(Actual) -> + _pipe = Actual, + equal(_pipe, false). + +-file("src/gleeunit/should.gleam", 70). +-spec fail() -> nil. +fail() -> + be_true(false). diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.erl new file mode 100644 index 0000000..05c7490 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.erl @@ -0,0 +1,21 @@ +-module(gleeunit_ffi). + +-export([find_files/2, run_eunit/2]). + +find_files(Pattern, In) -> + Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), + lists:map(fun list_to_binary/1, Results). + +run_eunit(Tests, Options) -> + case code:which(eunit) of + non_existing -> + gleeunit@internal@reporting:eunit_missing(); + + _ -> + case eunit:test(Tests, Options) of + ok -> {ok, nil}; + error -> {error, nil}; + {error, Term} -> {error, Term} + end + end. + diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.mjs b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.mjs new file mode 100644 index 0000000..7bdc071 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_ffi.mjs @@ -0,0 +1,100 @@ +import { readFileSync } from "node:fs"; +import { Result$Ok, Result$Error } from "./gleam.mjs"; +import * as reporting from "./gleeunit/internal/reporting.mjs"; + +export function read_file(path) { + try { + return Result$Ok(readFileSync(path)); + } catch { + return Result$Error(undefined); + } +} + +async function* gleamFiles(directory) { + for (let entry of await read_dir(directory)) { + let path = join_path(directory, entry); + if (path.endsWith(".gleam")) { + yield path; + } else { + try { + yield* gleamFiles(path); + } catch (error) { + // Could not read directory, assume it's a file + } + } + } +} + +async function readRootPackageName() { + let toml = await async_read_file("gleam.toml", "utf-8"); + for (let line of toml.split("\n")) { + let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() + if (matches) return matches[1]; + } + throw new Error("Could not determine package name from gleam.toml"); +} + +export async function main() { + let state = reporting.new_state(); + + let packageName = await readRootPackageName(); + let dist = `../${packageName}/`; + + for await (let path of await gleamFiles("test")) { + let js_path = path.slice("test/".length).replace(".gleam", ".mjs"); + let module = await import(join_path(dist, js_path)); + for (let fnName of Object.keys(module)) { + if (!fnName.endsWith("_test")) continue; + try { + await module[fnName](); + state = reporting.test_passed(state); + } catch (error) { + let moduleName = js_path.slice(0, -4); + state = reporting.test_failed(state, moduleName, fnName, error); + } + } + } + + const status = reporting.finished(state); + exit(status); +} + +export function crash(message) { + throw new Error(message); +} + +function exit(code) { + if (globalThis.Deno) { + Deno.exit(code); + } else { + process.exit(code); + } +} + +async function read_dir(path) { + if (globalThis.Deno) { + let items = []; + for await (let item of Deno.readDir(path, { withFileTypes: true })) { + items.push(item.name); + } + return items; + } else { + let { readdir } = await import("node:fs/promises"); + return readdir(path); + } +} + +function join_path(a, b) { + if (a.endsWith("/")) return a + b; + return a + "/" + b; +} + +async function async_read_file(path) { + if (globalThis.Deno) { + return Deno.readTextFile(path); + } else { + let { readFile } = await import("node:fs/promises"); + let contents = await readFile(path); + return contents.toString(); + } +} diff --git a/examples/http_interactions/build/packages/gleeunit/src/gleeunit_progress.erl b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_progress.erl new file mode 100644 index 0000000..e6576a5 --- /dev/null +++ b/examples/http_interactions/build/packages/gleeunit/src/gleeunit_progress.erl @@ -0,0 +1,72 @@ +%% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters + +-module(gleeunit_progress). +-define(NOTEST, true). + +%% eunit_listener callbacks +-export([ + init/1, handle_begin/3, handle_end/3, handle_cancel/3, terminate/2, + start/0, start/1 +]). + +-define(reporting, gleeunit@internal@reporting). + +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +init(_Options) -> + ?reporting:new_state(). + +handle_begin(_test_or_group, _data, State) -> + State. + +handle_end(group, _data, State) -> + State; +handle_end(test, Data, State) -> + {AtomModule, AtomFunction, _Arity} = proplists:get_value(source, Data), + Module = erlang:atom_to_binary(AtomModule), + Function = erlang:atom_to_binary(AtomFunction), + + % EUnit swallows stdout, so print it to make debugging easier. + case proplists:get_value(output, Data) of + undefined -> ok; + <<>> -> ok; + Out -> gleam@io:print(Out) + end, + + case proplists:get_value(status, Data) of + ok -> + ?reporting:test_passed(State); + {skipped, _Reason} -> + ?reporting:test_skipped(State, Module, Function); + {error, {_, Exception, _Stack}} -> + ?reporting:test_failed(State, Module, Function, Exception) + end. + + +handle_cancel(_test_or_group, Data, State) -> + ?reporting:test_failed(State, <<"gleeunit">>, <<"main">>, Data). + +terminate({ok, _Data}, State) -> + ?reporting:finished(State), + ok; +terminate({error, Reason}, State) -> + ?reporting:finished(State), + io:fwrite(" +Eunit failed: + +~80p + +This is probably a bug in gleeunit. Please report it. +", [Reason]), + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. diff --git a/examples/http_interactions/build/packages/glisten/LICENSE b/examples/http_interactions/build/packages/glisten/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/glisten/README.md b/examples/http_interactions/build/packages/glisten/README.md new file mode 100644 index 0000000..89dd29a --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/README.md @@ -0,0 +1,88 @@ +# glisten + +[![Package Version](https://img.shields.io/hexpm/v/glisten)](https://hex.pm/packages/glisten) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/glisten/) + +A Gleam TCP server library. Built on top of `gleam_otp`, it provides a +supervisor over a pool of socket acceptors. Each acceptor will block on `accept` +until a connection is opened. The acceptor will then spawn a handler process +and then block again on `accept`. + +Below is a simple example that will echo received messages. You can get this +up and running with the following: + +```sh +$ gleam new +$ cd +$ gleam add glisten gleam_erlang gleam_otp +``` + +Then place this code in `src/.gleam`: + +```gleam +import gleam/bytes_tree +import gleam/erlang/process +import gleam/option.{None} +import glisten.{Packet} + +pub fn main() { + let assert Ok(_) = + glisten.new(fn(_conn) { #(Nil, None) }, fn(state, msg, conn) { + let assert Packet(msg) = msg + let assert Ok(_) = glisten.send(conn, bytes_tree.from_bit_array(msg)) + glisten.continue(state) + }) + // NOTE: By default, `glisten` will listen on the loopback interface. If + // you want to listen on all interfaces, pass the following. You can also + // specify other interface values, including IPv6 addresses. + // |> glisten.bind("0.0.0.0") + |> glisten.start(3000) + + process.sleep_forever() +} +``` + +TLS is also handled using the `glisten.with_tls` method. This requires a +certificate and key file path. The rest of the handler flow remains unchanged. + +`glisten` doesn't provide a public API for connected clients. In order to hook +into the socket lifecycle, you can establish some functions which are called +for the opening and closing of the socket. An example is provided below. + +To serve over TLS: + +```gleam +// ... +pub fn main() { + let assert Ok(_) = + glisten.new( + // omitted + ) + |> glisten.with_tls( + certfile: "/path/to/server.crt", + keyfile: "/path/to/server.key", + ) + |> glisten.start(port: 8080) + process.sleep_forever() +} +``` + +But you can also drop down to the lower level listen/accept flow if you'd prefer +to manage connections yourself, or only handle a small number at a time. + +```gleam +import gleam/result +import glisten/socket/options.{ActiveMode, Passive} +import glisten/tcp + +pub fn main() { + use listener <- result.try(tcp.listen(8000, [ActiveMode(Passive)])) + use socket <- result.try(tcp.accept(listener)) + use msg <- result.try(tcp.receive(socket, 0)) + echo #("got a msg", msg) + + Ok(Nil) +} +``` + +See [mist](https://github.com/rawhat/mist) for HTTP support built on top of this library. diff --git a/examples/http_interactions/build/packages/glisten/gleam.toml b/examples/http_interactions/build/packages/glisten/gleam.toml new file mode 100644 index 0000000..42a1ab3 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/gleam.toml @@ -0,0 +1,18 @@ +name = "glisten" +version = "9.0.1" +description = "a shiny Gleam TCP/TLS server" +licences = ["Apache-2.0"] +repository = { type = "github", user = "rawhat", repo = "glisten" } +gleam = ">= 1.11.0" + +[erlang] +extra_applications = ["ssl"] + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +logging = ">= 1.3.0 and < 2.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_AcceptorState.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_AcceptorState.hrl new file mode 100644 index 0000000..88646cf --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_AcceptorState.hrl @@ -0,0 +1,5 @@ +-record(acceptor_state, { + sender :: gleam@erlang@process:subject(glisten@internal@acceptor:acceptor_message()), + socket :: gleam@option:option(glisten@socket:socket()), + transport :: glisten@transport:transport() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_Pool.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_Pool.hrl new file mode 100644 index 0000000..7a75e7d --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@acceptor_Pool.hrl @@ -0,0 +1,10 @@ +-record(pool, { + handler :: fun((any(), glisten@internal@handler:loop_message(any()), glisten@internal@handler:connection(any())) -> glisten@internal@handler:next(any(), glisten@internal@handler:loop_message(any()))), + pool_count :: integer(), + name :: gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(any())))), + on_init :: fun((glisten@internal@handler:connection(any())) -> {any(), + gleam@option:option(gleam@erlang@process:selector(any()))}), + on_close :: gleam@option:option(fun((any()) -> nil)), + transport :: glisten@transport:transport(), + active_state :: glisten@socket@options:active_state() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_AbnormalStop.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_AbnormalStop.hrl new file mode 100644 index 0000000..d2ca52b --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_AbnormalStop.hrl @@ -0,0 +1 @@ +-record(abnormal_stop, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Connection.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Connection.hrl new file mode 100644 index 0000000..789f50a --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Connection.hrl @@ -0,0 +1,7 @@ +-record(connection, { + client_ip :: {ok, {glisten@socket@options:ip_address(), integer()}} | + {error, nil}, + socket :: glisten@socket:socket(), + transport :: glisten@transport:transport(), + sender :: gleam@erlang@process:subject(glisten@internal@handler:message(any())) +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Continue.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Continue.hrl new file mode 100644 index 0000000..85677d1 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Continue.hrl @@ -0,0 +1,4 @@ +-record(continue, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Handler.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Handler.hrl new file mode 100644 index 0000000..b750ab6 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_Handler.hrl @@ -0,0 +1,9 @@ +-record(handler, { + socket :: glisten@socket:socket(), + loop :: fun((any(), glisten@internal@handler:loop_message(any()), glisten@internal@handler:connection(any())) -> glisten@internal@handler:next(any(), glisten@internal@handler:loop_message(any()))), + on_init :: fun((glisten@internal@handler:connection(any())) -> {any(), + gleam@option:option(gleam@erlang@process:selector(any()))}), + on_close :: gleam@option:option(fun((any()) -> nil)), + transport :: glisten@transport:transport(), + active_state :: glisten@socket@options:active_state() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_LoopState.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_LoopState.hrl new file mode 100644 index 0000000..89706cc --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@handler_LoopState.hrl @@ -0,0 +1,8 @@ +-record(loop_state, { + client_ip :: {ok, {glisten@socket@options:ip_address(), integer()}} | + {error, nil}, + socket :: glisten@socket:socket(), + sender :: gleam@erlang@process:subject(glisten@internal@handler:message(any())), + transport :: glisten@transport:transport(), + state :: any() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_Info.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_Info.hrl new file mode 100644 index 0000000..d10cbaa --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_Info.hrl @@ -0,0 +1,3 @@ +-record(info, { + caller :: gleam@erlang@process:subject(glisten@internal@listener:state()) +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_State.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_State.hrl new file mode 100644 index 0000000..959ff65 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@internal@listener_State.hrl @@ -0,0 +1,5 @@ +-record(state, { + listen_socket :: glisten@socket:listen_socket(), + port :: integer(), + ip_address :: glisten@socket@options:ip_address() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten@socket@options_CertKeyFiles.hrl b/examples/http_interactions/build/packages/glisten/include/glisten@socket@options_CertKeyFiles.hrl new file mode 100644 index 0000000..6c0fcaf --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten@socket@options_CertKeyFiles.hrl @@ -0,0 +1 @@ +-record(cert_key_files, {certfile :: binary(), keyfile :: binary()}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten_Builder.hrl b/examples/http_interactions/build/packages/glisten/include/glisten_Builder.hrl new file mode 100644 index 0000000..d6b1423 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten_Builder.hrl @@ -0,0 +1,14 @@ +-record(builder, { + interface :: glisten@socket@options:interface(), + on_init :: fun((glisten:connection(any())) -> {any(), + gleam@option:option(gleam@erlang@process:selector(any()))}), + loop :: fun((any(), glisten:message(any()), glisten:connection(any())) -> glisten:next(any(), glisten:message(any()))), + on_close :: gleam@option:option(fun((any()) -> nil)), + pool_size :: integer(), + http2_support :: boolean(), + ipv6_support :: boolean(), + tls_options :: gleam@option:option(glisten@socket@options:tls_certs()), + listener_name :: gleam@option:option(gleam@erlang@process:name(glisten@internal@listener:message())), + connection_factory_name :: gleam@option:option(gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(any()))))), + active_state :: glisten@socket@options:active_state() +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten_Connection.hrl b/examples/http_interactions/build/packages/glisten/include/glisten_Connection.hrl new file mode 100644 index 0000000..f869226 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten_Connection.hrl @@ -0,0 +1,5 @@ +-record(connection, { + socket :: glisten@socket:socket(), + transport :: glisten@transport:transport(), + subject :: gleam@erlang@process:subject(glisten@internal@handler:message(any())) +}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten_ConnectionInfo.hrl b/examples/http_interactions/build/packages/glisten/include/glisten_ConnectionInfo.hrl new file mode 100644 index 0000000..0774ca8 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten_ConnectionInfo.hrl @@ -0,0 +1 @@ +-record(connection_info, {port :: integer(), ip_address :: glisten:ip_address()}). diff --git a/examples/http_interactions/build/packages/glisten/include/glisten_Continue.hrl b/examples/http_interactions/build/packages/glisten/include/glisten_Continue.hrl new file mode 100644 index 0000000..85677d1 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/include/glisten_Continue.hrl @@ -0,0 +1,4 @@ +-record(continue, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten.app.src b/examples/http_interactions/build/packages/glisten/src/glisten.app.src new file mode 100644 index 0000000..4f87d4b --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten.app.src @@ -0,0 +1,23 @@ +{application, glisten, [ + {vsn, "9.0.1"}, + {applications, [gleam_erlang, + gleam_otp, + gleam_stdlib, + logging, + ssl]}, + {description, "a shiny Gleam TCP/TLS server"}, + {modules, [glisten, + glisten@@main, + glisten@internal@acceptor, + glisten@internal@handler, + glisten@internal@listener, + glisten@socket, + glisten@socket@options, + glisten@ssl, + glisten@tcp, + glisten@transport, + glisten_ffi, + glisten_ssl_ffi, + glisten_tcp_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/glisten/src/glisten.erl b/examples/http_interactions/build/packages/glisten/src/glisten.erl new file mode 100644 index 0000000..4068854 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten.erl @@ -0,0 +1,653 @@ +-module(glisten). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten.gleam"). +-export([convert_ip_address/1, get_server_info/2, ip_address_to_string/1, get_connection_info/1, send/2, continue/1, with_selector/2, stop/0, stop_abnormal/1, convert_next/1, map_selector/2, new/2, with_close/2, with_pool_size/2, with_http2/1, with_ipv6/1, with_tls/3, with_active_state/2, with_listener_name/2, with_connection_factory_name/2, start/2, bind/2, supervised/2]). +-export_type([message/1, ip_address/0, connection_info/0, connection/1, next/2, builder/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type message(GOO) :: {packet, bitstring()} | {user, GOO}. + +-type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} | + {ip_v6, + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. + +-type connection_info() :: {connection_info, integer(), ip_address()}. + +-type connection(GOP) :: {connection, + glisten@socket:socket(), + glisten@transport:transport(), + gleam@erlang@process:subject(glisten@internal@handler:message(GOP))}. + +-opaque next(GOQ, GOR) :: {continue, + GOQ, + gleam@option:option(gleam@erlang@process:selector(GOR))} | + normal_stop | + {abnormal_stop, binary()}. + +-opaque builder(GOS, GOT) :: {builder, + glisten@socket@options:interface(), + fun((connection(GOT)) -> {GOS, + gleam@option:option(gleam@erlang@process:selector(GOT))}), + fun((GOS, message(GOT), connection(GOT)) -> next(GOS, message(GOT))), + gleam@option:option(fun((GOS) -> nil)), + integer(), + boolean(), + boolean(), + gleam@option:option(glisten@socket@options:tls_certs()), + gleam@option:option(gleam@erlang@process:name(glisten@internal@listener:message())), + gleam@option:option(gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GOT))))), + glisten@socket@options:active_state()}. + +-file("src/glisten.gleam", 68). +?DOC(false). +-spec convert_ip_address(glisten@socket@options:ip_address()) -> ip_address(). +convert_ip_address(Ip) -> + case Ip of + {ip_v4, A, B, C, D} -> + {ip_v4, A, B, C, D}; + + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} + end. + +-file("src/glisten.gleam", 48). +?DOC(" Returns the user-provided port or the OS-assigned value if 0 was provided.\n"). +-spec get_server_info( + gleam@erlang@process:name(glisten@internal@listener:message()), + integer() +) -> connection_info(). +get_server_info(Listener, Timeout) -> + Listener@1 = gleam@erlang@process:named_subject(Listener), + State = gleam@erlang@process:call( + Listener@1, + Timeout, + fun(Field@0) -> {info, Field@0} end + ), + {connection_info, + erlang:element(3, State), + convert_ip_address(erlang:element(4, State))}. + +-file("src/glisten.gleam", 97). +-spec join_ipv6_fields(list(integer())) -> binary(). +join_ipv6_fields(Fields) -> + _pipe = gleam@list:map(Fields, fun gleam@int:to_base16/1), + gleam@string:join(_pipe, <<":"/utf8>>). + +-file("src/glisten.gleam", 106). +?DOC( + " Finds the longest sequence of consecutive all-zero fields in an IPv6.\n" + " If the address contains multiple runs of all-zero fields of the same size,\n" + " it is the leftmost that is compressed.\n" + "\n" + " This returns the start & end indices of the compressed zeros.\n" +). +-spec ipv6_zeros(list(integer()), integer(), integer(), integer(), integer()) -> {ok, + {integer(), integer()}} | + {error, nil}. +ipv6_zeros(Fields, Pos, Len, Max_start, Max_len) -> + case Fields of + [] when Max_len > 1 -> + {ok, {Max_start, Max_start + Max_len}}; + + [] -> + {error, nil}; + + [X | Xs] when X =:= 0 -> + Len@1 = Len + 1, + case Len@1 > Max_len of + true -> + ipv6_zeros(Xs, Pos + 1, Len@1, (Pos + 1) - Len@1, Len@1); + + false -> + ipv6_zeros(Xs, Pos + 1, Len@1, Max_start, Max_len) + end; + + [_ | Xs@1] -> + ipv6_zeros(Xs@1, Pos + 1, 0, Max_start, Max_len) + end. + +-file("src/glisten.gleam", 77). +?DOC( + " Convenience function for convert an `IpAddress` type into a string. It will\n" + " convert IPv6 addresses to the canonical short-hand (ie. loopback is ::1).\n" +). +-spec ip_address_to_string(ip_address()) -> binary(). +ip_address_to_string(Address) -> + case Address of + {ip_v4, A, B, C, D} -> + _pipe = [A, B, C, D], + _pipe@1 = gleam@list:map(_pipe, fun erlang:integer_to_binary/1), + gleam@string:join(_pipe@1, <<"."/utf8>>); + + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> + Fields = [A@1, B@1, C@1, D@1, E, F, G, H], + _pipe@2 = case ipv6_zeros(Fields, 0, 0, 0, 0) of + {error, _} -> + join_ipv6_fields(Fields); + + {ok, {Start, End}} -> + <<<<(join_ipv6_fields(gleam@list:take(Fields, Start)))/binary, + "::"/utf8>>/binary, + (join_ipv6_fields(gleam@list:drop(Fields, End)))/binary>> + end, + string:lowercase(_pipe@2) + end. + +-file("src/glisten.gleam", 127). +?DOC( + " Tries to read the IP address and port of a connected client. It will\n" + " return valid IPv4 or IPv6 addresses, attempting to return the most relevant\n" + " one for the client.\n" +). +-spec get_connection_info(connection(any())) -> {ok, connection_info()} | + {error, nil}. +get_connection_info(Conn) -> + _pipe = glisten@transport:peername( + erlang:element(3, Conn), + erlang:element(2, Conn) + ), + gleam@result:map( + _pipe, + fun(Pair) -> + {connection_info, + erlang:element(2, Pair), + convert_ip_address(erlang:element(1, Pair))} + end + ). + +-file("src/glisten.gleam", 135). +?DOC(" Sends a BytesTree message over the socket using the active transport\n"). +-spec send(connection(any()), gleam@bytes_tree:bytes_tree()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +send(Conn, Msg) -> + glisten@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Msg + ). + +-file("src/glisten.gleam", 148). +-spec continue(GPT) -> next(GPT, any()). +continue(State) -> + {continue, State, none}. + +-file("src/glisten.gleam", 152). +-spec with_selector(next(GPX, GPY), gleam@erlang@process:selector(GPY)) -> next(GPX, GPY). +with_selector(Next, Selector) -> + case Next of + {continue, State, _} -> + {continue, State, {some, Selector}}; + + Stop -> + Stop + end. + +-file("src/glisten.gleam", 162). +-spec stop() -> next(any(), any()). +stop() -> + normal_stop. + +-file("src/glisten.gleam", 166). +-spec stop_abnormal(binary()) -> next(any(), any()). +stop_abnormal(Reason) -> + {abnormal_stop, Reason}. + +-file("src/glisten.gleam", 171). +?DOC(false). +-spec convert_next(next(GQM, GQN)) -> glisten@internal@handler:next(GQM, GQN). +convert_next(Next) -> + case Next of + {continue, State, Selector} -> + {continue, State, Selector}; + + normal_stop -> + normal_stop; + + {abnormal_stop, Reason} -> + {abnormal_stop, Reason} + end. + +-file("src/glisten.gleam", 182). +?DOC(false). +-spec map_selector(next(GQS, GQT), fun((GQT) -> GQW)) -> next(GQS, GQW). +map_selector(Next, Mapper) -> + case Next of + {continue, State, {some, Selector}} -> + {continue, + State, + {some, gleam_erlang_ffi:map_selector(Selector, Mapper)}}; + + {continue, State@1, none} -> + {continue, State@1, none}; + + {abnormal_stop, Reason} -> + {abnormal_stop, Reason}; + + normal_stop -> + normal_stop + end. + +-file("src/glisten.gleam", 222). +-spec map_user_selector(gleam@erlang@process:selector(message(GQZ))) -> gleam@erlang@process:selector(glisten@internal@handler:loop_message(GQZ)). +map_user_selector(Selector) -> + gleam_erlang_ffi:map_selector(Selector, fun(Value) -> case Value of + {packet, Msg} -> + {packet, Msg}; + + {user, Msg@1} -> + {custom, Msg@1} + end end). + +-file("src/glisten.gleam", 233). +-spec convert_loop( + fun((GRE, message(GRF), connection(GRF)) -> next(GRE, message(GRF))) +) -> fun((GRE, glisten@internal@handler:loop_message(GRF), glisten@internal@handler:connection(GRF)) -> glisten@internal@handler:next(GRE, glisten@internal@handler:loop_message(GRF))). +convert_loop(Loop) -> + fun(Data, Msg, Conn) -> + Conn@1 = {connection, + erlang:element(3, Conn), + erlang:element(4, Conn), + erlang:element(5, Conn)}, + Message = case Msg of + {packet, Msg@1} -> + {packet, Msg@1}; + + {custom, Msg@2} -> + {user, Msg@2} + end, + case Loop(Data, Message, Conn@1) of + {continue, Data@1, Selector} -> + case Selector of + {some, Selector@1} -> + _pipe = glisten@internal@handler:continue(Data@1), + glisten@internal@handler:with_selector( + _pipe, + map_user_selector(Selector@1) + ); + + _ -> + glisten@internal@handler:continue(Data@1) + end; + + normal_stop -> + glisten@internal@handler:stop(); + + {abnormal_stop, Reason} -> + glisten@internal@handler:stop_abnormal(Reason) + end + end. + +-file("src/glisten.gleam", 257). +-spec convert_on_init( + fun((connection(GRK)) -> {GRM, + gleam@option:option(gleam@erlang@process:selector(GRK))}) +) -> fun((glisten@internal@handler:connection(GRK)) -> {GRM, + gleam@option:option(gleam@erlang@process:selector(GRK))}). +convert_on_init(On_init) -> + fun(Conn) -> + Connection = {connection, + erlang:element(3, Conn), + erlang:element(4, Conn), + erlang:element(5, Conn)}, + On_init(Connection) + end. + +-file("src/glisten.gleam", 276). +?DOC( + " Create a new handler for each connection. The required arguments mirror the\n" + " `actor.start` API from `gleam_otp`. The default pool is 10 accceptor\n" + " processes.\n" +). +-spec new( + fun((connection(GRS)) -> {GRU, + gleam@option:option(gleam@erlang@process:selector(GRS))}), + fun((GRU, message(GRS), connection(GRS)) -> next(GRU, message(GRS))) +) -> builder(GRU, GRS). +new(On_init, Loop) -> + {builder, + loopback, + On_init, + Loop, + none, + 10, + false, + false, + none, + none, + none, + once}. + +-file("src/glisten.gleam", 297). +?DOC(" Adds a function to the handler to be called when the connection is closed.\n"). +-spec with_close(builder(GSB, GSC), fun((GSB) -> nil)) -> builder(GSB, GSC). +with_close(Builder, On_close) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + {some, On_close}, + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 305). +?DOC(" Modify the size of the acceptor pool\n"). +-spec with_pool_size(builder(GSH, GSI), integer()) -> builder(GSH, GSI). +with_pool_size(Builder, Size) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + Size, + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 316). +?DOC(false). +-spec with_http2(builder(GSN, GSO)) -> builder(GSN, GSO). +with_http2(Builder) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + true, + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 344). +?DOC( + " By default, `glisten` listens on `localhost` only over IPv4. With an IPv4\n" + " address, you can call this builder method to also serve over IPv6 on that\n" + " interface. If it is not supported, your application will crash. If you\n" + " call this with an IPv6 interface specified, it will have no effect.\n" +). +-spec with_ipv6(builder(GSZ, GTA)) -> builder(GSZ, GTA). +with_ipv6(Builder) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + true, + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 351). +?DOC(" To use TLS, provide a path to a certficate and key file.\n"). +-spec with_tls(builder(GTF, GTG), binary(), binary()) -> builder(GTF, GTG). +with_tls(Builder, Cert, Key) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + {some, {cert_key_files, Cert, Key}}, + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 361). +?DOC( + " Set the server's `ActiveState` for flow control of received packets.\n" + " Default is `Once`. Allowed are `Once`, `Active` and `Count(n)` where n > 1.\n" +). +-spec with_active_state( + builder(GTL, GTM), + glisten@socket@options:active_state() +) -> builder(GTL, GTM). +with_active_state(Builder, Active_state) -> + case Active_state of + once -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + Active_state}; + + active -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + Active_state}; + + {count, N} when N > 1 -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + Active_state}; + + {count, _} -> + erlang:error(#{gleam_error => panic, + message => <<"Count shall be greater than 1"/utf8>>, + file => <>, + module => <<"glisten"/utf8>>, + function => <<"with_active_state"/utf8>>, + line => 369}); + + passive -> + erlang:error(#{gleam_error => panic, + message => <<"You cannot set the server's `ActiveState` to `Passive`"/utf8>>, + file => <>, + module => <<"glisten"/utf8>>, + function => <<"with_active_state"/utf8>>, + line => 371}) + end. + +-file("src/glisten.gleam", 376). +?DOC(false). +-spec with_listener_name( + builder(GTR, GTS), + gleam@erlang@process:name(glisten@internal@listener:message()) +) -> builder(GTR, GTS). +with_listener_name(Builder, Listener_name) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + {some, Listener_name}, + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 384). +?DOC(false). +-spec with_connection_factory_name( + builder(GTY, GTZ), + gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GTZ)))) +) -> builder(GTY, GTZ). +with_connection_factory_name(Builder, Connection_factory_name) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + {some, Connection_factory_name}, + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 394). +?DOC(" Start the TCP server with the given handler on the provided port\n"). +-spec start(builder(any(), any()), integer()) -> {ok, + gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} | + {error, gleam@otp@actor:start_error()}. +start(Builder, Port) -> + Listener_name = gleam@option:unwrap( + erlang:element(10, Builder), + gleam_erlang_ffi:new_name(<<"glisten_listener"/utf8>>) + ), + Connection_supervisor = gleam@option:unwrap( + erlang:element(11, Builder), + gleam_erlang_ffi:new_name(<<"glisten_connection_supervisor"/utf8>>) + ), + Options = begin + _pipe = [{ip, erlang:element(2, Builder)}], + _pipe@1 = lists:append(_pipe, case erlang:element(8, Builder) of + true -> + [ipv6]; + + false -> + [] + end), + _pipe@2 = lists:append(_pipe@1, case erlang:element(9, Builder) of + {some, Opts} -> + [{cert_key_config, Opts}]; + + _ -> + [] + end), + lists:append( + _pipe@2, + case {erlang:element(9, Builder), erlang:element(7, Builder)} of + {{some, _}, true} -> + [{alpn_preferred_protocols, + [<<"h2"/utf8>>, <<"http/1.1"/utf8>>]}]; + + {{some, _}, false} -> + [{alpn_preferred_protocols, [<<"http/1.1"/utf8>>]}]; + + {none, _} -> + [] + end + ) + end, + Transport = case erlang:element(9, Builder) of + {some, _} -> + ssl; + + _ -> + tcp + end, + _pipe@3 = {pool, + convert_loop(erlang:element(4, Builder)), + erlang:element(6, Builder), + Connection_supervisor, + convert_on_init(erlang:element(3, Builder)), + erlang:element(5, Builder), + Transport, + erlang:element(12, Builder)}, + glisten@internal@acceptor:start_pool( + _pipe@3, + Transport, + Port, + Options, + Listener_name + ). + +-file("src/glisten.gleam", 326). +?DOC( + " This sets the interface for `glisten` to listen on. It accepts the following\n" + " strings: \"localhost\", valid IPv4 addresses (i.e. \"127.0.0.1\"), and valid\n" + " IPv6 addresses (i.e. \"::1\"). If an invalid value is provided, this will\n" + " panic.\n" +). +-spec bind(builder(GST, GSU), binary()) -> builder(GST, GSU). +bind(Builder, Interface) -> + Address@1 = case {Interface, + glisten_ffi:parse_address(unicode:characters_to_list(Interface))} of + {<<"0.0.0.0"/utf8>>, _} -> + any; + + {<<"localhost"/utf8>>, _} -> + loopback; + + {<<"127.0.0.1"/utf8>>, _} -> + loopback; + + {_, {ok, Address}} -> + {address, Address}; + + {_, {error, _}} -> + erlang:error(#{gleam_error => panic, + message => <<"Invalid interface provided: must be a valid IPv4/IPv6 address, or \"localhost\""/utf8>>, + file => <>, + module => <<"glisten"/utf8>>, + function => <<"bind"/utf8>>, + line => 335}) + end, + {builder, + Address@1, + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder), + erlang:element(11, Builder), + erlang:element(12, Builder)}. + +-file("src/glisten.gleam", 443). +?DOC( + " Helper method for building a child specification for use in a supervision\n" + " tree.\n" +). +-spec supervised(builder(any(), any()), integer()) -> gleam@otp@supervision:child_specification(gleam@otp@static_supervisor:supervisor()). +supervised(Handler, Port) -> + gleam@otp@supervision:supervisor(fun() -> start(Handler, Port) end). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten.gleam b/examples/http_interactions/build/packages/glisten/src/glisten.gleam new file mode 100644 index 0000000..901bd64 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten.gleam @@ -0,0 +1,448 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/charlist.{type Charlist} +import gleam/erlang/process.{type Selector, type Subject} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/otp/factory_supervisor as factory +import gleam/otp/static_supervisor as supervisor +import gleam/otp/supervision.{type ChildSpecification} +import gleam/result +import gleam/string +import glisten/internal/acceptor.{Pool} +import glisten/internal/handler +import glisten/internal/listener +import glisten/socket.{ + type Socket as InternalSocket, type SocketReason as InternalSocketReason, +} +import glisten/socket/options +import glisten/transport.{type Transport} + +/// Your provided loop function will receive these message types as the +/// second argument. +pub type Message(user_message) { + /// These are messages received from the socket + Packet(BitArray) + /// These are any messages received from the selector returned from `on_init` + User(user_message) +} + +/// This is used to describe the connecting client's IP address. +pub type IpAddress { + IpV4(Int, Int, Int, Int) + IpV6(Int, Int, Int, Int, Int, Int, Int, Int) +} + +pub type Socket = + InternalSocket + +pub type SocketReason = + InternalSocketReason + +pub type ConnectionInfo { + ConnectionInfo(port: Int, ip_address: IpAddress) +} + +/// Returns the user-provided port or the OS-assigned value if 0 was provided. +pub fn get_server_info( + listener: process.Name(listener.Message), + timeout: Int, +) -> ConnectionInfo { + let listener = process.named_subject(listener) + let state = process.call(listener, timeout, listener.Info) + ConnectionInfo(state.port, convert_ip_address(state.ip_address)) +} + +/// This type holds useful bits of data for the active connection. +pub type Connection(user_message) { + Connection( + socket: Socket, + /// This provides a uniform interface for both TCP and TLS methods. + transport: Transport, + subject: Subject(handler.Message(user_message)), + ) +} + +@internal +pub fn convert_ip_address(ip: options.IpAddress) -> IpAddress { + case ip { + options.IpV4(a, b, c, d) -> IpV4(a, b, c, d) + options.IpV6(a, b, c, d, e, f, g, h) -> IpV6(a, b, c, d, e, f, g, h) + } +} + +/// Convenience function for convert an `IpAddress` type into a string. It will +/// convert IPv6 addresses to the canonical short-hand (ie. loopback is ::1). +pub fn ip_address_to_string(address: IpAddress) -> String { + case address { + IpV4(a, b, c, d) -> + [a, b, c, d] + |> list.map(int.to_string) + |> string.join(".") + IpV6(a, b, c, d, e, f, g, h) -> { + let fields = [a, b, c, d, e, f, g, h] + case ipv6_zeros(fields, 0, 0, 0, 0) { + Error(_) -> join_ipv6_fields(fields) + Ok(#(start, end)) -> + join_ipv6_fields(list.take(fields, start)) + <> "::" + <> join_ipv6_fields(list.drop(fields, end)) + } + |> string.lowercase + } + } +} + +fn join_ipv6_fields(fields) { + list.map(fields, int.to_base16) |> string.join(":") +} + +/// Finds the longest sequence of consecutive all-zero fields in an IPv6. +/// If the address contains multiple runs of all-zero fields of the same size, +/// it is the leftmost that is compressed. +/// +/// This returns the start & end indices of the compressed zeros. +fn ipv6_zeros(fields, pos, len, max_start, max_len) -> Result(#(Int, Int), Nil) { + case fields { + [] if max_len > 1 -> Ok(#(max_start, max_start + max_len)) + [] -> Error(Nil) + [x, ..xs] if x == 0 -> { + let len = len + 1 + case len > max_len { + // Biggest sequence yet + True -> ipv6_zeros(xs, pos + 1, len, pos + 1 - len, len) + // Continue to grow current sequence + False -> ipv6_zeros(xs, pos + 1, len, max_start, max_len) + } + } + // Continue to search for zeros + [_, ..xs] -> ipv6_zeros(xs, pos + 1, 0, max_start, max_len) + } +} + +/// Tries to read the IP address and port of a connected client. It will +/// return valid IPv4 or IPv6 addresses, attempting to return the most relevant +/// one for the client. +pub fn get_connection_info( + conn: Connection(user_message), +) -> Result(ConnectionInfo, Nil) { + transport.peername(conn.transport, conn.socket) + |> result.map(fn(pair) { ConnectionInfo(pair.1, convert_ip_address(pair.0)) }) +} + +/// Sends a BytesTree message over the socket using the active transport +pub fn send( + conn: Connection(user_message), + msg: BytesTree, +) -> Result(Nil, SocketReason) { + transport.send(conn.transport, conn.socket, msg) +} + +pub opaque type Next(user_state, user_message) { + Continue(state: user_state, selector: Option(Selector(user_message))) + NormalStop + AbnormalStop(String) +} + +pub fn continue(state: user_state) -> Next(user_state, user_message) { + Continue(state, None) +} + +pub fn with_selector( + next: Next(user_state, user_message), + selector: Selector(user_message), +) -> Next(user_state, user_message) { + case next { + Continue(state, _) -> Continue(state, Some(selector)) + stop -> stop + } +} + +pub fn stop() -> Next(user_state, user_message) { + NormalStop +} + +pub fn stop_abnormal(reason: String) -> Next(user_state, user_message) { + AbnormalStop(reason) +} + +@internal +pub fn convert_next( + next: Next(state, user_message), +) -> handler.Next(state, user_message) { + case next { + Continue(state, selector) -> handler.Continue(state, selector) + NormalStop -> handler.NormalStop + AbnormalStop(reason) -> handler.AbnormalStop(reason) + } +} + +@internal +pub fn map_selector( + next: Next(state, user_message), + mapper: fn(user_message) -> other_message, +) -> Next(state, other_message) { + case next { + Continue(state, Some(selector)) -> + Continue(state, Some(process.map_selector(selector, mapper))) + Continue(state, None) -> Continue(state, None) + AbnormalStop(reason) -> AbnormalStop(reason) + NormalStop -> NormalStop + } +} + +/// This is the shape of the function you need to provide for the `handler` +/// argument to `start`. +pub type Loop(state, user_message) = + fn(state, Message(user_message), Connection(user_message)) -> + Next(state, Message(user_message)) + +pub opaque type Builder(state, user_message) { + Builder( + interface: options.Interface, + on_init: fn(Connection(user_message)) -> + #(state, Option(Selector(user_message))), + loop: Loop(state, user_message), + on_close: Option(fn(state) -> Nil), + pool_size: Int, + http2_support: Bool, + ipv6_support: Bool, + tls_options: Option(options.TlsCerts), + listener_name: Option(process.Name(listener.Message)), + connection_factory_name: Option( + process.Name( + factory.Message(Socket, Subject(handler.Message(user_message))), + ), + ), + active_state: options.ActiveState, + ) +} + +fn map_user_selector( + selector: Selector(Message(user_message)), +) -> Selector(handler.LoopMessage(user_message)) { + process.map_selector(selector, fn(value) { + case value { + Packet(msg) -> handler.Packet(msg) + User(msg) -> handler.Custom(msg) + } + }) +} + +fn convert_loop( + loop: Loop(state, user_message), +) -> handler.Loop(state, user_message) { + fn(data, msg, conn: handler.Connection(user_message)) { + let conn = Connection(conn.socket, conn.transport, conn.sender) + let message = case msg { + handler.Packet(msg) -> Packet(msg) + handler.Custom(msg) -> User(msg) + } + case loop(data, message, conn) { + Continue(data, selector) -> + case selector { + Some(selector) -> + handler.continue(data) + |> handler.with_selector(map_user_selector(selector)) + _ -> handler.continue(data) + } + + NormalStop -> handler.stop() + AbnormalStop(reason) -> handler.stop_abnormal(reason) + } + } +} + +fn convert_on_init( + on_init: fn(Connection(user_message)) -> + #(state, Option(Selector(user_message))), +) -> fn(handler.Connection(user_message)) -> + #(state, Option(Selector(user_message))) { + fn(conn: handler.Connection(user_message)) { + let connection = + Connection( + subject: conn.sender, + socket: conn.socket, + transport: conn.transport, + ) + on_init(connection) + } +} + +/// Create a new handler for each connection. The required arguments mirror the +/// `actor.start` API from `gleam_otp`. The default pool is 10 accceptor +/// processes. +pub fn new( + on_init: fn(Connection(user_message)) -> + #(state, Option(Selector(user_message))), + loop: Loop(state, user_message), +) -> Builder(state, user_message) { + Builder( + interface: options.Loopback, + on_init: on_init, + loop: loop, + on_close: None, + pool_size: 10, + http2_support: False, + ipv6_support: False, + tls_options: None, + listener_name: None, + connection_factory_name: None, + active_state: options.Once, + ) +} + +/// Adds a function to the handler to be called when the connection is closed. +pub fn with_close( + builder: Builder(state, user_message), + on_close: fn(state) -> Nil, +) -> Builder(state, user_message) { + Builder(..builder, on_close: Some(on_close)) +} + +/// Modify the size of the acceptor pool +pub fn with_pool_size( + builder: Builder(state, user_message), + size: Int, +) -> Builder(state, user_message) { + Builder(..builder, pool_size: size) +} + +/// Sets the ALPN supported protocols to include HTTP/2. It's currently being +/// exposed only for `mist` to provide this support. For a TCP library, you +/// definitely do not need it. +@internal +pub fn with_http2( + builder: Builder(state, user_message), +) -> Builder(state, user_message) { + Builder(..builder, http2_support: True) +} + +/// This sets the interface for `glisten` to listen on. It accepts the following +/// strings: "localhost", valid IPv4 addresses (i.e. "127.0.0.1"), and valid +/// IPv6 addresses (i.e. "::1"). If an invalid value is provided, this will +/// panic. +pub fn bind( + builder: Builder(state, user_message), + interface: String, +) -> Builder(state, user_message) { + let address = case interface, parse_address(charlist.from_string(interface)) { + "0.0.0.0", _ -> options.Any + "localhost", _ | "127.0.0.1", _ -> options.Loopback + _, Ok(address) -> options.Address(address) + _, Error(_nil) -> + panic as "Invalid interface provided: must be a valid IPv4/IPv6 address, or \"localhost\"" + } + Builder(..builder, interface: address) +} + +/// By default, `glisten` listens on `localhost` only over IPv4. With an IPv4 +/// address, you can call this builder method to also serve over IPv6 on that +/// interface. If it is not supported, your application will crash. If you +/// call this with an IPv6 interface specified, it will have no effect. +pub fn with_ipv6( + builder: Builder(state, user_message), +) -> Builder(state, user_message) { + Builder(..builder, ipv6_support: True) +} + +/// To use TLS, provide a path to a certficate and key file. +pub fn with_tls( + builder: Builder(state, user_message), + certfile cert: String, + keyfile key: String, +) -> Builder(state, user_message) { + Builder(..builder, tls_options: Some(options.CertKeyFiles(cert, key))) +} + +/// Set the server's `ActiveState` for flow control of received packets. +/// Default is `Once`. Allowed are `Once`, `Active` and `Count(n)` where n > 1. +pub fn with_active_state( + builder: Builder(state, user_message), + active_state: options.ActiveState, +) -> Builder(state, user_message) { + case active_state { + options.Once | options.Active -> + Builder(..builder, active_state: active_state) + options.Count(n) if n > 1 -> Builder(..builder, active_state: active_state) + options.Count(_) -> panic as "Count shall be greater than 1" + options.Passive -> + panic as "You cannot set the server's `ActiveState` to `Passive`" + } +} + +@internal +pub fn with_listener_name( + builder: Builder(state, user_message), + listener_name: process.Name(listener.Message), +) -> Builder(state, user_message) { + Builder(..builder, listener_name: Some(listener_name)) +} + +@internal +pub fn with_connection_factory_name( + builder: Builder(state, user_message), + connection_factory_name: process.Name( + factory.Message(socket.Socket, Subject(handler.Message(user_message))), + ), +) -> Builder(state, user_message) { + Builder(..builder, connection_factory_name: Some(connection_factory_name)) +} + +/// Start the TCP server with the given handler on the provided port +pub fn start( + builder: Builder(state, user_message), + port: Int, +) -> Result(actor.Started(supervisor.Supervisor), actor.StartError) { + let listener_name = + option.unwrap(builder.listener_name, process.new_name("glisten_listener")) + let connection_supervisor = + option.unwrap( + builder.connection_factory_name, + process.new_name("glisten_connection_supervisor"), + ) + let options = + [options.Ip(builder.interface)] + |> list.append(case builder.ipv6_support { + True -> [options.Ipv6] + False -> [] + }) + |> list.append(case builder.tls_options { + Some(opts) -> [options.CertKeyConfig(opts)] + _ -> [] + }) + |> list.append(case builder.tls_options, builder.http2_support { + Some(_), True -> [options.AlpnPreferredProtocols(["h2", "http/1.1"])] + Some(_), False -> [options.AlpnPreferredProtocols(["http/1.1"])] + None, _ -> [] + }) + + let transport = case builder.tls_options { + Some(_) -> transport.Ssl + _ -> transport.Tcp + } + + Pool( + handler: convert_loop(builder.loop), + name: connection_supervisor, + pool_count: builder.pool_size, + on_init: convert_on_init(builder.on_init), + on_close: builder.on_close, + transport:, + active_state: builder.active_state, + ) + |> acceptor.start_pool(transport, port, options, listener_name) +} + +@external(erlang, "glisten_ffi", "parse_address") +fn parse_address(value: Charlist) -> Result(ip_address, Nil) + +/// Helper method for building a child specification for use in a supervision +/// tree. +pub fn supervised( + handler: Builder(state, user_message), + port: Int, +) -> ChildSpecification(supervisor.Supervisor) { + supervision.supervisor(fn() { start(handler, port) }) +} diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/internal/acceptor.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/internal/acceptor.gleam new file mode 100644 index 0000000..8bd8588 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/internal/acceptor.gleam @@ -0,0 +1,174 @@ +import gleam/erlang/atom +import gleam/erlang/process.{type Selector, type Subject} +import gleam/int +import gleam/option.{type Option, None} +import gleam/otp/actor +import gleam/otp/factory_supervisor as factory +import gleam/otp/static_supervisor as supervisor +import gleam/otp/supervision +import gleam/result +import glisten/internal/handler.{ + type Connection, type Loop, Handler, Internal, Ready, +} +import glisten/internal/listener +import glisten/socket.{type ListenSocket, type Socket} +import glisten/socket/options.{type TcpOption} +import glisten/transport.{type Transport} +import logging + +pub type AcceptorMessage { + AcceptConnection(ListenSocket) +} + +pub type AcceptorError { + AcceptError(socket.SocketReason) + HandlerError(actor.StartError) + ControlError(atom.Atom) +} + +pub type AcceptorState { + AcceptorState( + sender: Subject(AcceptorMessage), + socket: Option(Socket), + transport: Transport, + ) +} + +/// Worker process that handles `accept`ing connections and starts a new process +/// which receives the messages from the socket +pub fn start( + pool: Pool(data, user_message), + listener_name: process.Name(listener.Message), + connection_supervisor: process.Name( + factory.Message(Socket, Subject(handler.Message(user_message))), + ), +) -> Result(actor.Started(Subject(AcceptorMessage)), actor.StartError) { + actor.new_with_initialiser(1000, fn(subject) { + let listener = process.named_subject(listener_name) + + let state = process.call(listener, 750, listener.Info) + process.send(subject, AcceptConnection(state.listen_socket)) + + AcceptorState(subject, None, pool.transport) + |> actor.initialised + |> actor.returning(subject) + |> actor.selecting( + process.new_selector() + |> process.select(subject), + ) + |> Ok + }) + |> actor.on_message(fn(state, msg) { + let AcceptorState(sender, ..) = state + case msg { + AcceptConnection(listener) -> { + let res = { + use sock <- result.try( + transport.accept(state.transport, listener) + |> result.map_error(AcceptError), + ) + let connection_factory = factory.get_by_name(connection_supervisor) + case factory.start_child(connection_factory, sock) { + Ok(start) -> { + transport.controlling_process(state.transport, sock, start.pid) + |> result.map_error(ControlError) + |> result.map(fn(_) { process.send(start.data, Internal(Ready)) }) + } + Error(reason) -> { + Error(HandlerError(reason)) + } + } + } + case res { + Error(reason) -> { + let msg = case reason { + AcceptError(reason) -> + "acceptor failed: " <> socket.reason_to_string(reason) + HandlerError(actor.InitTimeout) -> "init timed out" + HandlerError(actor.InitFailed(reason)) -> + "init failed: " <> reason + HandlerError(actor.InitExited(process.Normal)) -> + "init exited normally" + HandlerError(actor.InitExited(process.Killed)) -> "init killed" + HandlerError(actor.InitExited(process.Abnormal(..))) -> + "init exited abnormally" + ControlError(reason) -> + "could not control socket: " <> atom.to_string(reason) + } + logging.log( + logging.Error, + "Failed to accept/start handler: " <> msg, + ) + actor.stop_abnormal("Failed to accept/start handler") + } + _val -> { + actor.send(sender, AcceptConnection(listener)) + actor.continue(state) + } + } + } + } + }) + |> actor.start +} + +pub type Pool(data, user_message) { + Pool( + handler: Loop(data, user_message), + pool_count: Int, + name: process.Name( + factory.Message(Socket, Subject(handler.Message(user_message))), + ), + on_init: fn(Connection(user_message)) -> + #(data, Option(Selector(user_message))), + on_close: Option(fn(data) -> Nil), + transport: Transport, + active_state: options.ActiveState, + ) +} + +/// Starts a pool of acceptors of size `pool_count`. +/// +/// Runs `loop_fn` on ever message received +pub fn start_pool( + pool: Pool(data, user_message), + transport: Transport, + port: Int, + options: List(TcpOption), + listener_name: process.Name(listener.Message), +) -> Result(actor.Started(supervisor.Supervisor), actor.StartError) { + supervisor.new(supervisor.OneForOne) + |> supervisor.add( + supervision.worker(fn() { + listener.start(port, transport, options, listener_name) + }), + ) + |> supervisor.add( + supervision.supervisor(fn() { + supervisor.new(supervisor.OneForOne) + |> int.range(from: 0, to: pool.pool_count, with: _, run: fn(sup, _index) { + supervisor.add( + sup, + supervision.worker(fn() { start(pool, listener_name, pool.name) }), + ) + }) + |> supervisor.start + }), + ) + |> supervisor.add( + factory.worker_child(fn(socket) { + handler.start(Handler( + socket:, + loop: pool.handler, + on_init: pool.on_init, + on_close: pool.on_close, + transport: pool.transport, + active_state: pool.active_state, + )) + }) + |> factory.named(pool.name) + |> factory.restart_strategy(supervision.Temporary) + |> factory.supervised, + ) + |> supervisor.start +} diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/internal/handler.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/internal/handler.gleam new file mode 100644 index 0000000..a5a4ab4 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/internal/handler.gleam @@ -0,0 +1,283 @@ +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom +import gleam/erlang/process.{type Selector, type Subject} +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/result +import gleam/string +import glisten/socket.{type Socket, type SocketReason} +import glisten/socket/options.{type ActiveState, type IpAddress} +import glisten/transport.{type Transport} +import logging + +@external(erlang, "glisten_ffi", "rescue") +fn rescue(func: fn() -> anything) -> Result(anything, Dynamic) + +/// All message types that the handler will receive, or that you can +/// send to the handler process +pub type InternalMessage { + Close + Ready + ReceiveMessage(BitArray) + Closed + Passive + SocketError(SocketReason) +} + +pub type Message(user_message) { + Internal(InternalMessage) + User(user_message) +} + +pub type LoopMessage(user_message) { + Packet(BitArray) + Custom(user_message) +} + +pub type ClientIp = + Result(#(IpAddress, Int), Nil) + +pub type LoopState(state, user_message) { + LoopState( + client_ip: ClientIp, + socket: Socket, + sender: Subject(Message(user_message)), + transport: Transport, + state: state, + ) +} + +pub type Connection(user_message) { + Connection( + client_ip: ClientIp, + socket: Socket, + transport: Transport, + sender: Subject(Message(user_message)), + ) +} + +pub type Next(user_state, user_message) { + Continue(state: user_state, selector: Option(Selector(user_message))) + NormalStop + AbnormalStop(reason: String) +} + +pub fn continue(state: user_state) -> Next(user_state, user_message) { + Continue(state, None) +} + +pub fn with_selector( + next: Next(user_state, user_message), + selector: Selector(user_message), +) -> Next(user_state, user_message) { + case next { + Continue(state, _) -> Continue(state, Some(selector)) + stop -> stop + } +} + +pub fn stop() -> Next(user_state, user_message) { + NormalStop +} + +pub fn stop_abnormal(reason: String) -> Next(user_state, user_message) { + AbnormalStop(reason) +} + +pub type Loop(state, user_message) = + fn(state, LoopMessage(user_message), Connection(user_message)) -> + Next(state, LoopMessage(user_message)) + +pub type Handler(state, user_message) { + Handler( + socket: Socket, + loop: Loop(state, user_message), + on_init: fn(Connection(user_message)) -> + #(state, Option(Selector(user_message))), + on_close: Option(fn(state) -> Nil), + transport: Transport, + active_state: ActiveState, + ) +} + +/// Starts an actor for the TCP connection +pub fn start( + handler: Handler(state, user_message), +) -> Result(actor.Started(Subject(Message(user_message))), actor.StartError) { + actor.new_with_initialiser(1000, fn(subject) { + let client_ip = + transport.peername(handler.transport, handler.socket) + |> result.replace_error(Nil) + let connection = + Connection( + socket: handler.socket, + client_ip: client_ip, + transport: handler.transport, + sender: subject, + ) + let #(initial_state, user_selector) = handler.on_init(connection) + let base_selector = process.new_selector() |> process.select(subject) + let selector = + process.new_selector() + |> process.select_record(atom.create("tcp"), 2, fn(record) { + ReceiveMessage(socket_data(record)) + }) + |> process.select_record(atom.create("ssl"), 2, fn(record) { + ReceiveMessage(socket_data(record)) + }) + |> process.select_record(atom.create("ssl_closed"), 1, fn(_nil) { Closed }) + |> process.select_record(atom.create("tcp_closed"), 1, fn(_nil) { Closed }) + |> process.select_record(atom.create("ssl_passive"), 1, fn(_nil) { + Passive + }) + |> process.select_record(atom.create("tcp_passive"), 1, fn(_nil) { + Passive + }) + |> process.select_record(atom.create("tcp_error"), 2, fn(record) { + SocketError(socket_error(record)) + }) + |> process.select_record(atom.create("ssl_error"), 2, fn(record) { + SocketError(socket_error(record)) + }) + |> process.map_selector(Internal) + |> process.merge_selector(base_selector) + let selector = case user_selector { + Some(sel) -> + sel + |> process.map_selector(User) + |> process.merge_selector(selector, _) + _ -> selector + } + LoopState( + client_ip: client_ip, + socket: handler.socket, + sender: subject, + transport: handler.transport, + state: initial_state, + ) + |> actor.initialised() + |> actor.selecting(selector) + |> actor.returning(subject) + |> Ok + }) + |> actor.on_message(fn(state, msg) { + let connection = + Connection( + socket: state.socket, + client_ip: state.client_ip, + transport: state.transport, + sender: state.sender, + ) + case msg { + Internal(Closed) | Internal(Close) -> + case transport.close(state.transport, state.socket) { + Ok(Nil) -> { + let _ = case handler.on_close { + Some(on_close) -> on_close(state.state) + _ -> Nil + } + actor.stop() + } + Error(err) -> actor.stop_abnormal(socket.reason_to_string(err)) + } + Internal(Ready) -> + case transport.handshake(state.transport, state.socket) { + Error(_) -> actor.stop_abnormal("Failed to handshake socket") + Ok(_socket) -> { + case transport.set_buffer_size(state.transport, state.socket) { + Ok(_) -> Nil + Error(_nil) -> { + logging.log(logging.Warning, "Failed to read `recbuf` size") + } + } + // Note that the active_state must set to Passive at start of + // Listener/Accept and not changed until the Ready message is + // received. + let options = [options.ActiveMode(handler.active_state)] + case transport.set_opts(state.transport, state.socket, options) { + Ok(_) -> actor.continue(state) + Error(_) -> actor.stop_abnormal("Failed to set socket active") + } + } + } + User(msg) -> { + let msg = Custom(msg) + let res = rescue(fn() { handler.loop(state.state, msg, connection) }) + case res { + Ok(Continue(next_state, _selector)) + if handler.active_state == options.Once + -> { + case + transport.set_opts(state.transport, state.socket, [ + options.ActiveMode(options.Once), + ]) + { + Ok(Nil) -> actor.continue(LoopState(..state, state: next_state)) + Error(_) -> actor.stop() + } + } + Ok(Continue(next_state, _selector)) -> + actor.continue(LoopState(..state, state: next_state)) + Ok(NormalStop) -> actor.stop() + Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason) + Error(reason) -> { + logging.log( + logging.Error, + "Caught error in user handler: " <> string.inspect(reason), + ) + actor.continue(state) + } + } + } + Internal(ReceiveMessage(msg)) -> { + let msg = Packet(msg) + let res = rescue(fn() { handler.loop(state.state, msg, connection) }) + case res { + Ok(Continue(next_state, _selector)) + if handler.active_state == options.Once + -> { + case + transport.set_opts(state.transport, state.socket, [ + options.ActiveMode(options.Once), + ]) + { + Ok(Nil) -> actor.continue(LoopState(..state, state: next_state)) + Error(_) -> actor.stop() + } + } + Ok(Continue(next_state, _selector)) -> + actor.continue(LoopState(..state, state: next_state)) + Ok(NormalStop) -> actor.stop() + Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason) + Error(reason) -> { + logging.log( + logging.Error, + "Caught error in user handler: " <> string.inspect(reason), + ) + actor.continue(state) + } + } + } + Internal(Passive) -> { + let options = [ + options.ActiveMode(handler.active_state), + ] + case transport.set_opts(state.transport, state.socket, options) { + Ok(_) -> actor.continue(state) + Error(_) -> actor.stop_abnormal("Failed to set socket active") + } + } + Internal(SocketError(reason)) -> + actor.stop_abnormal( + "Received socket error " <> socket.reason_to_string(reason), + ) + } + }) + |> actor.start() +} + +@external(erlang, "glisten_ffi", "socket_data") +fn socket_data(record: Dynamic) -> BitArray + +@external(erlang, "glisten_ffi", "socket_data") +fn socket_error(record: Dynamic) -> SocketReason diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/internal/listener.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/internal/listener.gleam new file mode 100644 index 0000000..fa4767d --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/internal/listener.gleam @@ -0,0 +1,57 @@ +import gleam/erlang/process.{type Subject} +import gleam/otp/actor +import gleam/result +import glisten/socket.{type ListenSocket} +import glisten/socket/options.{type IpAddress, type TcpOption} +import glisten/transport.{type Transport} +import logging + +pub type Message { + Info(caller: Subject(State)) +} + +pub type State { + State(listen_socket: ListenSocket, port: Int, ip_address: IpAddress) +} + +pub fn start( + port: Int, + transport: Transport, + options: List(TcpOption), + name: process.Name(Message), +) -> Result(actor.Started(Subject(Message)), actor.StartError) { + actor.new_with_initialiser(5000, fn(subject) { + transport.listen(transport, port, options) + |> result.try(fn(socket) { + transport.sockname(transport, socket) + |> result.map(fn(info) { + State(listen_socket: socket, ip_address: info.0, port: info.1) + }) + }) + |> result.map(fn(state) { + state + |> actor.initialised + |> actor.selecting( + process.new_selector() + |> process.select(subject), + ) + |> actor.returning(subject) + }) + |> result.map_error(fn(err) { + let error_string = + "Failed to start socket listener: " <> socket.reason_to_string(err) + logging.log(logging.Error, error_string) + error_string + }) + }) + |> actor.on_message(fn(state, msg) { + case msg { + Info(caller) -> { + process.send(caller, state) + actor.continue(state) + } + } + }) + |> actor.named(name) + |> actor.start +} diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/socket.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/socket.gleam new file mode 100644 index 0000000..ab3a5e7 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/socket.gleam @@ -0,0 +1,173 @@ +pub type SocketReason { + Closed + Timeout + Badarg + Terminated + + // inet:posix() errors. + Eaddrinuse + Eaddrnotavail + Eafnosupport + Ealready + Econnaborted + Econnrefused + Econnreset + Edestaddrreq + Ehostdown + Ehostunreach + Einprogress + Eisconn + Emsgsize + Enetdown + Enetunreach + Enopkg + Enoprotoopt + Enotconn + Enotty + Enotsock + Eproto + Eprotonosupport + Eprototype + Esocktnosupport + Etimedout + Ewouldblock + Exbadport + Exbadseq + + // file:posix() errors. + Eacces + Eagain + Ebadf + Ebadmsg + Ebusy + Edeadlk + Edeadlock + Edquot + Eexist + Efault + Efbig + Eftype + Eintr + Einval + Eio + Eisdir + Eloop + Emfile + Emlink + Emultihop + Enametoolong + Enfile + Enobufs + Enodev + Enolck + Enolink + Enoent + Enomem + Enospc + Enosr + Enostr + Enosys + Enotblk + Enotdir + Enotsup + Enxio + Eopnotsupp + Eoverflow + Eperm + Epipe + Erange + Erofs + Espipe + Esrch + Estale + Etxtbsy + Exdev +} + +pub fn reason_to_string(reason: SocketReason) -> String { + case reason { + Closed -> "Closed" + Timeout -> "Timeout" + Badarg -> "Badarg" + Terminated -> "Terminated" + Eaddrinuse -> "Eaddrinuse" + Eaddrnotavail -> "Eaddrnotavail" + Eafnosupport -> "Eafnosupport" + Ealready -> "Ealready" + Econnaborted -> "Econnaborted" + Econnrefused -> "Econnrefused" + Econnreset -> "Econnreset" + Edestaddrreq -> "Edestaddrreq" + Ehostdown -> "Ehostdown" + Ehostunreach -> "Ehostunreach" + Einprogress -> "Einprogress" + Eisconn -> "Eisconn" + Emsgsize -> "Emsgsize" + Enetdown -> "Enetdown" + Enetunreach -> "Enetunreach" + Enopkg -> "Enopkg" + Enoprotoopt -> "Enoprotoopt" + Enotconn -> "Enotconn" + Enotty -> "Enotty" + Enotsock -> "Enotsock" + Eproto -> "Eproto" + Eprotonosupport -> "Eprotonosupport" + Eprototype -> "Eprototype" + Esocktnosupport -> "Esocktnosupport" + Etimedout -> "Etimedout" + Ewouldblock -> "Ewouldblock" + Exbadport -> "Exbadport" + Exbadseq -> "Exbadseq" + Eacces -> "Eacces" + Eagain -> "Eagain" + Ebadf -> "Ebadf" + Ebadmsg -> "Ebadmsg" + Ebusy -> "Ebusy" + Edeadlk -> "Edeadlk" + Edeadlock -> "Edeadlock" + Edquot -> "Edquot" + Eexist -> "Eexist" + Efault -> "Efault" + Efbig -> "Efbig" + Eftype -> "Eftype" + Eintr -> "Eintr" + Einval -> "Einval" + Eio -> "Eio" + Eisdir -> "Eisdir" + Eloop -> "Eloop" + Emfile -> "Emfile" + Emlink -> "Emlink" + Emultihop -> "Emultihop" + Enametoolong -> "Enametoolong" + Enfile -> "Enfile" + Enobufs -> "Enobufs" + Enodev -> "Enodev" + Enolck -> "Enolck" + Enolink -> "Enolink" + Enoent -> "Enoent" + Enomem -> "Enomem" + Enospc -> "Enospc" + Enosr -> "Enosr" + Enostr -> "Enostr" + Enosys -> "Enosys" + Enotblk -> "Enotblk" + Enotdir -> "Enotdir" + Enotsup -> "Enotsup" + Enxio -> "Enxio" + Eopnotsupp -> "Eopnotsupp" + Eoverflow -> "Eoverflow" + Eperm -> "Eperm" + Epipe -> "Epipe" + Erange -> "Erange" + Erofs -> "Erofs" + Espipe -> "Espipe" + Esrch -> "Esrch" + Estale -> "Estale" + Etxtbsy -> "Etxtbsy" + Exdev -> "Exdev" + } +} + +pub type ListenSocket + +pub type Socket diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/socket/options.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/socket/options.gleam new file mode 100644 index 0000000..a27a406 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/socket/options.gleam @@ -0,0 +1,78 @@ +/// Mode for the socket. Currently `list` is not supported +pub type SocketMode { + Binary +} + +/// Mapping to the `{active, _}` option +pub type ActiveState { + /// The server acknowledges every package. + Once + /// Not used by server - for use with low level `tcp.receive`. + Passive + /// The server will receive `n` packages before activating again. + Count(Int) + /// Connection is always active, no flow control. + Active +} + +pub type Interface { + Address(IpAddress) + Any + Loopback +} + +pub type TlsCerts { + CertKeyFiles(certfile: String, keyfile: String) +} + +/// Options for the TCP socket +pub type TcpOption { + /// Default 1024 + Backlog(Int) + /// Default True + Nodelay(Bool) + Linger(#(Bool, Int)) + /// Default 30_000 + SendTimeout(Int) + /// Default True + SendTimeoutClose(Bool) + /// Default True + Reuseaddr(Bool) + /// Default Passive for low level and Once for server. + ActiveMode(ActiveState) + /// Default Binary + Mode(SocketMode) + // TODO: Probably should adjust the type here to only allow this for TLS + CertKeyConfig(TlsCerts) + AlpnPreferredProtocols(List(String)) + Ipv6 + Buffer(Int) + Ip(Interface) +} + +pub type ErlangTcpOption + +@external(erlang, "glisten_ffi", "to_erl_tcp_options") +pub fn to_erl_options(options: List(TcpOption)) -> List(ErlangTcpOption) + +pub const default_options = [ + Backlog(1024), + Nodelay(True), + SendTimeout(30_000), + SendTimeoutClose(True), + Reuseaddr(True), + Mode(Binary), + ActiveMode(Passive), +] + +@external(erlang, "glisten_ffi", "merge_type_list") +pub fn merge_type_list(original: List(a), override: List(a)) -> List(a) + +pub fn merge_with_defaults(options: List(TcpOption)) -> List(TcpOption) { + merge_type_list(default_options, options) +} + +pub type IpAddress { + IpV4(Int, Int, Int, Int) + IpV6(Int, Int, Int, Int, Int, Int, Int, Int) +} diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/ssl.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/ssl.gleam new file mode 100644 index 0000000..e284284 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/ssl.gleam @@ -0,0 +1,92 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} +import glisten/socket.{type ListenSocket, type Socket, type SocketReason} +import glisten/socket/options + +@external(erlang, "glisten_ssl_ffi", "controlling_process") +pub fn controlling_process(socket: Socket, pid: Pid) -> Result(Nil, Atom) + +@external(erlang, "ssl", "listen") +fn do_listen( + port: Int, + options: List(options.ErlangTcpOption), +) -> Result(ListenSocket, SocketReason) + +@external(erlang, "ssl", "transport_accept") +pub fn accept_timeout( + socket: ListenSocket, + timeout: Int, +) -> Result(Socket, SocketReason) + +@external(erlang, "ssl", "transport_accept") +pub fn accept(socket: ListenSocket) -> Result(Socket, SocketReason) + +@external(erlang, "ssl", "recv") +pub fn receive_timeout( + socket: Socket, + length: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) + +@external(erlang, "ssl", "recv") +pub fn receive(socket: Socket, length: Int) -> Result(BitArray, SocketReason) + +@external(erlang, "glisten_ssl_ffi", "send") +pub fn send(socket: Socket, packet: BytesTree) -> Result(Nil, SocketReason) + +@external(erlang, "glisten_ssl_ffi", "close") +pub fn close(socket: Socket) -> Result(Nil, SocketReason) + +@external(erlang, "glisten_ssl_ffi", "shutdown") +pub fn do_shutdown(socket: Socket, write: Atom) -> Result(Nil, SocketReason) + +pub fn shutdown(socket: Socket) -> Result(Nil, SocketReason) { + do_shutdown(socket, atom.create("write")) +} + +@external(erlang, "glisten_ssl_ffi", "set_opts") +fn do_set_opts( + socket: Socket, + opts: List(options.ErlangTcpOption), +) -> Result(Nil, SocketReason) + +/// Update the optons for a socket (mutates the socket) +pub fn set_opts( + socket: Socket, + opts: List(options.TcpOption), +) -> Result(Nil, SocketReason) { + opts + |> options.to_erl_options() + |> do_set_opts(socket, _) +} + +@external(erlang, "ssl", "handshake") +pub fn handshake(socket: Socket) -> Result(Socket, Nil) + +/// Start listening over TLS on a port with the given options +pub fn listen( + port: Int, + options: List(options.TcpOption), +) -> Result(ListenSocket, SocketReason) { + options + |> options.merge_with_defaults + |> options.to_erl_options + |> do_listen(port, _) +} + +@external(erlang, "glisten_ssl_ffi", "negotiated_protocol") +pub fn negotiated_protocol(socket: Socket) -> Result(String, String) + +@external(erlang, "ssl", "peername") +pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), SocketReason) + +@external(erlang, "ssl", "sockname") +pub fn sockname(socket: ListenSocket) -> Result(#(Dynamic, Int), SocketReason) + +@external(erlang, "ssl", "getopts") +pub fn get_socket_opts( + socket: Socket, + opts: List(Atom), +) -> Result(List(#(Atom, Dynamic)), SocketReason) diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/tcp.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/tcp.gleam new file mode 100644 index 0000000..338d1dc --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/tcp.gleam @@ -0,0 +1,97 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/dict.{type Dict} +import gleam/dynamic.{type Dynamic} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} +import glisten/socket.{type ListenSocket, type Socket, type SocketReason} +import glisten/socket/options.{type TcpOption} + +@external(erlang, "glisten_tcp_ffi", "controlling_process") +pub fn controlling_process(socket: Socket, pid: Pid) -> Result(Nil, Atom) + +@external(erlang, "gen_tcp", "listen") +fn do_listen_tcp( + port: Int, + options: List(options.ErlangTcpOption), +) -> Result(ListenSocket, SocketReason) + +@external(erlang, "gen_tcp", "accept") +pub fn accept_timeout( + socket: ListenSocket, + timeout: Int, +) -> Result(Socket, SocketReason) + +@external(erlang, "gen_tcp", "accept") +pub fn accept(socket: ListenSocket) -> Result(Socket, SocketReason) + +@external(erlang, "gen_tcp", "recv") +pub fn receive_timeout( + socket: Socket, + length: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) + +@external(erlang, "gen_tcp", "recv") +pub fn receive(socket: Socket, length: Int) -> Result(BitArray, SocketReason) + +@external(erlang, "glisten_tcp_ffi", "send") +pub fn send(socket: Socket, packet: BytesTree) -> Result(Nil, SocketReason) + +@external(erlang, "socket", "info") +pub fn socket_info(socket: Socket) -> Dict(a, b) + +@external(erlang, "glisten_tcp_ffi", "close") +pub fn close(socket: a) -> Result(Nil, SocketReason) + +@external(erlang, "glisten_tcp_ffi", "shutdown") +pub fn do_shutdown(socket: Socket, write: Atom) -> Result(Nil, SocketReason) + +pub fn shutdown(socket: Socket) -> Result(Nil, SocketReason) { + do_shutdown(socket, atom.create("write")) +} + +@external(erlang, "glisten_tcp_ffi", "set_opts") +fn do_set_opts( + socket: Socket, + opts: List(options.ErlangTcpOption), +) -> Result(Nil, SocketReason) + +/// Update the optons for a socket (mutates the socket) +pub fn set_opts( + socket: Socket, + opts: List(TcpOption), +) -> Result(Nil, SocketReason) { + opts + |> options.to_erl_options() + |> do_set_opts(socket, _) +} + +/// Start listening over TCP on a port with the given options +pub fn listen( + port: Int, + opts: List(TcpOption), +) -> Result(ListenSocket, SocketReason) { + opts + |> options.merge_with_defaults + |> options.to_erl_options + |> do_listen_tcp(port, _) +} + +pub fn handshake(socket: Socket) -> Result(Socket, Nil) { + Ok(socket) +} + +@external(erlang, "tcp", "negotiated_protocol") +pub fn negotiated_protocol(socket: Socket) -> a + +@external(erlang, "inet", "peername") +pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), SocketReason) + +@external(erlang, "inet", "getopts") +pub fn get_socket_opts( + socket: Socket, + opts: List(Atom), +) -> Result(List(#(Atom, Dynamic)), SocketReason) + +@external(erlang, "inet", "sockname") +pub fn sockname(socket: ListenSocket) -> Result(#(Dynamic, Int), SocketReason) diff --git a/examples/http_interactions/build/packages/glisten/src/glisten/transport.gleam b/examples/http_interactions/build/packages/glisten/src/glisten/transport.gleam new file mode 100644 index 0000000..c742a2b --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten/transport.gleam @@ -0,0 +1,239 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/dict.{type Dict} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} +import gleam/result +import glisten/socket.{type ListenSocket, type Socket, type SocketReason} +import glisten/socket/options +import glisten/ssl +import glisten/tcp + +pub type Transport { + Tcp + Ssl +} + +pub fn controlling_process( + transport: Transport, + socket: Socket, + pid: Pid, +) -> Result(Nil, Atom) { + case transport { + Tcp -> tcp.controlling_process(socket, pid) + Ssl -> ssl.controlling_process(socket, pid) + } +} + +pub fn listen( + transport: Transport, + port: Int, + opts: List(options.TcpOption), +) -> Result(ListenSocket, SocketReason) { + case transport { + Tcp -> tcp.listen(port, opts) + Ssl -> ssl.listen(port, opts) + } +} + +pub fn accept_timeout( + transport: Transport, + socket: ListenSocket, + timeout: Int, +) -> Result(Socket, SocketReason) { + case transport { + Tcp -> tcp.accept_timeout(socket, timeout) + Ssl -> ssl.accept_timeout(socket, timeout) + } +} + +pub fn accept( + transport: Transport, + socket: ListenSocket, +) -> Result(Socket, SocketReason) { + case transport { + Tcp -> tcp.accept(socket) + Ssl -> ssl.accept(socket) + } +} + +pub fn handshake(transport: Transport, socket: Socket) -> Result(Socket, Nil) { + case transport { + Tcp -> tcp.handshake(socket) + Ssl -> ssl.handshake(socket) + } +} + +pub fn receive_timeout( + transport: Transport, + socket: Socket, + amount: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) { + case transport { + Tcp -> tcp.receive_timeout(socket, amount, timeout) + Ssl -> ssl.receive_timeout(socket, amount, timeout) + } +} + +pub fn receive( + transport: Transport, + socket: Socket, + amount: Int, +) -> Result(BitArray, SocketReason) { + case transport { + Tcp -> tcp.receive(socket, amount) + Ssl -> ssl.receive(socket, amount) + } +} + +pub fn send( + transport: Transport, + socket: Socket, + data: BytesTree, +) -> Result(Nil, SocketReason) { + case transport { + Tcp -> tcp.send(socket, data) + Ssl -> ssl.send(socket, data) + } +} + +pub fn close(transport: Transport, socket: Socket) -> Result(Nil, SocketReason) { + case transport { + Tcp -> tcp.close(socket) + Ssl -> ssl.close(socket) + } +} + +pub fn shutdown( + transport: Transport, + socket: Socket, +) -> Result(Nil, SocketReason) { + case transport { + Tcp -> tcp.shutdown(socket) + Ssl -> ssl.shutdown(socket) + } +} + +pub fn set_opts( + transport: Transport, + socket: Socket, + opts: List(options.TcpOption), +) -> Result(Nil, SocketReason) { + case transport { + Tcp -> tcp.set_opts(socket, opts) + Ssl -> ssl.set_opts(socket, opts) + } +} + +pub fn negotiated_protocol( + transport: Transport, + socket: Socket, +) -> Result(String, String) { + case transport { + Tcp -> Error("Can't negotiate protocol on tcp") + Ssl -> ssl.negotiated_protocol(socket) + } +} + +fn decode_ipv4() -> Decoder(options.IpAddress) { + use a <- decode.field(0, decode.int) + use b <- decode.field(1, decode.int) + use c <- decode.field(2, decode.int) + use d <- decode.field(3, decode.int) + decode.success(options.IpV4(a, b, c, d)) +} + +fn decode_ipv6() -> Decoder(options.IpAddress) { + use a <- decode.field(0, decode.int) + use b <- decode.field(1, decode.int) + use c <- decode.field(2, decode.int) + use d <- decode.field(3, decode.int) + use e <- decode.field(4, decode.int) + use f <- decode.field(5, decode.int) + use g <- decode.field(6, decode.int) + use h <- decode.field(7, decode.int) + case a, b, c, d, e, f, g, h { + 0, 0, 0, 0, 0, 65_535, a, b -> { + let #(a, b, c, d) = convert_address(#(a, b, c, d, e, f, g, h)) + decode.success(options.IpV4(a, b, c, d)) + } + _, _, _, _, _, _, _, _ -> { + decode.success(options.IpV6(a, b, c, d, e, f, g, h)) + } + } +} + +pub fn decode_ip() -> Decoder(options.IpAddress) { + decode.one_of(decode_ipv6(), or: [decode_ipv4()]) +} + +pub fn peername( + transport: Transport, + socket: Socket, +) -> Result(#(options.IpAddress, Int), Nil) { + case transport { + Tcp -> tcp.peername(socket) + Ssl -> ssl.peername(socket) + } + |> result.replace_error(Nil) + |> result.try(fn(pair) { + let #(ip_address, port) = pair + decode.run(ip_address, decode_ip()) + |> result.map(fn(ip) { #(ip, port) }) + |> result.replace_error(Nil) + }) +} + +@external(erlang, "inet", "ipv4_mapped_ipv6_address") +fn convert_address(address: address) -> #(Int, Int, Int, Int) + +@external(erlang, "socket", "info") +pub fn socket_info(socket: Socket) -> Dict(Atom, Dynamic) + +pub fn get_socket_opts( + transport: Transport, + socket: Socket, + opts: List(Atom), +) -> Result(List(#(Atom, Dynamic)), Nil) { + case transport { + Tcp -> tcp.get_socket_opts(socket, opts) + Ssl -> ssl.get_socket_opts(socket, opts) + } + |> result.replace_error(Nil) +} + +pub fn set_buffer_size(transport: Transport, socket: Socket) -> Result(Nil, Nil) { + get_socket_opts(transport, socket, [atom.create("recbuf")]) + |> result.try(fn(p) { + case p { + [#(_buffer, value)] -> + value + |> decode.run(decode.int) + |> result.replace_error(Nil) + _ -> Error(Nil) + } + }) + |> result.try(fn(value) { + set_opts(transport, socket, [options.Buffer(value)]) + |> result.map_error(fn(_) { Nil }) + }) +} + +pub fn sockname( + transport: Transport, + socket: ListenSocket, +) -> Result(#(options.IpAddress, Int), SocketReason) { + case transport { + Tcp -> tcp.sockname(socket) + Ssl -> ssl.sockname(socket) + } + |> result.try(fn(pair) { + let #(maybe_ip, port) = pair + maybe_ip + |> decode.run(decode_ip()) + |> result.map(fn(ip) { #(ip, port) }) + |> result.replace_error(socket.Badarg) + }) +} diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@internal@acceptor.erl b/examples/http_interactions/build/packages/glisten/src/glisten@internal@acceptor.erl new file mode 100644 index 0000000..62b8d0e --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@internal@acceptor.erl @@ -0,0 +1,253 @@ +-module(glisten@internal@acceptor). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/internal/acceptor.gleam"). +-export([start/3, start_pool/5]). +-export_type([acceptor_message/0, acceptor_error/0, acceptor_state/0, pool/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type acceptor_message() :: {accept_connection, glisten@socket:listen_socket()}. + +-type acceptor_error() :: {accept_error, glisten@socket:socket_reason()} | + {handler_error, gleam@otp@actor:start_error()} | + {control_error, gleam@erlang@atom:atom_()}. + +-type acceptor_state() :: {acceptor_state, + gleam@erlang@process:subject(acceptor_message()), + gleam@option:option(glisten@socket:socket()), + glisten@transport:transport()}. + +-type pool(GIL, GIM) :: {pool, + fun((GIL, glisten@internal@handler:loop_message(GIM), glisten@internal@handler:connection(GIM)) -> glisten@internal@handler:next(GIL, glisten@internal@handler:loop_message(GIM))), + integer(), + gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GIM)))), + fun((glisten@internal@handler:connection(GIM)) -> {GIL, + gleam@option:option(gleam@erlang@process:selector(GIM))}), + gleam@option:option(fun((GIL) -> nil)), + glisten@transport:transport(), + glisten@socket@options:active_state()}. + +-file("src/glisten/internal/acceptor.gleam", 39). +?DOC(false). +-spec start( + pool(any(), GIO), + gleam@erlang@process:name(glisten@internal@listener:message()), + gleam@erlang@process:name(gleam@otp@factory_supervisor:message(glisten@socket:socket(), gleam@erlang@process:subject(glisten@internal@handler:message(GIO)))) +) -> {ok, + gleam@otp@actor:started(gleam@erlang@process:subject(acceptor_message()))} | + {error, gleam@otp@actor:start_error()}. +start(Pool, Listener_name, Connection_supervisor) -> + _pipe@5 = gleam@otp@actor:new_with_initialiser( + 1000, + fun(Subject) -> + Listener = gleam@erlang@process:named_subject(Listener_name), + State = gleam@erlang@process:call( + Listener, + 750, + fun(Field@0) -> {info, Field@0} end + ), + gleam@erlang@process:send( + Subject, + {accept_connection, erlang:element(2, State)} + ), + _pipe = {acceptor_state, Subject, none, erlang:element(7, Pool)}, + _pipe@1 = gleam@otp@actor:initialised(_pipe), + _pipe@2 = gleam@otp@actor:returning(_pipe@1, Subject), + _pipe@4 = gleam@otp@actor:selecting( + _pipe@2, + begin + _pipe@3 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@3, Subject) + end + ), + {ok, _pipe@4} + end + ), + _pipe@9 = gleam@otp@actor:on_message( + _pipe@5, + fun(State@1, Msg) -> + {acceptor_state, Sender, _, _} = State@1, + case Msg of + {accept_connection, Listener@1} -> + Res = begin + gleam@result:'try'( + begin + _pipe@6 = glisten@transport:accept( + erlang:element(4, State@1), + Listener@1 + ), + gleam@result:map_error( + _pipe@6, + fun(Field@0) -> {accept_error, Field@0} end + ) + end, + fun(Sock) -> + Connection_factory = gleam@otp@factory_supervisor:get_by_name( + Connection_supervisor + ), + case gleam@otp@factory_supervisor:start_child( + Connection_factory, + Sock + ) of + {ok, Start} -> + _pipe@7 = glisten@transport:controlling_process( + erlang:element(4, State@1), + Sock, + erlang:element(2, Start) + ), + _pipe@8 = gleam@result:map_error( + _pipe@7, + fun(Field@0) -> {control_error, Field@0} end + ), + gleam@result:map( + _pipe@8, + fun(_) -> + gleam@erlang@process:send( + erlang:element(3, Start), + {internal, ready} + ) + end + ); + + {error, Reason} -> + {error, {handler_error, Reason}} + end + end + ) + end, + case Res of + {error, Reason@1} -> + Msg@1 = case Reason@1 of + {accept_error, Reason@2} -> + <<"acceptor failed: "/utf8, + (glisten@socket:reason_to_string( + Reason@2 + ))/binary>>; + + {handler_error, init_timeout} -> + <<"init timed out"/utf8>>; + + {handler_error, {init_failed, Reason@3}} -> + <<"init failed: "/utf8, Reason@3/binary>>; + + {handler_error, {init_exited, normal}} -> + <<"init exited normally"/utf8>>; + + {handler_error, {init_exited, killed}} -> + <<"init killed"/utf8>>; + + {handler_error, {init_exited, {abnormal, _}}} -> + <<"init exited abnormally"/utf8>>; + + {control_error, Reason@4} -> + <<"could not control socket: "/utf8, + (erlang:atom_to_binary(Reason@4))/binary>> + end, + logging:log( + error, + <<"Failed to accept/start handler: "/utf8, + Msg@1/binary>> + ), + gleam@otp@actor:stop_abnormal( + <<"Failed to accept/start handler"/utf8>> + ); + + _ -> + gleam@otp@actor:send( + Sender, + {accept_connection, Listener@1} + ), + gleam@otp@actor:continue(State@1) + end + end + end + ), + gleam@otp@actor:start(_pipe@9). + +-file("src/glisten/internal/acceptor.gleam", 133). +?DOC(false). +-spec start_pool( + pool(any(), any()), + glisten@transport:transport(), + integer(), + list(glisten@socket@options:tcp_option()), + gleam@erlang@process:name(glisten@internal@listener:message()) +) -> {ok, gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} | + {error, gleam@otp@actor:start_error()}. +start_pool(Pool, Transport, Port, Options, Listener_name) -> + _pipe = gleam@otp@static_supervisor:new(one_for_one), + _pipe@1 = gleam@otp@static_supervisor:add( + _pipe, + gleam@otp@supervision:worker( + fun() -> + glisten@internal@listener:start( + Port, + Transport, + Options, + Listener_name + ) + end + ) + ), + _pipe@4 = gleam@otp@static_supervisor:add( + _pipe@1, + gleam@otp@supervision:supervisor( + fun() -> _pipe@2 = gleam@otp@static_supervisor:new(one_for_one), + _pipe@3 = gleam@int:range( + 0, + erlang:element(3, Pool), + _pipe@2, + fun(Sup, _) -> + gleam@otp@static_supervisor:add( + Sup, + gleam@otp@supervision:worker( + fun() -> + start( + Pool, + Listener_name, + erlang:element(4, Pool) + ) + end + ) + ) + end + ), + gleam@otp@static_supervisor:start(_pipe@3) end + ) + ), + _pipe@8 = gleam@otp@static_supervisor:add( + _pipe@4, + begin + _pipe@5 = gleam@otp@factory_supervisor:worker_child( + fun(Socket) -> + glisten@internal@handler:start( + {handler, + Socket, + erlang:element(2, Pool), + erlang:element(5, Pool), + erlang:element(6, Pool), + erlang:element(7, Pool), + erlang:element(8, Pool)} + ) + end + ), + _pipe@6 = gleam@otp@factory_supervisor:named( + _pipe@5, + erlang:element(4, Pool) + ), + _pipe@7 = gleam@otp@factory_supervisor:restart_strategy( + _pipe@6, + temporary + ), + gleam@otp@factory_supervisor:supervised(_pipe@7) + end + ), + gleam@otp@static_supervisor:start(_pipe@8). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@internal@handler.erl b/examples/http_interactions/build/packages/glisten/src/glisten@internal@handler.erl new file mode 100644 index 0000000..dceae98 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@internal@handler.erl @@ -0,0 +1,437 @@ +-module(glisten@internal@handler). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/internal/handler.gleam"). +-export([continue/1, with_selector/2, stop/0, stop_abnormal/1, start/1]). +-export_type([internal_message/0, message/1, loop_message/1, loop_state/2, connection/1, next/2, handler/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type internal_message() :: close | + ready | + {receive_message, bitstring()} | + closed | + passive | + {socket_error, glisten@socket:socket_reason()}. + +-type message(FRA) :: {internal, internal_message()} | {user, FRA}. + +-type loop_message(FRB) :: {packet, bitstring()} | {custom, FRB}. + +-type loop_state(FRC, FRD) :: {loop_state, + {ok, {glisten@socket@options:ip_address(), integer()}} | {error, nil}, + glisten@socket:socket(), + gleam@erlang@process:subject(message(FRD)), + glisten@transport:transport(), + FRC}. + +-type connection(FRE) :: {connection, + {ok, {glisten@socket@options:ip_address(), integer()}} | {error, nil}, + glisten@socket:socket(), + glisten@transport:transport(), + gleam@erlang@process:subject(message(FRE))}. + +-type next(FRF, FRG) :: {continue, + FRF, + gleam@option:option(gleam@erlang@process:selector(FRG))} | + normal_stop | + {abnormal_stop, binary()}. + +-type handler(FRH, FRI) :: {handler, + glisten@socket:socket(), + fun((FRH, loop_message(FRI), connection(FRI)) -> next(FRH, loop_message(FRI))), + fun((connection(FRI)) -> {FRH, + gleam@option:option(gleam@erlang@process:selector(FRI))}), + gleam@option:option(fun((FRH) -> nil)), + glisten@transport:transport(), + glisten@socket@options:active_state()}. + +-file("src/glisten/internal/handler.gleam", 65). +?DOC(false). +-spec continue(FRV) -> next(FRV, any()). +continue(State) -> + {continue, State, none}. + +-file("src/glisten/internal/handler.gleam", 69). +?DOC(false). +-spec with_selector(next(FRZ, FSA), gleam@erlang@process:selector(FSA)) -> next(FRZ, FSA). +with_selector(Next, Selector) -> + case Next of + {continue, State, _} -> + {continue, State, {some, Selector}}; + + Stop -> + Stop + end. + +-file("src/glisten/internal/handler.gleam", 79). +?DOC(false). +-spec stop() -> next(any(), any()). +stop() -> + normal_stop. + +-file("src/glisten/internal/handler.gleam", 83). +?DOC(false). +-spec stop_abnormal(binary()) -> next(any(), any()). +stop_abnormal(Reason) -> + {abnormal_stop, Reason}. + +-file("src/glisten/internal/handler.gleam", 104). +?DOC(false). +-spec start(handler(any(), FSP)) -> {ok, + gleam@otp@actor:started(gleam@erlang@process:subject(message(FSP)))} | + {error, gleam@otp@actor:start_error()}. +start(Handler) -> + _pipe@18 = gleam@otp@actor:new_with_initialiser( + 1000, + fun(Subject) -> + Client_ip = begin + _pipe = glisten@transport:peername( + erlang:element(6, Handler), + erlang:element(2, Handler) + ), + gleam@result:replace_error(_pipe, nil) + end, + Connection = {connection, + Client_ip, + erlang:element(2, Handler), + erlang:element(6, Handler), + Subject}, + {Initial_state, User_selector} = (erlang:element(4, Handler))( + Connection + ), + Base_selector = begin + _pipe@1 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@1, Subject) + end, + Selector = begin + _pipe@2 = gleam_erlang_ffi:new_selector(), + _pipe@3 = gleam@erlang@process:select_record( + _pipe@2, + erlang:binary_to_atom(<<"tcp"/utf8>>), + 2, + fun(Record) -> + {receive_message, glisten_ffi:socket_data(Record)} + end + ), + _pipe@4 = gleam@erlang@process:select_record( + _pipe@3, + erlang:binary_to_atom(<<"ssl"/utf8>>), + 2, + fun(Record@1) -> + {receive_message, glisten_ffi:socket_data(Record@1)} + end + ), + _pipe@5 = gleam@erlang@process:select_record( + _pipe@4, + erlang:binary_to_atom(<<"ssl_closed"/utf8>>), + 1, + fun(_) -> closed end + ), + _pipe@6 = gleam@erlang@process:select_record( + _pipe@5, + erlang:binary_to_atom(<<"tcp_closed"/utf8>>), + 1, + fun(_) -> closed end + ), + _pipe@7 = gleam@erlang@process:select_record( + _pipe@6, + erlang:binary_to_atom(<<"ssl_passive"/utf8>>), + 1, + fun(_) -> passive end + ), + _pipe@8 = gleam@erlang@process:select_record( + _pipe@7, + erlang:binary_to_atom(<<"tcp_passive"/utf8>>), + 1, + fun(_) -> passive end + ), + _pipe@9 = gleam@erlang@process:select_record( + _pipe@8, + erlang:binary_to_atom(<<"tcp_error"/utf8>>), + 2, + fun(Record@2) -> + {socket_error, glisten_ffi:socket_data(Record@2)} + end + ), + _pipe@10 = gleam@erlang@process:select_record( + _pipe@9, + erlang:binary_to_atom(<<"ssl_error"/utf8>>), + 2, + fun(Record@3) -> + {socket_error, glisten_ffi:socket_data(Record@3)} + end + ), + _pipe@11 = gleam_erlang_ffi:map_selector( + _pipe@10, + fun(Field@0) -> {internal, Field@0} end + ), + gleam_erlang_ffi:merge_selector(_pipe@11, Base_selector) + end, + Selector@1 = case User_selector of + {some, Sel} -> + _pipe@12 = Sel, + _pipe@13 = gleam_erlang_ffi:map_selector( + _pipe@12, + fun(Field@0) -> {user, Field@0} end + ), + gleam_erlang_ffi:merge_selector(Selector, _pipe@13); + + _ -> + Selector + end, + _pipe@14 = {loop_state, + Client_ip, + erlang:element(2, Handler), + Subject, + erlang:element(6, Handler), + Initial_state}, + _pipe@15 = gleam@otp@actor:initialised(_pipe@14), + _pipe@16 = gleam@otp@actor:selecting(_pipe@15, Selector@1), + _pipe@17 = gleam@otp@actor:returning(_pipe@16, Subject), + {ok, _pipe@17} + end + ), + _pipe@19 = gleam@otp@actor:on_message( + _pipe@18, + fun(State, Msg) -> + Connection@1 = {connection, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(5, State), + erlang:element(4, State)}, + case Msg of + {internal, closed} -> + case glisten@transport:close( + erlang:element(5, State), + erlang:element(3, State) + ) of + {ok, nil} -> + _ = case erlang:element(5, Handler) of + {some, On_close} -> + On_close(erlang:element(6, State)); + + _ -> + nil + end, + gleam@otp@actor:stop(); + + {error, Err} -> + gleam@otp@actor:stop_abnormal( + glisten@socket:reason_to_string(Err) + ) + end; + + {internal, close} -> + case glisten@transport:close( + erlang:element(5, State), + erlang:element(3, State) + ) of + {ok, nil} -> + _ = case erlang:element(5, Handler) of + {some, On_close} -> + On_close(erlang:element(6, State)); + + _ -> + nil + end, + gleam@otp@actor:stop(); + + {error, Err} -> + gleam@otp@actor:stop_abnormal( + glisten@socket:reason_to_string(Err) + ) + end; + + {internal, ready} -> + case glisten@transport:handshake( + erlang:element(5, State), + erlang:element(3, State) + ) of + {error, _} -> + gleam@otp@actor:stop_abnormal( + <<"Failed to handshake socket"/utf8>> + ); + + {ok, _} -> + case glisten@transport:set_buffer_size( + erlang:element(5, State), + erlang:element(3, State) + ) of + {ok, _} -> + nil; + + {error, _} -> + logging:log( + warning, + <<"Failed to read `recbuf` size"/utf8>> + ) + end, + Options = [{active_mode, erlang:element(7, Handler)}], + case glisten@transport:set_opts( + erlang:element(5, State), + erlang:element(3, State), + Options + ) of + {ok, _} -> + gleam@otp@actor:continue(State); + + {error, _} -> + gleam@otp@actor:stop_abnormal( + <<"Failed to set socket active"/utf8>> + ) + end + end; + + {user, Msg@1} -> + Msg@2 = {custom, Msg@1}, + Res = glisten_ffi:rescue( + fun() -> + (erlang:element(3, Handler))( + erlang:element(6, State), + Msg@2, + Connection@1 + ) + end + ), + case Res of + {ok, {continue, Next_state, _}} when erlang:element( + 7, + Handler + ) =:= once -> + case glisten@transport:set_opts( + erlang:element(5, State), + erlang:element(3, State), + [{active_mode, once}] + ) of + {ok, nil} -> + gleam@otp@actor:continue( + {loop_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + Next_state} + ); + + {error, _} -> + gleam@otp@actor:stop() + end; + + {ok, {continue, Next_state@1, _}} -> + gleam@otp@actor:continue( + {loop_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + Next_state@1} + ); + + {ok, normal_stop} -> + gleam@otp@actor:stop(); + + {ok, {abnormal_stop, Reason}} -> + gleam@otp@actor:stop_abnormal(Reason); + + {error, Reason@1} -> + logging:log( + error, + <<"Caught error in user handler: "/utf8, + (gleam@string:inspect(Reason@1))/binary>> + ), + gleam@otp@actor:continue(State) + end; + + {internal, {receive_message, Msg@3}} -> + Msg@4 = {packet, Msg@3}, + Res@1 = glisten_ffi:rescue( + fun() -> + (erlang:element(3, Handler))( + erlang:element(6, State), + Msg@4, + Connection@1 + ) + end + ), + case Res@1 of + {ok, {continue, Next_state@2, _}} when erlang:element( + 7, + Handler + ) =:= once -> + case glisten@transport:set_opts( + erlang:element(5, State), + erlang:element(3, State), + [{active_mode, once}] + ) of + {ok, nil} -> + gleam@otp@actor:continue( + {loop_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + Next_state@2} + ); + + {error, _} -> + gleam@otp@actor:stop() + end; + + {ok, {continue, Next_state@3, _}} -> + gleam@otp@actor:continue( + {loop_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + Next_state@3} + ); + + {ok, normal_stop} -> + gleam@otp@actor:stop(); + + {ok, {abnormal_stop, Reason@2}} -> + gleam@otp@actor:stop_abnormal(Reason@2); + + {error, Reason@3} -> + logging:log( + error, + <<"Caught error in user handler: "/utf8, + (gleam@string:inspect(Reason@3))/binary>> + ), + gleam@otp@actor:continue(State) + end; + + {internal, passive} -> + Options@1 = [{active_mode, erlang:element(7, Handler)}], + case glisten@transport:set_opts( + erlang:element(5, State), + erlang:element(3, State), + Options@1 + ) of + {ok, _} -> + gleam@otp@actor:continue(State); + + {error, _} -> + gleam@otp@actor:stop_abnormal( + <<"Failed to set socket active"/utf8>> + ) + end; + + {internal, {socket_error, Reason@4}} -> + gleam@otp@actor:stop_abnormal( + <<"Received socket error "/utf8, + (glisten@socket:reason_to_string(Reason@4))/binary>> + ) + end + end + ), + gleam@otp@actor:start(_pipe@19). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@internal@listener.erl b/examples/http_interactions/build/packages/glisten/src/glisten@internal@listener.erl new file mode 100644 index 0000000..ee722f3 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@internal@listener.erl @@ -0,0 +1,83 @@ +-module(glisten@internal@listener). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/internal/listener.gleam"). +-export([start/4]). +-export_type([message/0, state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type message() :: {info, gleam@erlang@process:subject(state())}. + +-type state() :: {state, + glisten@socket:listen_socket(), + integer(), + glisten@socket@options:ip_address()}. + +-file("src/glisten/internal/listener.gleam", 17). +?DOC(false). +-spec start( + integer(), + glisten@transport:transport(), + list(glisten@socket@options:tcp_option()), + gleam@erlang@process:name(message()) +) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(message()))} | + {error, gleam@otp@actor:start_error()}. +start(Port, Transport, Options, Name) -> + _pipe@8 = gleam@otp@actor:new_with_initialiser( + 5000, + fun(Subject) -> + _pipe = glisten@transport:listen(Transport, Port, Options), + _pipe@2 = gleam@result:'try'( + _pipe, + fun(Socket) -> + _pipe@1 = glisten@transport:sockname(Transport, Socket), + gleam@result:map( + _pipe@1, + fun(Info) -> + {state, + Socket, + erlang:element(2, Info), + erlang:element(1, Info)} + end + ) + end + ), + _pipe@7 = gleam@result:map(_pipe@2, fun(State) -> _pipe@3 = State, + _pipe@4 = gleam@otp@actor:initialised(_pipe@3), + _pipe@6 = gleam@otp@actor:selecting( + _pipe@4, + begin + _pipe@5 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@5, Subject) + end + ), + gleam@otp@actor:returning(_pipe@6, Subject) end), + gleam@result:map_error( + _pipe@7, + fun(Err) -> + Error_string = <<"Failed to start socket listener: "/utf8, + (glisten@socket:reason_to_string(Err))/binary>>, + logging:log(error, Error_string), + Error_string + end + ) + end + ), + _pipe@9 = gleam@otp@actor:on_message( + _pipe@8, + fun(State@1, Msg) -> case Msg of + {info, Caller} -> + gleam@erlang@process:send(Caller, State@1), + gleam@otp@actor:continue(State@1) + end end + ), + _pipe@10 = gleam@otp@actor:named(_pipe@9, Name), + gleam@otp@actor:start(_pipe@10). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@socket.erl b/examples/http_interactions/build/packages/glisten/src/glisten@socket.erl new file mode 100644 index 0000000..9c9d352 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@socket.erl @@ -0,0 +1,331 @@ +-module(glisten@socket). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/socket.gleam"). +-export([reason_to_string/1]). +-export_type([socket_reason/0, listen_socket/0, socket/0]). + +-type socket_reason() :: closed | + timeout | + badarg | + terminated | + eaddrinuse | + eaddrnotavail | + eafnosupport | + ealready | + econnaborted | + econnrefused | + econnreset | + edestaddrreq | + ehostdown | + ehostunreach | + einprogress | + eisconn | + emsgsize | + enetdown | + enetunreach | + enopkg | + enoprotoopt | + enotconn | + enotty | + enotsock | + eproto | + eprotonosupport | + eprototype | + esocktnosupport | + etimedout | + ewouldblock | + exbadport | + exbadseq | + eacces | + eagain | + ebadf | + ebadmsg | + ebusy | + edeadlk | + edeadlock | + edquot | + eexist | + efault | + efbig | + eftype | + eintr | + einval | + eio | + eisdir | + eloop | + emfile | + emlink | + emultihop | + enametoolong | + enfile | + enobufs | + enodev | + enolck | + enolink | + enoent | + enomem | + enospc | + enosr | + enostr | + enosys | + enotblk | + enotdir | + enotsup | + enxio | + eopnotsupp | + eoverflow | + eperm | + epipe | + erange | + erofs | + espipe | + esrch | + estale | + etxtbsy | + exdev. + +-type listen_socket() :: any(). + +-type socket() :: any(). + +-file("src/glisten/socket.gleam", 87). +-spec reason_to_string(socket_reason()) -> binary(). +reason_to_string(Reason) -> + case Reason of + closed -> + <<"Closed"/utf8>>; + + timeout -> + <<"Timeout"/utf8>>; + + badarg -> + <<"Badarg"/utf8>>; + + terminated -> + <<"Terminated"/utf8>>; + + eaddrinuse -> + <<"Eaddrinuse"/utf8>>; + + eaddrnotavail -> + <<"Eaddrnotavail"/utf8>>; + + eafnosupport -> + <<"Eafnosupport"/utf8>>; + + ealready -> + <<"Ealready"/utf8>>; + + econnaborted -> + <<"Econnaborted"/utf8>>; + + econnrefused -> + <<"Econnrefused"/utf8>>; + + econnreset -> + <<"Econnreset"/utf8>>; + + edestaddrreq -> + <<"Edestaddrreq"/utf8>>; + + ehostdown -> + <<"Ehostdown"/utf8>>; + + ehostunreach -> + <<"Ehostunreach"/utf8>>; + + einprogress -> + <<"Einprogress"/utf8>>; + + eisconn -> + <<"Eisconn"/utf8>>; + + emsgsize -> + <<"Emsgsize"/utf8>>; + + enetdown -> + <<"Enetdown"/utf8>>; + + enetunreach -> + <<"Enetunreach"/utf8>>; + + enopkg -> + <<"Enopkg"/utf8>>; + + enoprotoopt -> + <<"Enoprotoopt"/utf8>>; + + enotconn -> + <<"Enotconn"/utf8>>; + + enotty -> + <<"Enotty"/utf8>>; + + enotsock -> + <<"Enotsock"/utf8>>; + + eproto -> + <<"Eproto"/utf8>>; + + eprotonosupport -> + <<"Eprotonosupport"/utf8>>; + + eprototype -> + <<"Eprototype"/utf8>>; + + esocktnosupport -> + <<"Esocktnosupport"/utf8>>; + + etimedout -> + <<"Etimedout"/utf8>>; + + ewouldblock -> + <<"Ewouldblock"/utf8>>; + + exbadport -> + <<"Exbadport"/utf8>>; + + exbadseq -> + <<"Exbadseq"/utf8>>; + + eacces -> + <<"Eacces"/utf8>>; + + eagain -> + <<"Eagain"/utf8>>; + + ebadf -> + <<"Ebadf"/utf8>>; + + ebadmsg -> + <<"Ebadmsg"/utf8>>; + + ebusy -> + <<"Ebusy"/utf8>>; + + edeadlk -> + <<"Edeadlk"/utf8>>; + + edeadlock -> + <<"Edeadlock"/utf8>>; + + edquot -> + <<"Edquot"/utf8>>; + + eexist -> + <<"Eexist"/utf8>>; + + efault -> + <<"Efault"/utf8>>; + + efbig -> + <<"Efbig"/utf8>>; + + eftype -> + <<"Eftype"/utf8>>; + + eintr -> + <<"Eintr"/utf8>>; + + einval -> + <<"Einval"/utf8>>; + + eio -> + <<"Eio"/utf8>>; + + eisdir -> + <<"Eisdir"/utf8>>; + + eloop -> + <<"Eloop"/utf8>>; + + emfile -> + <<"Emfile"/utf8>>; + + emlink -> + <<"Emlink"/utf8>>; + + emultihop -> + <<"Emultihop"/utf8>>; + + enametoolong -> + <<"Enametoolong"/utf8>>; + + enfile -> + <<"Enfile"/utf8>>; + + enobufs -> + <<"Enobufs"/utf8>>; + + enodev -> + <<"Enodev"/utf8>>; + + enolck -> + <<"Enolck"/utf8>>; + + enolink -> + <<"Enolink"/utf8>>; + + enoent -> + <<"Enoent"/utf8>>; + + enomem -> + <<"Enomem"/utf8>>; + + enospc -> + <<"Enospc"/utf8>>; + + enosr -> + <<"Enosr"/utf8>>; + + enostr -> + <<"Enostr"/utf8>>; + + enosys -> + <<"Enosys"/utf8>>; + + enotblk -> + <<"Enotblk"/utf8>>; + + enotdir -> + <<"Enotdir"/utf8>>; + + enotsup -> + <<"Enotsup"/utf8>>; + + enxio -> + <<"Enxio"/utf8>>; + + eopnotsupp -> + <<"Eopnotsupp"/utf8>>; + + eoverflow -> + <<"Eoverflow"/utf8>>; + + eperm -> + <<"Eperm"/utf8>>; + + epipe -> + <<"Epipe"/utf8>>; + + erange -> + <<"Erange"/utf8>>; + + erofs -> + <<"Erofs"/utf8>>; + + espipe -> + <<"Espipe"/utf8>>; + + esrch -> + <<"Esrch"/utf8>>; + + estale -> + <<"Estale"/utf8>>; + + etxtbsy -> + <<"Etxtbsy"/utf8>>; + + exdev -> + <<"Exdev"/utf8>> + end. diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@socket@options.erl b/examples/http_interactions/build/packages/glisten/src/glisten@socket@options.erl new file mode 100644 index 0000000..9840b02 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@socket@options.erl @@ -0,0 +1,64 @@ +-module(glisten@socket@options). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/socket/options.gleam"). +-export([to_erl_options/1, merge_type_list/2, merge_with_defaults/1]). +-export_type([socket_mode/0, active_state/0, interface/0, tls_certs/0, tcp_option/0, erlang_tcp_option/0, ip_address/0]). + +-type socket_mode() :: binary. + +-type active_state() :: once | passive | {count, integer()} | active. + +-type interface() :: {address, ip_address()} | any | loopback. + +-type tls_certs() :: {cert_key_files, binary(), binary()}. + +-type tcp_option() :: {backlog, integer()} | + {nodelay, boolean()} | + {linger, {boolean(), integer()}} | + {send_timeout, integer()} | + {send_timeout_close, boolean()} | + {reuseaddr, boolean()} | + {active_mode, active_state()} | + {mode, socket_mode()} | + {cert_key_config, tls_certs()} | + {alpn_preferred_protocols, list(binary())} | + ipv6 | + {buffer, integer()} | + {ip, interface()}. + +-type erlang_tcp_option() :: any(). + +-type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} | + {ip_v6, + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. + +-file("src/glisten/socket/options.gleam", 56). +-spec to_erl_options(list(tcp_option())) -> list(erlang_tcp_option()). +to_erl_options(Options) -> + glisten_ffi:to_erl_tcp_options(Options). + +-file("src/glisten/socket/options.gleam", 69). +-spec merge_type_list(list(FGJ), list(FGJ)) -> list(FGJ). +merge_type_list(Original, Override) -> + glisten_ffi:merge_type_list(Original, Override). + +-file("src/glisten/socket/options.gleam", 71). +-spec merge_with_defaults(list(tcp_option())) -> list(tcp_option()). +merge_with_defaults(Options) -> + glisten_ffi:merge_type_list( + [{backlog, 1024}, + {nodelay, true}, + {send_timeout, 30000}, + {send_timeout_close, true}, + {reuseaddr, true}, + {mode, binary}, + {active_mode, passive}], + Options + ). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@ssl.erl b/examples/http_interactions/build/packages/glisten/src/glisten@ssl.erl new file mode 100644 index 0000000..aa155ec --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@ssl.erl @@ -0,0 +1,125 @@ +-module(glisten@ssl). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/ssl.gleam"). +-export([controlling_process/2, accept_timeout/2, accept/1, receive_timeout/3, 'receive'/2, send/2, close/1, do_shutdown/2, shutdown/1, set_opts/2, handshake/1, listen/2, negotiated_protocol/1, peername/1, sockname/1, get_socket_opts/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/glisten/ssl.gleam", 9). +-spec controlling_process(glisten@socket:socket(), gleam@erlang@process:pid_()) -> {ok, + nil} | + {error, gleam@erlang@atom:atom_()}. +controlling_process(Socket, Pid) -> + glisten_ssl_ffi:controlling_process(Socket, Pid). + +-file("src/glisten/ssl.gleam", 18). +-spec accept_timeout(glisten@socket:listen_socket(), integer()) -> {ok, + glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept_timeout(Socket, Timeout) -> + ssl:transport_accept(Socket, Timeout). + +-file("src/glisten/ssl.gleam", 24). +-spec accept(glisten@socket:listen_socket()) -> {ok, glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept(Socket) -> + ssl:transport_accept(Socket). + +-file("src/glisten/ssl.gleam", 27). +-spec receive_timeout(glisten@socket:socket(), integer(), integer()) -> {ok, + bitstring()} | + {error, glisten@socket:socket_reason()}. +receive_timeout(Socket, Length, Timeout) -> + ssl:recv(Socket, Length, Timeout). + +-file("src/glisten/ssl.gleam", 34). +-spec 'receive'(glisten@socket:socket(), integer()) -> {ok, bitstring()} | + {error, glisten@socket:socket_reason()}. +'receive'(Socket, Length) -> + ssl:recv(Socket, Length). + +-file("src/glisten/ssl.gleam", 37). +-spec send(glisten@socket:socket(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +send(Socket, Packet) -> + glisten_ssl_ffi:send(Socket, Packet). + +-file("src/glisten/ssl.gleam", 40). +-spec close(glisten@socket:socket()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +close(Socket) -> + glisten_ssl_ffi:close(Socket). + +-file("src/glisten/ssl.gleam", 43). +-spec do_shutdown(glisten@socket:socket(), gleam@erlang@atom:atom_()) -> {ok, + nil} | + {error, glisten@socket:socket_reason()}. +do_shutdown(Socket, Write) -> + glisten_ssl_ffi:shutdown(Socket, Write). + +-file("src/glisten/ssl.gleam", 45). +-spec shutdown(glisten@socket:socket()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +shutdown(Socket) -> + glisten_ssl_ffi:shutdown(Socket, erlang:binary_to_atom(<<"write"/utf8>>)). + +-file("src/glisten/ssl.gleam", 56). +?DOC(" Update the optons for a socket (mutates the socket)\n"). +-spec set_opts( + glisten@socket:socket(), + list(glisten@socket@options:tcp_option()) +) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +set_opts(Socket, Opts) -> + _pipe = Opts, + _pipe@1 = glisten_ffi:to_erl_tcp_options(_pipe), + glisten_ssl_ffi:set_opts(Socket, _pipe@1). + +-file("src/glisten/ssl.gleam", 66). +-spec handshake(glisten@socket:socket()) -> {ok, glisten@socket:socket()} | + {error, nil}. +handshake(Socket) -> + ssl:handshake(Socket). + +-file("src/glisten/ssl.gleam", 69). +?DOC(" Start listening over TLS on a port with the given options\n"). +-spec listen(integer(), list(glisten@socket@options:tcp_option())) -> {ok, + glisten@socket:listen_socket()} | + {error, glisten@socket:socket_reason()}. +listen(Port, Options) -> + _pipe = Options, + _pipe@1 = glisten@socket@options:merge_with_defaults(_pipe), + _pipe@2 = glisten_ffi:to_erl_tcp_options(_pipe@1), + ssl:listen(Port, _pipe@2). + +-file("src/glisten/ssl.gleam", 80). +-spec negotiated_protocol(glisten@socket:socket()) -> {ok, binary()} | + {error, binary()}. +negotiated_protocol(Socket) -> + glisten_ssl_ffi:negotiated_protocol(Socket). + +-file("src/glisten/ssl.gleam", 83). +-spec peername(glisten@socket:socket()) -> {ok, + {gleam@dynamic:dynamic_(), integer()}} | + {error, glisten@socket:socket_reason()}. +peername(Socket) -> + ssl:peername(Socket). + +-file("src/glisten/ssl.gleam", 86). +-spec sockname(glisten@socket:listen_socket()) -> {ok, + {gleam@dynamic:dynamic_(), integer()}} | + {error, glisten@socket:socket_reason()}. +sockname(Socket) -> + ssl:sockname(Socket). + +-file("src/glisten/ssl.gleam", 89). +-spec get_socket_opts(glisten@socket:socket(), list(gleam@erlang@atom:atom_())) -> {ok, + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()})} | + {error, glisten@socket:socket_reason()}. +get_socket_opts(Socket, Opts) -> + ssl:getopts(Socket, Opts). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@tcp.erl b/examples/http_interactions/build/packages/glisten/src/glisten@tcp.erl new file mode 100644 index 0000000..d27e0c8 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@tcp.erl @@ -0,0 +1,128 @@ +-module(glisten@tcp). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/tcp.gleam"). +-export([controlling_process/2, accept_timeout/2, accept/1, receive_timeout/3, 'receive'/2, send/2, socket_info/1, close/1, do_shutdown/2, shutdown/1, set_opts/2, listen/2, handshake/1, negotiated_protocol/1, peername/1, get_socket_opts/2, sockname/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/glisten/tcp.gleam", 10). +-spec controlling_process(glisten@socket:socket(), gleam@erlang@process:pid_()) -> {ok, + nil} | + {error, gleam@erlang@atom:atom_()}. +controlling_process(Socket, Pid) -> + glisten_tcp_ffi:controlling_process(Socket, Pid). + +-file("src/glisten/tcp.gleam", 19). +-spec accept_timeout(glisten@socket:listen_socket(), integer()) -> {ok, + glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept_timeout(Socket, Timeout) -> + gen_tcp:accept(Socket, Timeout). + +-file("src/glisten/tcp.gleam", 25). +-spec accept(glisten@socket:listen_socket()) -> {ok, glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept(Socket) -> + gen_tcp:accept(Socket). + +-file("src/glisten/tcp.gleam", 28). +-spec receive_timeout(glisten@socket:socket(), integer(), integer()) -> {ok, + bitstring()} | + {error, glisten@socket:socket_reason()}. +receive_timeout(Socket, Length, Timeout) -> + gen_tcp:recv(Socket, Length, Timeout). + +-file("src/glisten/tcp.gleam", 35). +-spec 'receive'(glisten@socket:socket(), integer()) -> {ok, bitstring()} | + {error, glisten@socket:socket_reason()}. +'receive'(Socket, Length) -> + gen_tcp:recv(Socket, Length). + +-file("src/glisten/tcp.gleam", 38). +-spec send(glisten@socket:socket(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +send(Socket, Packet) -> + glisten_tcp_ffi:send(Socket, Packet). + +-file("src/glisten/tcp.gleam", 41). +-spec socket_info(glisten@socket:socket()) -> gleam@dict:dict(any(), any()). +socket_info(Socket) -> + socket:info(Socket). + +-file("src/glisten/tcp.gleam", 44). +-spec close(any()) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +close(Socket) -> + glisten_tcp_ffi:close(Socket). + +-file("src/glisten/tcp.gleam", 47). +-spec do_shutdown(glisten@socket:socket(), gleam@erlang@atom:atom_()) -> {ok, + nil} | + {error, glisten@socket:socket_reason()}. +do_shutdown(Socket, Write) -> + glisten_tcp_ffi:shutdown(Socket, Write). + +-file("src/glisten/tcp.gleam", 49). +-spec shutdown(glisten@socket:socket()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +shutdown(Socket) -> + glisten_tcp_ffi:shutdown(Socket, erlang:binary_to_atom(<<"write"/utf8>>)). + +-file("src/glisten/tcp.gleam", 60). +?DOC(" Update the optons for a socket (mutates the socket)\n"). +-spec set_opts( + glisten@socket:socket(), + list(glisten@socket@options:tcp_option()) +) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +set_opts(Socket, Opts) -> + _pipe = Opts, + _pipe@1 = glisten_ffi:to_erl_tcp_options(_pipe), + glisten_tcp_ffi:set_opts(Socket, _pipe@1). + +-file("src/glisten/tcp.gleam", 70). +?DOC(" Start listening over TCP on a port with the given options\n"). +-spec listen(integer(), list(glisten@socket@options:tcp_option())) -> {ok, + glisten@socket:listen_socket()} | + {error, glisten@socket:socket_reason()}. +listen(Port, Opts) -> + _pipe = Opts, + _pipe@1 = glisten@socket@options:merge_with_defaults(_pipe), + _pipe@2 = glisten_ffi:to_erl_tcp_options(_pipe@1), + gen_tcp:listen(Port, _pipe@2). + +-file("src/glisten/tcp.gleam", 80). +-spec handshake(glisten@socket:socket()) -> {ok, glisten@socket:socket()} | + {error, nil}. +handshake(Socket) -> + {ok, Socket}. + +-file("src/glisten/tcp.gleam", 85). +-spec negotiated_protocol(glisten@socket:socket()) -> any(). +negotiated_protocol(Socket) -> + tcp:negotiated_protocol(Socket). + +-file("src/glisten/tcp.gleam", 88). +-spec peername(glisten@socket:socket()) -> {ok, + {gleam@dynamic:dynamic_(), integer()}} | + {error, glisten@socket:socket_reason()}. +peername(Socket) -> + inet:peername(Socket). + +-file("src/glisten/tcp.gleam", 91). +-spec get_socket_opts(glisten@socket:socket(), list(gleam@erlang@atom:atom_())) -> {ok, + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()})} | + {error, glisten@socket:socket_reason()}. +get_socket_opts(Socket, Opts) -> + inet:getopts(Socket, Opts). + +-file("src/glisten/tcp.gleam", 97). +-spec sockname(glisten@socket:listen_socket()) -> {ok, + {gleam@dynamic:dynamic_(), integer()}} | + {error, glisten@socket:socket_reason()}. +sockname(Socket) -> + inet:sockname(Socket). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten@transport.erl b/examples/http_interactions/build/packages/glisten/src/glisten@transport.erl new file mode 100644 index 0000000..b870d23 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten@transport.erl @@ -0,0 +1,414 @@ +-module(glisten@transport). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/glisten/transport.gleam"). +-export([controlling_process/3, listen/3, accept_timeout/3, accept/2, handshake/2, receive_timeout/4, 'receive'/3, send/3, close/2, shutdown/2, set_opts/3, negotiated_protocol/2, decode_ip/0, peername/2, socket_info/1, get_socket_opts/3, set_buffer_size/2, sockname/2]). +-export_type([transport/0]). + +-type transport() :: tcp | ssl. + +-file("src/glisten/transport.gleam", 18). +-spec controlling_process( + transport(), + glisten@socket:socket(), + gleam@erlang@process:pid_() +) -> {ok, nil} | {error, gleam@erlang@atom:atom_()}. +controlling_process(Transport, Socket, Pid) -> + case Transport of + tcp -> + glisten_tcp_ffi:controlling_process(Socket, Pid); + + ssl -> + glisten_ssl_ffi:controlling_process(Socket, Pid) + end. + +-file("src/glisten/transport.gleam", 29). +-spec listen(transport(), integer(), list(glisten@socket@options:tcp_option())) -> {ok, + glisten@socket:listen_socket()} | + {error, glisten@socket:socket_reason()}. +listen(Transport, Port, Opts) -> + case Transport of + tcp -> + glisten@tcp:listen(Port, Opts); + + ssl -> + glisten@ssl:listen(Port, Opts) + end. + +-file("src/glisten/transport.gleam", 40). +-spec accept_timeout(transport(), glisten@socket:listen_socket(), integer()) -> {ok, + glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept_timeout(Transport, Socket, Timeout) -> + case Transport of + tcp -> + gen_tcp:accept(Socket, Timeout); + + ssl -> + ssl:transport_accept(Socket, Timeout) + end. + +-file("src/glisten/transport.gleam", 51). +-spec accept(transport(), glisten@socket:listen_socket()) -> {ok, + glisten@socket:socket()} | + {error, glisten@socket:socket_reason()}. +accept(Transport, Socket) -> + case Transport of + tcp -> + gen_tcp:accept(Socket); + + ssl -> + ssl:transport_accept(Socket) + end. + +-file("src/glisten/transport.gleam", 61). +-spec handshake(transport(), glisten@socket:socket()) -> {ok, + glisten@socket:socket()} | + {error, nil}. +handshake(Transport, Socket) -> + case Transport of + tcp -> + glisten@tcp:handshake(Socket); + + ssl -> + ssl:handshake(Socket) + end. + +-file("src/glisten/transport.gleam", 68). +-spec receive_timeout( + transport(), + glisten@socket:socket(), + integer(), + integer() +) -> {ok, bitstring()} | {error, glisten@socket:socket_reason()}. +receive_timeout(Transport, Socket, Amount, Timeout) -> + case Transport of + tcp -> + gen_tcp:recv(Socket, Amount, Timeout); + + ssl -> + ssl:recv(Socket, Amount, Timeout) + end. + +-file("src/glisten/transport.gleam", 80). +-spec 'receive'(transport(), glisten@socket:socket(), integer()) -> {ok, + bitstring()} | + {error, glisten@socket:socket_reason()}. +'receive'(Transport, Socket, Amount) -> + case Transport of + tcp -> + gen_tcp:recv(Socket, Amount); + + ssl -> + ssl:recv(Socket, Amount) + end. + +-file("src/glisten/transport.gleam", 91). +-spec send(transport(), glisten@socket:socket(), gleam@bytes_tree:bytes_tree()) -> {ok, + nil} | + {error, glisten@socket:socket_reason()}. +send(Transport, Socket, Data) -> + case Transport of + tcp -> + glisten_tcp_ffi:send(Socket, Data); + + ssl -> + glisten_ssl_ffi:send(Socket, Data) + end. + +-file("src/glisten/transport.gleam", 102). +-spec close(transport(), glisten@socket:socket()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +close(Transport, Socket) -> + case Transport of + tcp -> + glisten_tcp_ffi:close(Socket); + + ssl -> + glisten_ssl_ffi:close(Socket) + end. + +-file("src/glisten/transport.gleam", 109). +-spec shutdown(transport(), glisten@socket:socket()) -> {ok, nil} | + {error, glisten@socket:socket_reason()}. +shutdown(Transport, Socket) -> + case Transport of + tcp -> + glisten@tcp:shutdown(Socket); + + ssl -> + glisten@ssl:shutdown(Socket) + end. + +-file("src/glisten/transport.gleam", 119). +-spec set_opts( + transport(), + glisten@socket:socket(), + list(glisten@socket@options:tcp_option()) +) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +set_opts(Transport, Socket, Opts) -> + case Transport of + tcp -> + glisten@tcp:set_opts(Socket, Opts); + + ssl -> + glisten@ssl:set_opts(Socket, Opts) + end. + +-file("src/glisten/transport.gleam", 130). +-spec negotiated_protocol(transport(), glisten@socket:socket()) -> {ok, + binary()} | + {error, binary()}. +negotiated_protocol(Transport, Socket) -> + case Transport of + tcp -> + {error, <<"Can't negotiate protocol on tcp"/utf8>>}; + + ssl -> + glisten_ssl_ffi:negotiated_protocol(Socket) + end. + +-file("src/glisten/transport.gleam", 140). +-spec decode_ipv4() -> gleam@dynamic@decode:decoder(glisten@socket@options:ip_address()). +decode_ipv4() -> + gleam@dynamic@decode:field( + 0, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(A) -> + gleam@dynamic@decode:field( + 1, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(B) -> + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(C) -> + gleam@dynamic@decode:field( + 3, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(D) -> + gleam@dynamic@decode:success( + {ip_v4, A, B, C, D} + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/glisten/transport.gleam", 148). +-spec decode_ipv6() -> gleam@dynamic@decode:decoder(glisten@socket@options:ip_address()). +decode_ipv6() -> + gleam@dynamic@decode:field( + 0, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(A) -> + gleam@dynamic@decode:field( + 1, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(B) -> + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(C) -> + gleam@dynamic@decode:field( + 3, + {decoder, fun gleam@dynamic@decode:decode_int/1}, + fun(D) -> + gleam@dynamic@decode:field( + 4, + {decoder, + fun gleam@dynamic@decode:decode_int/1}, + fun(E) -> + gleam@dynamic@decode:field( + 5, + {decoder, + fun gleam@dynamic@decode:decode_int/1}, + fun(F) -> + gleam@dynamic@decode:field( + 6, + {decoder, + fun gleam@dynamic@decode:decode_int/1}, + fun(G) -> + gleam@dynamic@decode:field( + 7, + {decoder, + fun gleam@dynamic@decode:decode_int/1}, + fun(H) -> + case {A, + B, + C, + D, + E, + F, + G, + H} of + {0, + 0, + 0, + 0, + 0, + 65535, + A@1, + B@1} -> + {A@2, + B@2, + C@1, + D@1} = inet:ipv4_mapped_ipv6_address( + {A@1, + B@1, + C, + D, + E, + F, + G, + H} + ), + gleam@dynamic@decode:success( + {ip_v4, + A@2, + B@2, + C@1, + D@1} + ); + + {_, + _, + _, + _, + _, + _, + _, + _} -> + gleam@dynamic@decode:success( + {ip_v6, + A, + B, + C, + D, + E, + F, + G, + H} + ) + end + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/glisten/transport.gleam", 168). +-spec decode_ip() -> gleam@dynamic@decode:decoder(glisten@socket@options:ip_address()). +decode_ip() -> + gleam@dynamic@decode:one_of(decode_ipv6(), [decode_ipv4()]). + +-file("src/glisten/transport.gleam", 172). +-spec peername(transport(), glisten@socket:socket()) -> {ok, + {glisten@socket@options:ip_address(), integer()}} | + {error, nil}. +peername(Transport, Socket) -> + _pipe = case Transport of + tcp -> + inet:peername(Socket); + + ssl -> + ssl:peername(Socket) + end, + _pipe@1 = gleam@result:replace_error(_pipe, nil), + gleam@result:'try'( + _pipe@1, + fun(Pair) -> + {Ip_address, Port} = Pair, + _pipe@2 = gleam@dynamic@decode:run(Ip_address, decode_ip()), + _pipe@3 = gleam@result:map(_pipe@2, fun(Ip) -> {Ip, Port} end), + gleam@result:replace_error(_pipe@3, nil) + end + ). + +-file("src/glisten/transport.gleam", 193). +-spec socket_info(glisten@socket:socket()) -> gleam@dict:dict(gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()). +socket_info(Socket) -> + socket:info(Socket). + +-file("src/glisten/transport.gleam", 195). +-spec get_socket_opts( + transport(), + glisten@socket:socket(), + list(gleam@erlang@atom:atom_()) +) -> {ok, list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()})} | + {error, nil}. +get_socket_opts(Transport, Socket, Opts) -> + _pipe = case Transport of + tcp -> + inet:getopts(Socket, Opts); + + ssl -> + ssl:getopts(Socket, Opts) + end, + gleam@result:replace_error(_pipe, nil). + +-file("src/glisten/transport.gleam", 207). +-spec set_buffer_size(transport(), glisten@socket:socket()) -> {ok, nil} | + {error, nil}. +set_buffer_size(Transport, Socket) -> + _pipe = get_socket_opts( + Transport, + Socket, + [erlang:binary_to_atom(<<"recbuf"/utf8>>)] + ), + _pipe@3 = gleam@result:'try'(_pipe, fun(P) -> case P of + [{_, Value}] -> + _pipe@1 = Value, + _pipe@2 = gleam@dynamic@decode:run( + _pipe@1, + {decoder, fun gleam@dynamic@decode:decode_int/1} + ), + gleam@result:replace_error(_pipe@2, nil); + + _ -> + {error, nil} + end end), + gleam@result:'try'( + _pipe@3, + fun(Value@1) -> + _pipe@4 = set_opts(Transport, Socket, [{buffer, Value@1}]), + gleam@result:map_error(_pipe@4, fun(_) -> nil end) + end + ). + +-file("src/glisten/transport.gleam", 224). +-spec sockname(transport(), glisten@socket:listen_socket()) -> {ok, + {glisten@socket@options:ip_address(), integer()}} | + {error, glisten@socket:socket_reason()}. +sockname(Transport, Socket) -> + _pipe = case Transport of + tcp -> + inet:sockname(Socket); + + ssl -> + ssl:sockname(Socket) + end, + gleam@result:'try'( + _pipe, + fun(Pair) -> + {Maybe_ip, Port} = Pair, + _pipe@1 = Maybe_ip, + _pipe@2 = gleam@dynamic@decode:run(_pipe@1, decode_ip()), + _pipe@3 = gleam@result:map(_pipe@2, fun(Ip) -> {Ip, Port} end), + gleam@result:replace_error(_pipe@3, badarg) + end + ). diff --git a/examples/http_interactions/build/packages/glisten/src/glisten_ffi.erl b/examples/http_interactions/build/packages/glisten/src/glisten_ffi.erl new file mode 100644 index 0000000..175da9a --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten_ffi.erl @@ -0,0 +1,68 @@ +-module(glisten_ffi). + +-export([parse_address/1, rescue/1, to_erl_tcp_options/1, + merge_type_list/2, socket_data/1]). + +parse_address(Address) -> + case inet:parse_address(Address) of + {ok, {A, B, C, D}} -> + {ok, {ip_v4, A, B, C, D}}; + {ok, {A, B, C, D, E, F, G, H}} -> + {ok, {ip_v6, A, B, C, D, E, F, G, H}}; + {error, _Reason} -> + {error, nil} + end. + +rescue(Func) -> + try + Res = Func(), + {ok, Res} + catch + Anything -> {error, Anything} + end. + +to_erl_tcp_options(Options) -> + lists:map(fun(A) -> to_erl_tcp_option(A) end, Options). + +to_erl_tcp_option({active_mode, once}) -> {active, once}; +to_erl_tcp_option({active_mode, passive}) -> {active, false}; +to_erl_tcp_option({active_mode, active}) -> {active, true}; +to_erl_tcp_option({active_mode, {count, N}}) -> {active, N}; +to_erl_tcp_option({ip, {address, {ip_v4, A, B, C, D}}}) -> + {ip, {A, B, C, D}}; +to_erl_tcp_option({ip, {address, {ip_v6, A, B, C, D, E, F, G, H}}}) -> + {ip, {A, B, C, D, E, F, G, H}}; +to_erl_tcp_option({ip, any}) -> {ip, any}; +to_erl_tcp_option({ip, loopback}) -> {ip, loopback}; +to_erl_tcp_option(ipv6) -> inet6; +to_erl_tcp_option({cert_key_config, {cert_key_files, CertFile, KeyFile}}) -> + {certs_keys, [#{certfile => CertFile, keyfile => KeyFile}]}; +to_erl_tcp_option(Other) -> Other. + +merge_type_list(Original, Override) -> + NewKeys = get_type_keys(Override), + KeepFromOriginal = + lists:foldl( + fun(Value, Accu) -> insert_value(Value, Accu, NewKeys) end, + [], + Original), + KeepFromOriginal ++ Override. + +insert_value(Value, Accu, NewKeys) -> + case lists:member(get_type_key(Value), NewKeys) of + true -> Accu; + false -> [ Value | Accu ] + end. + +get_type_keys(TypeList) -> + lists:map( + fun(Type) -> get_type_key(Type) end, + TypeList). + +get_type_key(Type) when is_atom(Type) -> Type; +get_type_key(Type) when is_tuple(Type) -> element(1, Type). + +socket_data({tcp, _Socket, Data}) -> Data; +socket_data({ssl, _Socket, Data}) -> Data; +socket_data({tcp_error, _Socket, Reason}) -> Reason; +socket_data({ssl_error, _Socket, Reason}) -> Reason. diff --git a/examples/http_interactions/build/packages/glisten/src/glisten_ssl_ffi.erl b/examples/http_interactions/build/packages/glisten/src/glisten_ssl_ffi.erl new file mode 100644 index 0000000..d87cfbc --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten_ssl_ffi.erl @@ -0,0 +1,52 @@ +-module(glisten_ssl_ffi). + +-export([controlling_process/2, send/2, set_opts/2, shutdown/2, close/1, + negotiated_protocol/1]). + +send(Socket, Packet) -> + case ssl:send(Socket, Packet) of + ok -> + {ok, nil}; + Res -> + Res + end. + +set_opts(Socket, Options) -> + case ssl:setopts(Socket, Options) of + ok -> + {ok, nil}; + {error, _Reason} -> + {error, nil} + end. + +controlling_process(Socket, Pid) -> + case ssl:controlling_process(Socket, Pid) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +shutdown(Socket, How) -> + case ssl:shutdown(Socket, How) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +close(Socket) -> + case ssl:close(Socket) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +negotiated_protocol(Socket) -> + case ssl:negotiated_protocol(Socket) of + {error, _} -> + {error, "Socket not negotiated"}; + Protocol -> + Protocol + end. diff --git a/examples/http_interactions/build/packages/glisten/src/glisten_tcp_ffi.erl b/examples/http_interactions/build/packages/glisten/src/glisten_tcp_ffi.erl new file mode 100644 index 0000000..1130c49 --- /dev/null +++ b/examples/http_interactions/build/packages/glisten/src/glisten_tcp_ffi.erl @@ -0,0 +1,43 @@ +-module(glisten_tcp_ffi). + +-export([controlling_process/2, send/2, set_opts/2, shutdown/2, close/1]). + +send(Socket, Packet) -> + case gen_tcp:send(Socket, Packet) of + ok -> + {ok, nil}; + Res -> + Res + end. + +set_opts(Socket, Options) -> + case inet:setopts(Socket, Options) of + ok -> + {ok, nil}; + {error, _Reason} -> + {error, nil} + end. + +controlling_process(Socket, Pid) -> + case gen_tcp:controlling_process(Socket, Pid) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +shutdown(Socket, How) -> + case gen_tcp:shutdown(Socket, How) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +close(Socket) -> + case gen_tcp:close(Socket) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. diff --git a/examples/http_interactions/build/packages/gramps/README.md b/examples/http_interactions/build/packages/gramps/README.md new file mode 100644 index 0000000..a52dc98 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/README.md @@ -0,0 +1,19 @@ +# gramps + +[![Package Version](https://img.shields.io/hexpm/v/gramps)](https://hex.pm/packages/gramps) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gramps/) + +Some helper data types and functions for WebSockets. + +Used in [stratus](https://github.com/rawhat/stratus) and [mist](https://github.com/rawhat/mist) as well. + +I will document this more, and am definitely open to adding / reshaping this to +fit the needs of other packages! + +## Installation + +```sh +gleam add gramps +``` + +Its documentation can be found at . diff --git a/examples/http_interactions/build/packages/gramps/gleam.toml b/examples/http_interactions/build/packages/gramps/gleam.toml new file mode 100644 index 0000000..85c3727 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/gleam.toml @@ -0,0 +1,15 @@ +name = "gramps" +version = "6.0.1" +description = "A Gleam HTTP and WebSocket helper library" + +licences = ["Apache-2.0"] +repository = { type = "github", user = "rawhat", repo = "gramps" } + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" +gleam_http = ">= 3.6.0 and < 5.0.0" +gleam_crypto = ">= 1.3.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.6.1 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@http_HttpError.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@http_HttpError.hrl new file mode 100644 index 0000000..19e915b --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@http_HttpError.hrl @@ -0,0 +1 @@ +-record(http_error, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@http_More.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@http_More.hrl new file mode 100644 index 0000000..a1f1ef7 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@http_More.hrl @@ -0,0 +1 @@ +-record(more, {length :: integer()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Compression.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Compression.hrl new file mode 100644 index 0000000..2ce9ff4 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Compression.hrl @@ -0,0 +1,4 @@ +-record(compression, { + inflate :: gramps@websocket@compression:context(), + deflate :: gramps@websocket@compression:context() +}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Context.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Context.hrl new file mode 100644 index 0000000..1ecef5f --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_Context.hrl @@ -0,0 +1,4 @@ +-record(context, { + context :: gramps@websocket@compression:compression_context(), + no_takeover :: boolean() +}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_ContextTakeover.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_ContextTakeover.hrl new file mode 100644 index 0000000..b84fe98 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket@compression_ContextTakeover.hrl @@ -0,0 +1 @@ +-record(context_takeover, {no_client :: boolean(), no_server :: boolean()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_BinaryFrame.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_BinaryFrame.hrl new file mode 100644 index 0000000..b43c8fe --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_BinaryFrame.hrl @@ -0,0 +1 @@ +-record(binary_frame, {payload :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CloseFrame.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CloseFrame.hrl new file mode 100644 index 0000000..1967711 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CloseFrame.hrl @@ -0,0 +1 @@ +-record(close_frame, {reason :: gramps@websocket:close_reason()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Continuation.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Continuation.hrl new file mode 100644 index 0000000..3987aba --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Continuation.hrl @@ -0,0 +1 @@ +-record(continuation, {length :: integer(), payload :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CustomCloseReason.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CustomCloseReason.hrl new file mode 100644 index 0000000..92e3e4f --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_CustomCloseReason.hrl @@ -0,0 +1 @@ +-record(custom_close_reason, {code :: integer(), body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_GoingAway.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_GoingAway.hrl new file mode 100644 index 0000000..4992317 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_GoingAway.hrl @@ -0,0 +1 @@ +-record(going_away, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_InconsistentDataType.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_InconsistentDataType.hrl new file mode 100644 index 0000000..aedbbdb --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_InconsistentDataType.hrl @@ -0,0 +1 @@ +-record(inconsistent_data_type, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MessageTooBig.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MessageTooBig.hrl new file mode 100644 index 0000000..ec6d67f --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MessageTooBig.hrl @@ -0,0 +1 @@ +-record(message_too_big, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MissingExtensions.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MissingExtensions.hrl new file mode 100644 index 0000000..2c6ab73 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_MissingExtensions.hrl @@ -0,0 +1 @@ +-record(missing_extensions, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Normal.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Normal.hrl new file mode 100644 index 0000000..ab2c396 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_Normal.hrl @@ -0,0 +1 @@ +-record(normal, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PingFrame.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PingFrame.hrl new file mode 100644 index 0000000..aaf5bbf --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PingFrame.hrl @@ -0,0 +1 @@ +-record(ping_frame, {payload :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PolicyViolation.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PolicyViolation.hrl new file mode 100644 index 0000000..7fa4ca6 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PolicyViolation.hrl @@ -0,0 +1 @@ +-record(policy_violation, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PongFrame.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PongFrame.hrl new file mode 100644 index 0000000..7a28de3 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_PongFrame.hrl @@ -0,0 +1 @@ +-record(pong_frame, {payload :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_ProtocolError.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_ProtocolError.hrl new file mode 100644 index 0000000..32ed846 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_ProtocolError.hrl @@ -0,0 +1 @@ +-record(protocol_error, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_TextFrame.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_TextFrame.hrl new file mode 100644 index 0000000..dcbe7ae --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_TextFrame.hrl @@ -0,0 +1 @@ +-record(text_frame, {payload :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedCondition.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedCondition.hrl new file mode 100644 index 0000000..ffe5cec --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedCondition.hrl @@ -0,0 +1 @@ +-record(unexpected_condition, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedDataType.hrl b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedDataType.hrl new file mode 100644 index 0000000..bf800f9 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/include/gramps@websocket_UnexpectedDataType.hrl @@ -0,0 +1 @@ +-record(unexpected_data_type, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/gramps/src/gramps.app.src b/examples/http_interactions/build/packages/gramps/src/gramps.app.src new file mode 100644 index 0000000..aa52d55 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps.app.src @@ -0,0 +1,15 @@ +{application, gramps, [ + {vsn, "6.0.1"}, + {applications, [gleam_crypto, + gleam_erlang, + gleam_http, + gleam_stdlib]}, + {description, "A Gleam HTTP and WebSocket helper library"}, + {modules, [gramps@@main, + gramps@debug, + gramps@http, + gramps@websocket, + gramps@websocket@compression, + gramps_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/gramps/src/gramps/debug.gleam b/examples/http_interactions/build/packages/gramps/src/gramps/debug.gleam new file mode 100644 index 0000000..8da57ee --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps/debug.gleam @@ -0,0 +1,9 @@ +import gleam/list + +pub fn literal_bits(source: BitArray, values: List(Int)) -> List(Int) { + case source { + <<>> -> list.reverse(values) + <> -> literal_bits(rest, [bit, ..values]) + _ -> panic as "where'd that bit go" + } +} diff --git a/examples/http_interactions/build/packages/gramps/src/gramps/http.gleam b/examples/http_interactions/build/packages/gramps/src/gramps/http.gleam new file mode 100644 index 0000000..fd31566 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps/http.gleam @@ -0,0 +1,219 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/dynamic.{type Dynamic} +import gleam/http.{type Header, type Scheme} +import gleam/http/request.{type Request, Request} +import gleam/http/response.{type Response, Response} +import gleam/int +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import gleam/string + +pub type PacketType { + HttphBin + HttpBin +} + +pub type DecodePacketError { + More(length: Int) + HttpError(reason: String) +} + +type UriPacket { + AbsPath(String) + AbsoluteUri(scheme: Scheme, host: String, port: Int, path: String) +} + +type DecodedPacket { + HttpRequest(method: String, uri: UriPacket, version: #(Int, Int)) + HttpResponse(version: #(Int, Int), status: Int, text: String) + HttpHeader(unknown: Int, field: Dynamic, raw_field: String, value: String) + HttpEoh +} + +@external(erlang, "gramps_ffi", "decode_packet") +fn decode_packet( + kind: PacketType, + bin: BitArray, + opts: List(options), +) -> Result(#(DecodedPacket, BitArray), DecodePacketError) + +fn get_headers( + data: BitArray, + headers: List(#(String, String)), +) -> Result(#(List(#(String, String)), BitArray), DecodePacketError) { + case decode_packet(HttphBin, data, []) { + Ok(#(HttpEoh, rest)) -> Ok(#(headers, rest)) + Ok(#(HttpHeader(raw_field: field, value: value, ..), rest)) -> + get_headers(rest, [#(string.lowercase(field), value), ..headers]) + Ok(val) -> { + Error(HttpError( + "Expected only headers but got request/response: " + <> string.inspect(val), + )) + } + Error(reason) -> Error(reason) + } +} + +pub fn read_response( + data: BitArray, +) -> Result(#(Response(Nil), BitArray), DecodePacketError) { + case decode_packet(HttpBin, data, []) { + Ok(#(HttpResponse(_version, status, _text), rest)) -> { + case get_headers(rest, []) { + Ok(#(headers, rest)) -> { + Ok(#(Response(body: Nil, headers: headers, status: status), rest)) + } + Error(reason) -> Error(reason) + } + } + Error(reason) -> Error(reason) + _ -> Error(HttpError("Unexpected data")) + } +} + +pub fn read_request( + data: BitArray, +) -> Result(#(Request(Nil), BitArray), DecodePacketError) { + case decode_packet(HttpBin, data, []) { + Ok(#(HttpRequest(method, uri, _version), rest)) -> { + case get_headers(rest, []) { + Ok(#(headers, rest)) -> { + let host = + headers + |> list.key_find("host") + |> result.unwrap("") + let #(scheme, host, port, path) = case uri { + AbsoluteUri(scheme, host, port, path) -> { + #(scheme, host, Some(port), path) + } + AbsPath(path) -> { + #(http.Http, host, None, path) + } + } + let #(path, query) = + path + |> string.split_once("?") + |> result.map(fn(pair) { #(pair.0, Some(pair.1)) }) + |> result.unwrap(#(path, None)) + let method = http.parse_method(method) + Ok(#( + Request( + body: Nil, + headers: headers, + host: host, + path: path, + method: result.unwrap(method, http.Get), + port: port, + scheme: scheme, + query: query, + ), + rest, + )) + } + Error(reason) -> Error(reason) + } + } + Error(reason) -> Error(reason) + err -> { + Error(HttpError("Unexpected data: " <> string.inspect(err))) + } + } +} + +/// Turns an HTTP response into a TCP message +pub fn to_bytes_tree(resp: Response(BytesTree)) -> BytesTree { + resp.status + |> response_builder(resp.headers) + |> bytes_tree.append_tree(resp.body) +} + +pub fn response_builder(status: Int, headers: List(Header)) -> BytesTree { + let status_string = + status + |> int.to_string + |> bytes_tree.from_string + |> bytes_tree.append(<<" ":utf8>>) + |> bytes_tree.append(status_to_bit_array(status)) + + bytes_tree.new() + |> bytes_tree.append(<<"HTTP/1.1 ":utf8>>) + |> bytes_tree.append_tree(status_string) + |> bytes_tree.append(<<"\r\n":utf8>>) + |> bytes_tree.append_tree(encode_headers(headers)) + |> bytes_tree.append(<<"\r\n":utf8>>) +} + +pub fn status_to_bit_array(status: Int) -> BitArray { + // Obviously nowhere near exhaustive... + case status { + 100 -> <<"Continue":utf8>> + 101 -> <<"Switching Protocols":utf8>> + 103 -> <<"Early Hints":utf8>> + 200 -> <<"OK":utf8>> + 201 -> <<"Created":utf8>> + 202 -> <<"Accepted":utf8>> + 203 -> <<"Non-Authoritative Information":utf8>> + 204 -> <<"No Content":utf8>> + 205 -> <<"Reset Content":utf8>> + 206 -> <<"Partial Content":utf8>> + 300 -> <<"Multiple Choices":utf8>> + 301 -> <<"Moved Permanently":utf8>> + 302 -> <<"Found":utf8>> + 303 -> <<"See Other":utf8>> + 304 -> <<"Not Modified":utf8>> + 307 -> <<"Temporary Redirect":utf8>> + 308 -> <<"Permanent Redirect":utf8>> + 400 -> <<"Bad Request":utf8>> + 401 -> <<"Unauthorized":utf8>> + 402 -> <<"Payment Required":utf8>> + 403 -> <<"Forbidden":utf8>> + 404 -> <<"Not Found":utf8>> + 405 -> <<"Method Not Allowed":utf8>> + 406 -> <<"Not Acceptable":utf8>> + 407 -> <<"Proxy Authentication Required":utf8>> + 408 -> <<"Request Timeout":utf8>> + 409 -> <<"Conflict":utf8>> + 410 -> <<"Gone":utf8>> + 411 -> <<"Length Required":utf8>> + 412 -> <<"Precondition Failed":utf8>> + 413 -> <<"Payload Too Large":utf8>> + 414 -> <<"URI Too Long":utf8>> + 415 -> <<"Unsupported Media Type":utf8>> + 416 -> <<"Range Not Satisfiable":utf8>> + 417 -> <<"Expectation Failed":utf8>> + 418 -> <<"I'm a teapot":utf8>> + 422 -> <<"Unprocessable Entity":utf8>> + 425 -> <<"Too Early":utf8>> + 426 -> <<"Upgrade Required":utf8>> + 428 -> <<"Precondition Required":utf8>> + 429 -> <<"Too Many Requests":utf8>> + 431 -> <<"Request Header Fields Too Large":utf8>> + 451 -> <<"Unavailable For Legal Reasons":utf8>> + 500 -> <<"Internal Server Error":utf8>> + 501 -> <<"Not Implemented":utf8>> + 502 -> <<"Bad Gateway":utf8>> + 503 -> <<"Service Unavailable":utf8>> + 504 -> <<"Gateway Timeout":utf8>> + 505 -> <<"HTTP Version Not Supported":utf8>> + 506 -> <<"Variant Also Negotiates":utf8>> + 507 -> <<"Insufficient Storage":utf8>> + 508 -> <<"Loop Detected":utf8>> + 510 -> <<"Not Extended":utf8>> + 511 -> <<"Network Authentication Required":utf8>> + _ -> <<"Unknown HTTP Status":utf8>> + } +} + +pub fn encode_headers(headers: List(Header)) -> BytesTree { + list.fold(headers, bytes_tree.new(), fn(builder, tup) { + let #(header, value) = tup + + builder + |> bytes_tree.append_string(header) + |> bytes_tree.append(<<": ":utf8>>) + |> bytes_tree.append_string(value) + |> bytes_tree.append(<<"\r\n":utf8>>) + }) +} diff --git a/examples/http_interactions/build/packages/gramps/src/gramps/websocket.gleam b/examples/http_interactions/build/packages/gramps/src/gramps/websocket.gleam new file mode 100644 index 0000000..69ad936 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps/websocket.gleam @@ -0,0 +1,527 @@ +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree.{type BytesTree} +import gleam/crypto +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string +import gramps/websocket/compression.{ + type Context, type ContextTakeover, ContextTakeover, +} + +pub type DataFrame { + TextFrame(payload: BitArray) + BinaryFrame(payload: BitArray) +} + +pub type CloseReason { + NotProvided + Normal(body: BitArray) + GoingAway(body: BitArray) + ProtocolError(body: BitArray) + UnexpectedDataType(body: BitArray) + InconsistentDataType(body: BitArray) + PolicyViolation(body: BitArray) + MessageTooBig(body: BitArray) + MissingExtensions(body: BitArray) + UnexpectedCondition(body: BitArray) + /// Usually used for `4000` codes. + CustomCloseReason( + /// If `code >= 5000`, it will be the same as a `Normal` close reason. + code: Int, + body: BitArray, + ) +} + +pub type ControlFrame { + CloseFrame(reason: CloseReason) + PingFrame(payload: BitArray) + PongFrame(payload: BitArray) +} + +pub type Frame { + Data(DataFrame) + Control(ControlFrame) + Continuation(length: Int, payload: BitArray) +} + +@external(erlang, "crypto", "exor") +fn crypto_exor(a a: BitArray, b b: BitArray) -> BitArray + +fn mask_data( + data: BitArray, + masks: List(BitArray), + index: Int, + resp: BitArray, +) -> BitArray { + case data { + <> -> { + let assert [one, two, three, four] = masks + let mask_value = case index % 4 { + 0 -> one + 1 -> two + 2 -> three + 3 -> four + _ -> panic as "Somehow a value mod 4 is not 0, 1, 2, or 3" + } + let unmasked = crypto_exor(mask_value, masked) + mask_data(rest, masks, index + 1, <>) + } + _ -> resp + } +} + +pub type FrameParseError { + NeedMoreData(BitArray) + InvalidFrame +} + +pub type ParsedFrame { + Complete(Frame) + Incomplete(Frame) +} + +pub fn decode_frame( + message: BitArray, + context: Option(Context), +) -> Result(#(ParsedFrame, BitArray), FrameParseError) { + case message { + << + complete:1, + compressed:1, + _reserved:2, + opcode:int-size(4), + masked:1, + payload_length:int-size(7), + rest:bits, + >> -> { + let compressed = compressed == 1 + let masked = masked == 1 + use <- bool.guard( + when: compressed && option.is_none(context), + return: Error(InvalidFrame), + ) + let payload_size = case payload_length { + 126 -> 16 + 127 -> 64 + _ -> 0 + } + let maybe_pair = case masked, rest { + True, + << + length:int-size(payload_size), + mask1:bytes-size(1), + mask2:bytes-size(1), + mask3:bytes-size(1), + mask4:bytes-size(1), + rest:bits, + >> + -> { + let payload_byte_size = case length { + 0 -> payload_length + n -> n + } + case rest { + <> -> { + let data = + mask_data(payload, [mask1, mask2, mask3, mask4], 0, <<>>) + Ok(#(data, rest)) + } + _ -> { + Error(NeedMoreData(message)) + } + } + } + False, <> -> { + let payload_byte_size = case length { + 0 -> payload_length + n -> n + } + case rest { + <> -> { + Ok(#(payload, rest)) + } + _ -> { + Error(NeedMoreData(message)) + } + } + } + _, _ -> Error(InvalidFrame) + } + + use #(data, rest) <- result.try(maybe_pair) + case opcode { + 0 -> { + data + |> inflate(compressed, context, _) + |> result.map(fn(decompressed) { + Continuation(payload_size, decompressed) + }) + } + 1 -> { + data + |> inflate(compressed, context, _) + |> result.map(fn(decompressed) { Data(TextFrame(decompressed)) }) + } + 2 -> { + data + |> inflate(compressed, context, _) + |> result.map(fn(decompressed) { Data(BinaryFrame(decompressed)) }) + } + 8 -> { + case data { + <<1000:16, rest:bits>> -> Ok(Control(CloseFrame(Normal(rest)))) + <<1001:16, rest:bits>> -> Ok(Control(CloseFrame(GoingAway(rest)))) + <<1002:16, rest:bits>> -> + Ok(Control(CloseFrame(ProtocolError(rest)))) + <<1003:16, rest:bits>> -> + Ok(Control(CloseFrame(UnexpectedDataType(rest)))) + <<1007:16, rest:bits>> -> + Ok(Control(CloseFrame(InconsistentDataType(rest)))) + <<1008:16, rest:bits>> -> + Ok(Control(CloseFrame(PolicyViolation(rest)))) + <<1009:16, rest:bits>> -> + Ok(Control(CloseFrame(MessageTooBig(rest)))) + <<1010:16, rest:bits>> -> + Ok(Control(CloseFrame(MissingExtensions(rest)))) + <<1011:16, rest:bits>> -> + Ok(Control(CloseFrame(UnexpectedCondition(rest)))) + <> -> + Ok(Control(CloseFrame(CustomCloseReason(code, rest)))) + _ -> Ok(Control(CloseFrame(NotProvided))) + } + } + 9 -> Ok(Control(PingFrame(data))) + 10 -> Ok(Control(PongFrame(data))) + _ -> Error(InvalidFrame) + } + |> result.try(fn(frame) { + case complete { + 1 -> Ok(#(Complete(frame), rest)) + 0 -> Ok(#(Incomplete(frame), rest)) + _ -> Error(InvalidFrame) + } + }) + } + _ -> Error(InvalidFrame) + } +} + +pub fn encode_text_frame( + data: String, + context: Option(Context), + mask: Option(BitArray), +) -> BytesTree { + to_frame(bit_array.from_string(data), context, mask, TextFrame, Data) +} + +pub fn encode_binary_frame( + data: BitArray, + context: Option(Context), + mask: Option(BitArray), +) -> BytesTree { + to_frame(data, context, mask, BinaryFrame, Data) +} + +pub fn encode_close_frame( + reason: CloseReason, + mask: Option(BitArray), +) -> BytesTree { + encode_frame(Control(CloseFrame(reason)), Uncompressed, mask) +} + +pub fn encode_ping_frame(data: BitArray, mask: Option(BitArray)) -> BytesTree { + to_frame(data, None, mask, PingFrame, Control) +} + +pub fn encode_pong_frame(data: BitArray, mask: Option(BitArray)) -> BytesTree { + to_frame(data, None, mask, PongFrame, Control) +} + +pub fn encode_continuation_frame( + data: BitArray, + total_size: Int, + mask: Option(BitArray), +) -> BytesTree { + let payload = apply_mask(data, mask) + encode_frame(Continuation(total_size, payload), Uncompressed, mask) +} + +fn encode_frame( + frame: Frame, + compressed: Compression, + mask: Option(BitArray), +) -> BytesTree { + case frame { + Data(TextFrame(payload)) -> { + let payload_length = bit_array.byte_size(payload) + make_frame(1, payload_length, payload, compressed, mask) + } + Control(CloseFrame(reason)) -> { + let #(payload_length, payload) = case reason { + NotProvided -> #(0, <<>>) + GoingAway(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1001:16, body:bits>>) + } + InconsistentDataType(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1007:16, body:bits>>) + } + MessageTooBig(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1009:16, body:bits>>) + } + MissingExtensions(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1010:16, body:bits>>) + } + Normal(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1000:16, body:bits>>) + } + PolicyViolation(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1008:16, body:bits>>) + } + ProtocolError(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1002:16, body:bits>>) + } + UnexpectedCondition(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1011:16, body:bits>>) + } + UnexpectedDataType(body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + #(payload_size, <<1003:16, body:bits>>) + } + CustomCloseReason(code:, body:) -> { + let payload_size = bit_array.byte_size(body) + 2 + // Prevents integer overflow and changes the status code to `Normal` for invalid codes. + let code = case code < 5000 { + True -> code + False -> 1000 + } + #(payload_size, <>) + } + } + make_frame(8, payload_length, apply_mask(payload, mask), compressed, mask) + } + Data(BinaryFrame(payload)) -> { + let payload_length = bit_array.byte_size(payload) + make_frame(2, payload_length, payload, compressed, mask) + } + Control(PongFrame(payload)) -> { + let payload_length = bit_array.byte_size(payload) + make_frame(10, payload_length, payload, compressed, mask) + } + Control(PingFrame(payload)) -> { + let payload_length = bit_array.byte_size(payload) + make_frame(9, payload_length, payload, compressed, mask) + } + Continuation(length, payload) -> + make_frame(0, length, payload, compressed, mask) + } +} + +fn make_length(length: Int) -> BitArray { + case length { + length if length > 65_535 -> <<127:7, length:int-size(64)>> + length if length >= 126 -> <<126:7, length:int-size(16)>> + _length -> <> + } +} + +type Compression { + Compressed + Uncompressed +} + +fn make_frame( + opcode: Int, + length: Int, + payload: BitArray, + compressed: Compression, + mask: Option(BitArray), +) -> BytesTree { + let length_section = make_length(length) + + let masked = case option.is_some(mask) { + True -> 1 + False -> 0 + } + + let mask_key = option.unwrap(mask, <<>>) + + let compressed = case compressed { + Compressed -> 1 + Uncompressed -> 0 + } + + << + 1:1, + compressed:1, + 0:2, + opcode:4, + masked:1, + length_section:bits, + mask_key:bits, + payload:bits, + >> + |> bytes_tree.from_bit_array +} + +pub fn apply_mask(data: BitArray, mask: Option(BitArray)) -> BitArray { + case mask { + Some(mask) -> { + let assert << + mask1:bytes-size(1), + mask2:bytes-size(1), + mask3:bytes-size(1), + mask4:bytes-size(1), + >> = mask + mask_data(data, [mask1, mask2, mask3, mask4], 0, <<>>) + } + None -> data + } +} + +pub fn apply_deflate(data: BitArray, context: Option(Context)) -> BitArray { + case context { + Some(context) -> compression.deflate(context, data) + _ -> data + } +} + +pub fn apply_inflate(data: BitArray, context: Option(Context)) -> BitArray { + case context { + Some(context) -> compression.deflate(context, data) + _ -> data + } +} + +fn to_frame( + data: BitArray, + context: Option(Context), + mask: Option(BitArray), + create_inner_frame: fn(BitArray) -> a, + create_frame: fn(a) -> Frame, +) -> BytesTree { + let frame = + data + |> apply_deflate(context) + |> apply_mask(mask) + |> create_inner_frame + |> create_frame + let compress = case context { + Some(_context) -> Compressed + _ -> Uncompressed + } + encode_frame(frame, compress, mask) +} + +pub fn decode_many_frames( + data: BitArray, + context: Option(Context), + frames: List(ParsedFrame), +) -> #(List(ParsedFrame), BitArray) { + case decode_frame(data, context) { + Ok(#(frame, <<>>)) -> #(list.reverse([frame, ..frames]), <<>>) + Ok(#(frame, rest)) -> decode_many_frames(rest, context, [frame, ..frames]) + Error(NeedMoreData(rest)) -> #(list.reverse(frames), rest) + Error(InvalidFrame) -> #(list.reverse(frames), data) + } +} + +fn append_frame(left: Frame, data: BitArray) -> Frame { + case left { + Data(TextFrame(payload)) -> Data(TextFrame(<>)) + Data(BinaryFrame(payload)) -> Data(BinaryFrame(<>)) + Control(CloseFrame(..)) -> left + Control(PingFrame(payload)) -> + Control(PingFrame(<>)) + Control(PongFrame(payload)) -> + Control(PongFrame(<>)) + Continuation(..) -> left + } +} + +pub fn aggregate_frames( + frames: List(ParsedFrame), + previous: Option(Frame), + joined: List(Frame), +) -> Result(List(Frame), Nil) { + case frames, previous { + [], _ -> Ok(list.reverse(joined)) + [Complete(Continuation(payload: data, ..)), ..rest], Some(prev) -> { + let next = append_frame(prev, data) + aggregate_frames(rest, None, [next, ..joined]) + } + [Incomplete(Continuation(payload: data, ..)), ..rest], Some(prev) -> { + let next = append_frame(prev, data) + aggregate_frames(rest, Some(next), joined) + } + [Incomplete(frame), ..rest], None -> { + aggregate_frames(rest, Some(frame), joined) + } + [Complete(frame), ..rest], None -> { + aggregate_frames(rest, None, [frame, ..joined]) + } + _, _ -> Error(Nil) + } +} + +const websocket_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +pub fn make_client_key() -> String { + let bytes = crypto.strong_random_bytes(16) + bit_array.base64_encode(bytes, True) +} + +type ShaHash { + Sha +} + +pub fn parse_websocket_key(key: String) -> String { + key + |> string.append(websocket_key) + |> crypto_hash(Sha, _) + |> base64_encode +} + +@external(erlang, "crypto", "hash") +fn crypto_hash(hash hash: ShaHash, data data: String) -> String + +@external(erlang, "base64", "encode") +fn base64_encode(data data: String) -> String + +fn inflate( + compressed: Bool, + context: Option(Context), + data: BitArray, +) -> Result(BitArray, FrameParseError) { + case compressed, context { + True, Some(context) -> { + Ok(compression.inflate(context, data)) + } + True, None -> Error(InvalidFrame) + _, _ -> Ok(data) + } +} + +pub fn has_deflate(extensions: List(String)) -> Bool { + list.any(extensions, fn(str) { str == "permessage-deflate" }) +} + +pub fn get_context_takeovers(extensions: List(String)) -> ContextTakeover { + let no_client_context_takeover = + list.any(extensions, fn(str) { str == "client_no_context_takeover" }) + let no_server_context_takeover = + list.any(extensions, fn(str) { str == "server_no_context_takeover" }) + ContextTakeover( + no_client: no_client_context_takeover, + no_server: no_server_context_takeover, + ) +} diff --git a/examples/http_interactions/build/packages/gramps/src/gramps/websocket/compression.gleam b/examples/http_interactions/build/packages/gramps/src/gramps/websocket/compression.gleam new file mode 100644 index 0000000..536d312 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps/websocket/compression.gleam @@ -0,0 +1,121 @@ +import gleam/bit_array +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Pid} + +pub type CompressionContext + +pub type Context { + Context(context: CompressionContext, no_takeover: Bool) +} + +type Flush { + Sync +} + +type Deflated { + Deflated +} + +type Default { + Default +} + +pub type ContextTakeover { + ContextTakeover(no_client: Bool, no_server: Bool) +} + +pub type Compression { + Compression(inflate: Context, deflate: Context) +} + +pub fn init(takeover: ContextTakeover) -> Compression { + let inflate = open() + let inflate_context = + Context(context: inflate, no_takeover: takeover.no_client) + + inflate_init(inflate, -15) + let deflate = open() + let deflate_context = + Context(context: deflate, no_takeover: takeover.no_server) + deflate_init(deflate, Default, Deflated, -15, 8, Default) + + Compression(inflate: inflate_context, deflate: deflate_context) +} + +@external(erlang, "zlib", "inflateInit") +fn inflate_init(context: CompressionContext, bits: Int) -> Atom + +@external(erlang, "zlib", "deflateInit") +fn deflate_init( + context: CompressionContext, + level: Default, + deflated: Deflated, + bits: Int, + mem_level: Int, + strategy: Default, +) -> Atom + +@external(erlang, "zlib", "open") +fn open() -> CompressionContext + +@external(erlang, "zlib", "inflate") +fn do_inflate(context: CompressionContext, data: BitArray) -> BytesTree + +pub fn inflate(context: Context, data: BitArray) -> BitArray { + let output = + context.context + |> do_inflate(<>) + |> bytes_tree.to_bit_array + + let _ = case context.no_takeover { + True -> inflate_reset(context.context) + False -> Nil + } + + output +} + +@external(erlang, "zlib", "deflate") +fn do_deflate( + context: CompressionContext, + data: BitArray, + flush: Flush, +) -> BytesTree + +pub fn deflate(context: Context, data: BitArray) -> BitArray { + let data = + context.context + |> do_deflate(data, Sync) + |> bytes_tree.to_bit_array + + let size = bit_array.byte_size(data) - 4 + + let return = case data { + <> -> value + _ -> data + } + + let _ = case context.no_takeover { + True -> deflate_reset(context.context) + False -> Nil + } + + return +} + +@external(erlang, "zlib", "set_controlling_process") +pub fn set_controlling_process(context: Context, pid: Pid) -> Atom + +pub fn close(context: Context) -> Nil { + do_close(context.context) +} + +@external(erlang, "zlib", "close") +fn do_close(context: CompressionContext) -> Nil + +@external(erlang, "zlib", "inflateReset") +fn inflate_reset(context: CompressionContext) -> Nil + +@external(erlang, "zlib", "deflateReset") +fn deflate_reset(context: CompressionContext) -> Nil diff --git a/examples/http_interactions/build/packages/gramps/src/gramps@debug.erl b/examples/http_interactions/build/packages/gramps/src/gramps@debug.erl new file mode 100644 index 0000000..135aa86 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps@debug.erl @@ -0,0 +1,23 @@ +-module(gramps@debug). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gramps/debug.gleam"). +-export([literal_bits/2]). + +-file("src/gramps/debug.gleam", 3). +-spec literal_bits(bitstring(), list(integer())) -> list(integer()). +literal_bits(Source, Values) -> + case Source of + <<>> -> + lists:reverse(Values); + + <> -> + literal_bits(Rest, [Bit | Values]); + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"where'd that bit go"/utf8>>, + file => <>, + module => <<"gramps/debug"/utf8>>, + function => <<"literal_bits"/utf8>>, + line => 7}) + end. diff --git a/examples/http_interactions/build/packages/gramps/src/gramps@http.erl b/examples/http_interactions/build/packages/gramps/src/gramps@http.erl new file mode 100644 index 0000000..2d1d011 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps@http.erl @@ -0,0 +1,343 @@ +-module(gramps@http). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gramps/http.gleam"). +-export([read_response/1, read_request/1, status_to_bit_array/1, encode_headers/1, response_builder/2, to_bytes_tree/1]). +-export_type([packet_type/0, decode_packet_error/0, uri_packet/0, decoded_packet/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type packet_type() :: httph_bin | http_bin. + +-type decode_packet_error() :: {more, integer()} | {http_error, binary()}. + +-type uri_packet() :: {abs_path, binary()} | + {absolute_uri, gleam@http:scheme(), binary(), integer(), binary()}. + +-type decoded_packet() :: {http_request, + binary(), + uri_packet(), + {integer(), integer()}} | + {http_response, {integer(), integer()}, integer(), binary()} | + {http_header, integer(), gleam@dynamic:dynamic_(), binary(), binary()} | + http_eoh. + +-file("src/gramps/http.gleam", 41). +-spec get_headers(bitstring(), list({binary(), binary()})) -> {ok, + {list({binary(), binary()}), bitstring()}} | + {error, decode_packet_error()}. +get_headers(Data, Headers) -> + case gramps_ffi:decode_packet(httph_bin, Data, []) of + {ok, {http_eoh, Rest}} -> + {ok, {Headers, Rest}}; + + {ok, {{http_header, _, _, Field, Value}, Rest@1}} -> + get_headers(Rest@1, [{string:lowercase(Field), Value} | Headers]); + + {ok, Val} -> + {error, + {http_error, + <<"Expected only headers but got request/response: "/utf8, + (gleam@string:inspect(Val))/binary>>}}; + + {error, Reason} -> + {error, Reason} + end. + +-file("src/gramps/http.gleam", 59). +-spec read_response(bitstring()) -> {ok, + {gleam@http@response:response(nil), bitstring()}} | + {error, decode_packet_error()}. +read_response(Data) -> + case gramps_ffi:decode_packet(http_bin, Data, []) of + {ok, {{http_response, _, Status, _}, Rest}} -> + case get_headers(Rest, []) of + {ok, {Headers, Rest@1}} -> + {ok, {{response, Status, Headers, nil}, Rest@1}}; + + {error, Reason} -> + {error, Reason} + end; + + {error, Reason@1} -> + {error, Reason@1}; + + _ -> + {error, {http_error, <<"Unexpected data"/utf8>>}} + end. + +-file("src/gramps/http.gleam", 76). +-spec read_request(bitstring()) -> {ok, + {gleam@http@request:request(nil), bitstring()}} | + {error, decode_packet_error()}. +read_request(Data) -> + case gramps_ffi:decode_packet(http_bin, Data, []) of + {ok, {{http_request, Method, Uri, _}, Rest}} -> + case get_headers(Rest, []) of + {ok, {Headers, Rest@1}} -> + Host = begin + _pipe = Headers, + _pipe@1 = gleam@list:key_find(_pipe, <<"host"/utf8>>), + gleam@result:unwrap(_pipe@1, <<""/utf8>>) + end, + {Scheme@1, Host@2, Port@1, Path@2} = case Uri of + {absolute_uri, Scheme, Host@1, Port, Path} -> + {Scheme, Host@1, {some, Port}, Path}; + + {abs_path, Path@1} -> + {http, Host, none, Path@1} + end, + {Path@3, Query} = begin + _pipe@2 = Path@2, + _pipe@3 = gleam@string:split_once(_pipe@2, <<"?"/utf8>>), + _pipe@4 = gleam@result:map( + _pipe@3, + fun(Pair) -> + {erlang:element(1, Pair), + {some, erlang:element(2, Pair)}} + end + ), + gleam@result:unwrap(_pipe@4, {Path@2, none}) + end, + Method@1 = gleam@http:parse_method(Method), + {ok, + {{request, + gleam@result:unwrap(Method@1, get), + Headers, + nil, + Scheme@1, + Host@2, + Port@1, + Path@3, + Query}, + Rest@1}}; + + {error, Reason} -> + {error, Reason} + end; + + {error, Reason@1} -> + {error, Reason@1}; + + Err -> + {error, + {http_error, + <<"Unexpected data: "/utf8, + (gleam@string:inspect(Err))/binary>>}} + end. + +-file("src/gramps/http.gleam", 148). +-spec status_to_bit_array(integer()) -> bitstring(). +status_to_bit_array(Status) -> + case Status of + 100 -> + <<"Continue"/utf8>>; + + 101 -> + <<"Switching Protocols"/utf8>>; + + 103 -> + <<"Early Hints"/utf8>>; + + 200 -> + <<"OK"/utf8>>; + + 201 -> + <<"Created"/utf8>>; + + 202 -> + <<"Accepted"/utf8>>; + + 203 -> + <<"Non-Authoritative Information"/utf8>>; + + 204 -> + <<"No Content"/utf8>>; + + 205 -> + <<"Reset Content"/utf8>>; + + 206 -> + <<"Partial Content"/utf8>>; + + 300 -> + <<"Multiple Choices"/utf8>>; + + 301 -> + <<"Moved Permanently"/utf8>>; + + 302 -> + <<"Found"/utf8>>; + + 303 -> + <<"See Other"/utf8>>; + + 304 -> + <<"Not Modified"/utf8>>; + + 307 -> + <<"Temporary Redirect"/utf8>>; + + 308 -> + <<"Permanent Redirect"/utf8>>; + + 400 -> + <<"Bad Request"/utf8>>; + + 401 -> + <<"Unauthorized"/utf8>>; + + 402 -> + <<"Payment Required"/utf8>>; + + 403 -> + <<"Forbidden"/utf8>>; + + 404 -> + <<"Not Found"/utf8>>; + + 405 -> + <<"Method Not Allowed"/utf8>>; + + 406 -> + <<"Not Acceptable"/utf8>>; + + 407 -> + <<"Proxy Authentication Required"/utf8>>; + + 408 -> + <<"Request Timeout"/utf8>>; + + 409 -> + <<"Conflict"/utf8>>; + + 410 -> + <<"Gone"/utf8>>; + + 411 -> + <<"Length Required"/utf8>>; + + 412 -> + <<"Precondition Failed"/utf8>>; + + 413 -> + <<"Payload Too Large"/utf8>>; + + 414 -> + <<"URI Too Long"/utf8>>; + + 415 -> + <<"Unsupported Media Type"/utf8>>; + + 416 -> + <<"Range Not Satisfiable"/utf8>>; + + 417 -> + <<"Expectation Failed"/utf8>>; + + 418 -> + <<"I'm a teapot"/utf8>>; + + 422 -> + <<"Unprocessable Entity"/utf8>>; + + 425 -> + <<"Too Early"/utf8>>; + + 426 -> + <<"Upgrade Required"/utf8>>; + + 428 -> + <<"Precondition Required"/utf8>>; + + 429 -> + <<"Too Many Requests"/utf8>>; + + 431 -> + <<"Request Header Fields Too Large"/utf8>>; + + 451 -> + <<"Unavailable For Legal Reasons"/utf8>>; + + 500 -> + <<"Internal Server Error"/utf8>>; + + 501 -> + <<"Not Implemented"/utf8>>; + + 502 -> + <<"Bad Gateway"/utf8>>; + + 503 -> + <<"Service Unavailable"/utf8>>; + + 504 -> + <<"Gateway Timeout"/utf8>>; + + 505 -> + <<"HTTP Version Not Supported"/utf8>>; + + 506 -> + <<"Variant Also Negotiates"/utf8>>; + + 507 -> + <<"Insufficient Storage"/utf8>>; + + 508 -> + <<"Loop Detected"/utf8>>; + + 510 -> + <<"Not Extended"/utf8>>; + + 511 -> + <<"Network Authentication Required"/utf8>>; + + _ -> + <<"Unknown HTTP Status"/utf8>> + end. + +-file("src/gramps/http.gleam", 209). +-spec encode_headers(list({binary(), binary()})) -> gleam@bytes_tree:bytes_tree(). +encode_headers(Headers) -> + gleam@list:fold( + Headers, + gleam@bytes_tree:new(), + fun(Builder, Tup) -> + {Header, Value} = Tup, + _pipe = Builder, + _pipe@1 = gleam@bytes_tree:append_string(_pipe, Header), + _pipe@2 = gleam@bytes_tree:append(_pipe@1, <<": "/utf8>>), + _pipe@3 = gleam@bytes_tree:append_string(_pipe@2, Value), + gleam@bytes_tree:append(_pipe@3, <<"\r\n"/utf8>>) + end + ). + +-file("src/gramps/http.gleam", 132). +-spec response_builder(integer(), list({binary(), binary()})) -> gleam@bytes_tree:bytes_tree(). +response_builder(Status, Headers) -> + Status_string = begin + _pipe = Status, + _pipe@1 = erlang:integer_to_binary(_pipe), + _pipe@2 = gleam_stdlib:wrap_list(_pipe@1), + _pipe@3 = gleam@bytes_tree:append(_pipe@2, <<" "/utf8>>), + gleam@bytes_tree:append(_pipe@3, status_to_bit_array(Status)) + end, + _pipe@4 = gleam@bytes_tree:new(), + _pipe@5 = gleam@bytes_tree:append(_pipe@4, <<"HTTP/1.1 "/utf8>>), + _pipe@6 = gleam_stdlib:iodata_append(_pipe@5, Status_string), + _pipe@7 = gleam@bytes_tree:append(_pipe@6, <<"\r\n"/utf8>>), + _pipe@8 = gleam_stdlib:iodata_append(_pipe@7, encode_headers(Headers)), + gleam@bytes_tree:append(_pipe@8, <<"\r\n"/utf8>>). + +-file("src/gramps/http.gleam", 126). +?DOC(" Turns an HTTP response into a TCP message\n"). +-spec to_bytes_tree(gleam@http@response:response(gleam@bytes_tree:bytes_tree())) -> gleam@bytes_tree:bytes_tree(). +to_bytes_tree(Resp) -> + _pipe = erlang:element(2, Resp), + _pipe@1 = response_builder(_pipe, erlang:element(3, Resp)), + gleam_stdlib:iodata_append(_pipe@1, erlang:element(4, Resp)). diff --git a/examples/http_interactions/build/packages/gramps/src/gramps@websocket.erl b/examples/http_interactions/build/packages/gramps/src/gramps@websocket.erl new file mode 100644 index 0000000..36c5133 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps@websocket.erl @@ -0,0 +1,737 @@ +-module(gramps@websocket). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gramps/websocket.gleam"). +-export([apply_mask/2, encode_close_frame/2, encode_continuation_frame/3, apply_deflate/2, apply_inflate/2, encode_text_frame/3, encode_binary_frame/3, encode_ping_frame/2, encode_pong_frame/2, aggregate_frames/3, make_client_key/0, decode_frame/2, decode_many_frames/3, has_deflate/1, get_context_takeovers/1, parse_websocket_key/1]). +-export_type([data_frame/0, close_reason/0, control_frame/0, frame/0, frame_parse_error/0, parsed_frame/0, compression/0, sha_hash/0]). + +-type data_frame() :: {text_frame, bitstring()} | {binary_frame, bitstring()}. + +-type close_reason() :: not_provided | + {normal, bitstring()} | + {going_away, bitstring()} | + {protocol_error, bitstring()} | + {unexpected_data_type, bitstring()} | + {inconsistent_data_type, bitstring()} | + {policy_violation, bitstring()} | + {message_too_big, bitstring()} | + {missing_extensions, bitstring()} | + {unexpected_condition, bitstring()} | + {custom_close_reason, integer(), bitstring()}. + +-type control_frame() :: {close_frame, close_reason()} | + {ping_frame, bitstring()} | + {pong_frame, bitstring()}. + +-type frame() :: {data, data_frame()} | + {control, control_frame()} | + {continuation, integer(), bitstring()}. + +-type frame_parse_error() :: {need_more_data, bitstring()} | invalid_frame. + +-type parsed_frame() :: {complete, frame()} | {incomplete, frame()}. + +-type compression() :: compressed | uncompressed. + +-type sha_hash() :: sha. + +-file("src/gramps/websocket.gleam", 52). +-spec mask_data(bitstring(), list(bitstring()), integer(), bitstring()) -> bitstring(). +mask_data(Data, Masks, Index, Resp) -> + case Data of + <> -> + {One@1, Two@1, Three@1, Four@1} = case Masks of + [One, Two, Three, Four] -> {One, Two, Three, Four}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gramps/websocket"/utf8>>, + function => <<"mask_data"/utf8>>, + line => 60, + value => _assert_fail, + start => 1398, + 'end' => 1440, + pattern_start => 1409, + pattern_end => 1432}) + end, + Mask_value = case Index rem 4 of + 0 -> + One@1; + + 1 -> + Two@1; + + 2 -> + Three@1; + + 3 -> + Four@1; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"Somehow a value mod 4 is not 0, 1, 2, or 3"/utf8>>, + file => <>, + module => <<"gramps/websocket"/utf8>>, + function => <<"mask_data"/utf8>>, + line => 66}) + end, + Unmasked = crypto:exor(Mask_value, Masked), + mask_data( + Rest, + Masks, + Index + 1, + <> + ); + + _ -> + Resp + end. + +-file("src/gramps/websocket.gleam", 329). +-spec make_length(integer()) -> bitstring(). +make_length(Length) -> + case Length of + Length@1 when Length@1 > 65535 -> + <<127:7, Length@1:64/integer>>; + + Length@2 when Length@2 >= 126 -> + <<126:7, Length@2:16/integer>>; + + _ -> + <> + end. + +-file("src/gramps/websocket.gleam", 342). +-spec make_frame( + integer(), + integer(), + bitstring(), + compression(), + gleam@option:option(bitstring()) +) -> gleam@bytes_tree:bytes_tree(). +make_frame(Opcode, Length, Payload, Compressed, Mask) -> + Length_section = make_length(Length), + Masked = case gleam@option:is_some(Mask) of + true -> + 1; + + false -> + 0 + end, + Mask_key = gleam@option:unwrap(Mask, <<>>), + Compressed@1 = case Compressed of + compressed -> + 1; + + uncompressed -> + 0 + end, + _pipe = <<1:1, + Compressed@1:1, + 0:2, + Opcode:4, + Masked:1, + Length_section/bitstring, + Mask_key/bitstring, + Payload/bitstring>>, + gleam@bytes_tree:from_bit_array(_pipe). + +-file("src/gramps/websocket.gleam", 376). +-spec apply_mask(bitstring(), gleam@option:option(bitstring())) -> bitstring(). +apply_mask(Data, Mask) -> + case Mask of + {some, Mask@1} -> + {Mask1@1, Mask2@1, Mask3@1, Mask4@1} = case Mask@1 of + <> -> {Mask1, Mask2, Mask3, Mask4}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"gramps/websocket"/utf8>>, + function => <<"apply_mask"/utf8>>, + line => 379, + value => _assert_fail, + start => 10538, + 'end' => 10683, + pattern_start => 10549, + pattern_end => 10676}) + end, + mask_data(Data, [Mask1@1, Mask2@1, Mask3@1, Mask4@1], 0, <<>>); + + none -> + Data + end. + +-file("src/gramps/websocket.gleam", 251). +-spec encode_frame(frame(), compression(), gleam@option:option(bitstring())) -> gleam@bytes_tree:bytes_tree(). +encode_frame(Frame, Compressed, Mask) -> + case Frame of + {data, {text_frame, Payload}} -> + Payload_length = erlang:byte_size(Payload), + make_frame(1, Payload_length, Payload, Compressed, Mask); + + {control, {close_frame, Reason}} -> + {Payload_length@1, Payload@1} = case Reason of + not_provided -> + {0, <<>>}; + + {going_away, Body} -> + Payload_size = erlang:byte_size(Body) + 2, + {Payload_size, <<1001:16, Body/bitstring>>}; + + {inconsistent_data_type, Body@1} -> + Payload_size@1 = erlang:byte_size(Body@1) + 2, + {Payload_size@1, <<1007:16, Body@1/bitstring>>}; + + {message_too_big, Body@2} -> + Payload_size@2 = erlang:byte_size(Body@2) + 2, + {Payload_size@2, <<1009:16, Body@2/bitstring>>}; + + {missing_extensions, Body@3} -> + Payload_size@3 = erlang:byte_size(Body@3) + 2, + {Payload_size@3, <<1010:16, Body@3/bitstring>>}; + + {normal, Body@4} -> + Payload_size@4 = erlang:byte_size(Body@4) + 2, + {Payload_size@4, <<1000:16, Body@4/bitstring>>}; + + {policy_violation, Body@5} -> + Payload_size@5 = erlang:byte_size(Body@5) + 2, + {Payload_size@5, <<1008:16, Body@5/bitstring>>}; + + {protocol_error, Body@6} -> + Payload_size@6 = erlang:byte_size(Body@6) + 2, + {Payload_size@6, <<1002:16, Body@6/bitstring>>}; + + {unexpected_condition, Body@7} -> + Payload_size@7 = erlang:byte_size(Body@7) + 2, + {Payload_size@7, <<1011:16, Body@7/bitstring>>}; + + {unexpected_data_type, Body@8} -> + Payload_size@8 = erlang:byte_size(Body@8) + 2, + {Payload_size@8, <<1003:16, Body@8/bitstring>>}; + + {custom_close_reason, Code, Body@9} -> + Payload_size@9 = erlang:byte_size(Body@9) + 2, + Code@1 = case Code < 5000 of + true -> + Code; + + false -> + 1000 + end, + {Payload_size@9, <>} + end, + make_frame( + 8, + Payload_length@1, + apply_mask(Payload@1, Mask), + Compressed, + Mask + ); + + {data, {binary_frame, Payload@2}} -> + Payload_length@2 = erlang:byte_size(Payload@2), + make_frame(2, Payload_length@2, Payload@2, Compressed, Mask); + + {control, {pong_frame, Payload@3}} -> + Payload_length@3 = erlang:byte_size(Payload@3), + make_frame(10, Payload_length@3, Payload@3, Compressed, Mask); + + {control, {ping_frame, Payload@4}} -> + Payload_length@4 = erlang:byte_size(Payload@4), + make_frame(9, Payload_length@4, Payload@4, Compressed, Mask); + + {continuation, Length, Payload@5} -> + make_frame(0, Length, Payload@5, Compressed, Mask) + end. + +-file("src/gramps/websocket.gleam", 227). +-spec encode_close_frame(close_reason(), gleam@option:option(bitstring())) -> gleam@bytes_tree:bytes_tree(). +encode_close_frame(Reason, Mask) -> + encode_frame({control, {close_frame, Reason}}, uncompressed, Mask). + +-file("src/gramps/websocket.gleam", 242). +-spec encode_continuation_frame( + bitstring(), + integer(), + gleam@option:option(bitstring()) +) -> gleam@bytes_tree:bytes_tree(). +encode_continuation_frame(Data, Total_size, Mask) -> + Payload = apply_mask(Data, Mask), + encode_frame({continuation, Total_size, Payload}, uncompressed, Mask). + +-file("src/gramps/websocket.gleam", 391). +-spec apply_deflate( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()) +) -> bitstring(). +apply_deflate(Data, Context) -> + case Context of + {some, Context@1} -> + gramps@websocket@compression:deflate(Context@1, Data); + + _ -> + Data + end. + +-file("src/gramps/websocket.gleam", 398). +-spec apply_inflate( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()) +) -> bitstring(). +apply_inflate(Data, Context) -> + case Context of + {some, Context@1} -> + gramps@websocket@compression:deflate(Context@1, Data); + + _ -> + Data + end. + +-file("src/gramps/websocket.gleam", 405). +-spec to_frame( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()), + gleam@option:option(bitstring()), + fun((bitstring()) -> FGB), + fun((FGB) -> frame()) +) -> gleam@bytes_tree:bytes_tree(). +to_frame(Data, Context, Mask, Create_inner_frame, Create_frame) -> + Frame = begin + _pipe = Data, + _pipe@1 = apply_deflate(_pipe, Context), + _pipe@2 = apply_mask(_pipe@1, Mask), + _pipe@3 = Create_inner_frame(_pipe@2), + Create_frame(_pipe@3) + end, + Compress = case Context of + {some, _} -> + compressed; + + _ -> + uncompressed + end, + encode_frame(Frame, Compress, Mask). + +-file("src/gramps/websocket.gleam", 211). +-spec encode_text_frame( + binary(), + gleam@option:option(gramps@websocket@compression:context()), + gleam@option:option(bitstring()) +) -> gleam@bytes_tree:bytes_tree(). +encode_text_frame(Data, Context, Mask) -> + to_frame( + gleam_stdlib:identity(Data), + Context, + Mask, + fun(Field@0) -> {text_frame, Field@0} end, + fun(Field@0) -> {data, Field@0} end + ). + +-file("src/gramps/websocket.gleam", 219). +-spec encode_binary_frame( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()), + gleam@option:option(bitstring()) +) -> gleam@bytes_tree:bytes_tree(). +encode_binary_frame(Data, Context, Mask) -> + to_frame( + Data, + Context, + Mask, + fun(Field@0) -> {binary_frame, Field@0} end, + fun(Field@0) -> {data, Field@0} end + ). + +-file("src/gramps/websocket.gleam", 234). +-spec encode_ping_frame(bitstring(), gleam@option:option(bitstring())) -> gleam@bytes_tree:bytes_tree(). +encode_ping_frame(Data, Mask) -> + to_frame( + Data, + none, + Mask, + fun(Field@0) -> {ping_frame, Field@0} end, + fun(Field@0) -> {control, Field@0} end + ). + +-file("src/gramps/websocket.gleam", 238). +-spec encode_pong_frame(bitstring(), gleam@option:option(bitstring())) -> gleam@bytes_tree:bytes_tree(). +encode_pong_frame(Data, Mask) -> + to_frame( + Data, + none, + Mask, + fun(Field@0) -> {pong_frame, Field@0} end, + fun(Field@0) -> {control, Field@0} end + ). + +-file("src/gramps/websocket.gleam", 438). +-spec append_frame(frame(), bitstring()) -> frame(). +append_frame(Left, Data) -> + case Left of + {data, {text_frame, Payload}} -> + {data, {text_frame, <>}}; + + {data, {binary_frame, Payload@1}} -> + {data, {binary_frame, <>}}; + + {control, {close_frame, _}} -> + Left; + + {control, {ping_frame, Payload@2}} -> + {control, {ping_frame, <>}}; + + {control, {pong_frame, Payload@3}} -> + {control, {pong_frame, <>}}; + + {continuation, _, _} -> + Left + end. + +-file("src/gramps/websocket.gleam", 451). +-spec aggregate_frames( + list(parsed_frame()), + gleam@option:option(frame()), + list(frame()) +) -> {ok, list(frame())} | {error, nil}. +aggregate_frames(Frames, Previous, Joined) -> + case {Frames, Previous} of + {[], _} -> + {ok, lists:reverse(Joined)}; + + {[{complete, {continuation, _, Data}} | Rest], {some, Prev}} -> + Next = append_frame(Prev, Data), + aggregate_frames(Rest, none, [Next | Joined]); + + {[{incomplete, {continuation, _, Data@1}} | Rest@1], {some, Prev@1}} -> + Next@1 = append_frame(Prev@1, Data@1), + aggregate_frames(Rest@1, {some, Next@1}, Joined); + + {[{incomplete, Frame} | Rest@2], none} -> + aggregate_frames(Rest@2, {some, Frame}, Joined); + + {[{complete, Frame@1} | Rest@3], none} -> + aggregate_frames(Rest@3, none, [Frame@1 | Joined]); + + {_, _} -> + {error, nil} + end. + +-file("src/gramps/websocket.gleam", 478). +-spec make_client_key() -> binary(). +make_client_key() -> + Bytes = crypto:strong_rand_bytes(16), + gleam_stdlib:base64_encode(Bytes, true). + +-file("src/gramps/websocket.gleam", 500). +-spec inflate( + boolean(), + gleam@option:option(gramps@websocket@compression:context()), + bitstring() +) -> {ok, bitstring()} | {error, frame_parse_error()}. +inflate(Compressed, Context, Data) -> + case {Compressed, Context} of + {true, {some, Context@1}} -> + {ok, gramps@websocket@compression:inflate(Context@1, Data)}; + + {true, none} -> + {error, invalid_frame}; + + {_, _} -> + {ok, Data} + end. + +-file("src/gramps/websocket.gleam", 85). +-spec decode_frame( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()) +) -> {ok, {parsed_frame(), bitstring()}} | {error, frame_parse_error()}. +decode_frame(Message, Context) -> + case Message of + <> -> + Compressed@1 = Compressed =:= 1, + Masked@1 = Masked =:= 1, + gleam@bool:guard( + Compressed@1 andalso gleam@option:is_none(Context), + {error, invalid_frame}, + fun() -> + Payload_size = case Payload_length of + 126 -> + 16; + + 127 -> + 64; + + _ -> + 0 + end, + Maybe_pair = case {Masked@1, Rest} of + {true, + <>} -> + Payload_byte_size = case Length of + 0 -> + Payload_length; + + N -> + N + end, + case Rest@1 of + <> -> + Data = mask_data( + Payload, + [Mask1, Mask2, Mask3, Mask4], + 0, + <<>> + ), + {ok, {Data, Rest@2}}; + + _ -> + {error, {need_more_data, Message}} + end; + + {false, + <>} -> + Payload_byte_size@1 = case Length@1 of + 0 -> + Payload_length; + + N@1 -> + N@1 + end, + case Rest@3 of + <> -> + {ok, {Payload@1, Rest@4}}; + + _ -> + {error, {need_more_data, Message}} + end; + + {_, _} -> + {error, invalid_frame} + end, + gleam@result:'try'( + Maybe_pair, + fun(_use0) -> + {Data@1, Rest@5} = _use0, + _pipe@6 = case Opcode of + 0 -> + _pipe = Data@1, + _pipe@1 = inflate( + Compressed@1, + Context, + _pipe + ), + gleam@result:map( + _pipe@1, + fun(Decompressed) -> + {continuation, + Payload_size, + Decompressed} + end + ); + + 1 -> + _pipe@2 = Data@1, + _pipe@3 = inflate( + Compressed@1, + Context, + _pipe@2 + ), + gleam@result:map( + _pipe@3, + fun(Decompressed@1) -> + {data, {text_frame, Decompressed@1}} + end + ); + + 2 -> + _pipe@4 = Data@1, + _pipe@5 = inflate( + Compressed@1, + Context, + _pipe@4 + ), + gleam@result:map( + _pipe@5, + fun(Decompressed@2) -> + {data, + {binary_frame, Decompressed@2}} + end + ); + + 8 -> + case Data@1 of + <<1000:16, Rest@6/bitstring>> -> + {ok, + {control, + {close_frame, + {normal, Rest@6}}}}; + + <<1001:16, Rest@7/bitstring>> -> + {ok, + {control, + {close_frame, + {going_away, Rest@7}}}}; + + <<1002:16, Rest@8/bitstring>> -> + {ok, + {control, + {close_frame, + {protocol_error, Rest@8}}}}; + + <<1003:16, Rest@9/bitstring>> -> + {ok, + {control, + {close_frame, + {unexpected_data_type, + Rest@9}}}}; + + <<1007:16, Rest@10/bitstring>> -> + {ok, + {control, + {close_frame, + {inconsistent_data_type, + Rest@10}}}}; + + <<1008:16, Rest@11/bitstring>> -> + {ok, + {control, + {close_frame, + {policy_violation, + Rest@11}}}}; + + <<1009:16, Rest@12/bitstring>> -> + {ok, + {control, + {close_frame, + {message_too_big, + Rest@12}}}}; + + <<1010:16, Rest@13/bitstring>> -> + {ok, + {control, + {close_frame, + {missing_extensions, + Rest@13}}}}; + + <<1011:16, Rest@14/bitstring>> -> + {ok, + {control, + {close_frame, + {unexpected_condition, + Rest@14}}}}; + + <> -> + {ok, + {control, + {close_frame, + {custom_close_reason, + Code, + Rest@15}}}}; + + _ -> + {ok, + {control, + {close_frame, not_provided}}} + end; + + 9 -> + {ok, {control, {ping_frame, Data@1}}}; + + 10 -> + {ok, {control, {pong_frame, Data@1}}}; + + _ -> + {error, invalid_frame} + end, + gleam@result:'try'( + _pipe@6, + fun(Frame) -> case Complete of + 1 -> + {ok, {{complete, Frame}, Rest@5}}; + + 0 -> + {ok, {{incomplete, Frame}, Rest@5}}; + + _ -> + {error, invalid_frame} + end end + ) + end + ) + end + ); + + _ -> + {error, invalid_frame} + end. + +-file("src/gramps/websocket.gleam", 425). +-spec decode_many_frames( + bitstring(), + gleam@option:option(gramps@websocket@compression:context()), + list(parsed_frame()) +) -> {list(parsed_frame()), bitstring()}. +decode_many_frames(Data, Context, Frames) -> + case decode_frame(Data, Context) of + {ok, {Frame, <<>>}} -> + {lists:reverse([Frame | Frames]), <<>>}; + + {ok, {Frame@1, Rest}} -> + decode_many_frames(Rest, Context, [Frame@1 | Frames]); + + {error, {need_more_data, Rest@1}} -> + {lists:reverse(Frames), Rest@1}; + + {error, invalid_frame} -> + {lists:reverse(Frames), Data} + end. + +-file("src/gramps/websocket.gleam", 514). +-spec has_deflate(list(binary())) -> boolean(). +has_deflate(Extensions) -> + gleam@list:any( + Extensions, + fun(Str) -> Str =:= <<"permessage-deflate"/utf8>> end + ). + +-file("src/gramps/websocket.gleam", 518). +-spec get_context_takeovers(list(binary())) -> gramps@websocket@compression:context_takeover(). +get_context_takeovers(Extensions) -> + No_client_context_takeover = gleam@list:any( + Extensions, + fun(Str) -> Str =:= <<"client_no_context_takeover"/utf8>> end + ), + No_server_context_takeover = gleam@list:any( + Extensions, + fun(Str@1) -> Str@1 =:= <<"server_no_context_takeover"/utf8>> end + ), + {context_takeover, No_client_context_takeover, No_server_context_takeover}. + +-file("src/gramps/websocket.gleam", 487). +-spec parse_websocket_key(binary()) -> binary(). +parse_websocket_key(Key) -> + _pipe = Key, + _pipe@1 = gleam@string:append( + _pipe, + <<"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"/utf8>> + ), + _pipe@2 = crypto:hash(sha, _pipe@1), + base64:encode(_pipe@2). diff --git a/examples/http_interactions/build/packages/gramps/src/gramps@websocket@compression.erl b/examples/http_interactions/build/packages/gramps/src/gramps@websocket@compression.erl new file mode 100644 index 0000000..a5333a3 --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps@websocket@compression.erl @@ -0,0 +1,85 @@ +-module(gramps@websocket@compression). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/gramps/websocket/compression.gleam"). +-export([init/1, set_controlling_process/2, close/1, inflate/2, deflate/2]). +-export_type([compression_context/0, context/0, flush/0, deflated/0, default/0, context_takeover/0, compression/0]). + +-type compression_context() :: any(). + +-type context() :: {context, compression_context(), boolean()}. + +-type flush() :: sync. + +-type deflated() :: deflated. + +-type default() :: default. + +-type context_takeover() :: {context_takeover, boolean(), boolean()}. + +-type compression() :: {compression, context(), context()}. + +-file("src/gramps/websocket/compression.gleam", 32). +-spec init(context_takeover()) -> compression(). +init(Takeover) -> + Inflate = zlib:open(), + Inflate_context = {context, Inflate, erlang:element(2, Takeover)}, + zlib:'inflateInit'(Inflate, -15), + Deflate = zlib:open(), + Deflate_context = {context, Deflate, erlang:element(3, Takeover)}, + zlib:'deflateInit'(Deflate, default, deflated, -15, 8, default), + {compression, Inflate_context, Deflate_context}. + +-file("src/gramps/websocket/compression.gleam", 108). +-spec set_controlling_process(context(), gleam@erlang@process:pid_()) -> gleam@erlang@atom:atom_(). +set_controlling_process(Context, Pid) -> + zlib:set_controlling_process(Context, Pid). + +-file("src/gramps/websocket/compression.gleam", 110). +-spec close(context()) -> nil. +close(Context) -> + zlib:close(erlang:element(2, Context)). + +-file("src/gramps/websocket/compression.gleam", 65). +-spec inflate(context(), bitstring()) -> bitstring(). +inflate(Context, Data) -> + Output = begin + _pipe = erlang:element(2, Context), + _pipe@1 = zlib:inflate( + _pipe, + <> + ), + erlang:list_to_bitstring(_pipe@1) + end, + _ = case erlang:element(3, Context) of + true -> + zlib:'inflateReset'(erlang:element(2, Context)); + + false -> + nil + end, + Output. + +-file("src/gramps/websocket/compression.gleam", 86). +-spec deflate(context(), bitstring()) -> bitstring(). +deflate(Context, Data) -> + Data@1 = begin + _pipe = erlang:element(2, Context), + _pipe@1 = zlib:deflate(_pipe, Data, sync), + erlang:list_to_bitstring(_pipe@1) + end, + Size = erlang:byte_size(Data@1) - 4, + Return = case Data@1 of + <> -> + Value; + + _ -> + Data@1 + end, + _ = case erlang:element(3, Context) of + true -> + zlib:'deflateReset'(erlang:element(2, Context)); + + false -> + nil + end, + Return. diff --git a/examples/http_interactions/build/packages/gramps/src/gramps_ffi.erl b/examples/http_interactions/build/packages/gramps/src/gramps_ffi.erl new file mode 100644 index 0000000..4530e6b --- /dev/null +++ b/examples/http_interactions/build/packages/gramps/src/gramps_ffi.erl @@ -0,0 +1,15 @@ +-module(gramps_ffi). + +-export([decode_packet/3]). + +decode_packet(Type, Bin, Opts) -> + case erlang:decode_packet(Type, Bin, Opts) of + {ok, {http_request, Method, Uri, Version}, Rest} when is_atom(Method) -> + {ok, {{http_request, atom_to_binary(Method), Uri, Version}, Rest}}; + {ok, Packet, Rest} -> + {ok, {Packet, Rest}}; + {more, Length} -> + {error, {more, Length}}; + {error, Reason} -> + {error, {http_error, Reason}} + end. diff --git a/examples/http_interactions/build/packages/grom.config_fingerprint b/examples/http_interactions/build/packages/grom.config_fingerprint new file mode 100644 index 0000000..baf76c7 --- /dev/null +++ b/examples/http_interactions/build/packages/grom.config_fingerprint @@ -0,0 +1 @@ +11119291992747727949 \ No newline at end of file diff --git a/examples/http_interactions/build/packages/houdini/README.md b/examples/http_interactions/build/packages/houdini/README.md new file mode 100644 index 0000000..d465bf2 --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/README.md @@ -0,0 +1,22 @@ +# houdini + +[![Package Version](https://img.shields.io/hexpm/v/houdini)](https://hex.pm/packages/houdini) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/houdini/) + +🪄 Fast HTML escaping + +```sh +gleam add houdini@1 +``` + +Use `houdini.escape` to escape any string to be safely used inside an HTML +document: + +```gleam +import houdini + +pub fn main() -> Nil { + assert houdini.escape("wibble & wobble") == "wibble & wobble" + assert houdini.escape("wibble > wobble") == "wibble > wobble" +} +``` diff --git a/examples/http_interactions/build/packages/houdini/gleam.toml b/examples/http_interactions/build/packages/houdini/gleam.toml new file mode 100644 index 0000000..b7545c0 --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/gleam.toml @@ -0,0 +1,12 @@ +name = "houdini" +version = "1.2.1" +description = "🪄 Fast HTML escaping" +licences = ["Apache-2.0"] +repository = { type = "github", user = "giacomocavalieri", repo = "houdini" } + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +gleamy_bench = ">= 0.6.0 and < 1.0.0" +simplifile = ">= 2.2.1 and < 3.0.0" +qcheck = ">= 1.0.0 and < 2.0.0" +gleam_stdlib = ">= 0.62.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/houdini/src/houdini.app.src b/examples/http_interactions/build/packages/houdini/src/houdini.app.src new file mode 100644 index 0000000..db8748d --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/src/houdini.app.src @@ -0,0 +1,8 @@ +{application, houdini, [ + {vsn, "1.2.1"}, + {applications, []}, + {description, "🪄 Fast HTML escaping"}, + {modules, [houdini, + houdini@@main]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/houdini/src/houdini.erl b/examples/http_interactions/build/packages/houdini/src/houdini.erl new file mode 100644 index 0000000..3fba5c1 --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/src/houdini.erl @@ -0,0 +1,139 @@ +-module(houdini). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/houdini.gleam"). +-export([escape/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/houdini.gleam", 83). +-spec do_escape_normal( + bitstring(), + integer(), + bitstring(), + bitstring(), + integer() +) -> bitstring(). +do_escape_normal(Bin, Skip, Original, Acc, Len) -> + case Bin of + <<"<"/utf8, Rest/bitstring>> -> + Acc@1 = <>, + do_escape(Rest, (Skip + Len) + 1, Original, Acc@1); + + <<">"/utf8, Rest@1/bitstring>> -> + Acc@2 = <>, + do_escape(Rest@1, (Skip + Len) + 1, Original, Acc@2); + + <<"&"/utf8, Rest@2/bitstring>> -> + Acc@3 = <>, + do_escape(Rest@2, (Skip + Len) + 1, Original, Acc@3); + + <<"\""/utf8, Rest@3/bitstring>> -> + Acc@4 = <>, + do_escape(Rest@3, (Skip + Len) + 1, Original, Acc@4); + + <<"'"/utf8, Rest@4/bitstring>> -> + Acc@5 = <>, + do_escape(Rest@4, (Skip + Len) + 1, Original, Acc@5); + + <<_, B, C, D, E, F, G, H, Rest@5/bitstring>> when ((((((((((B =/= 34) andalso (B =/= 38)) andalso (B =/= 39)) andalso (B =/= 60)) andalso (B =/= 62)) andalso (((((C =/= 34) andalso (C =/= 38)) andalso (C =/= 39)) andalso (C =/= 60)) andalso (C =/= 62))) andalso (((((D =/= 34) andalso (D =/= 38)) andalso (D =/= 39)) andalso (D =/= 60)) andalso (D =/= 62))) andalso (((((E =/= 34) andalso (E =/= 38)) andalso (E =/= 39)) andalso (E =/= 60)) andalso (E =/= 62))) andalso (((((F =/= 34) andalso (F =/= 38)) andalso (F =/= 39)) andalso (F =/= 60)) andalso (F =/= 62))) andalso (((((G =/= 34) andalso (G =/= 38)) andalso (G =/= 39)) andalso (G =/= 60)) andalso (G =/= 62))) andalso (((((H =/= 34) andalso (H =/= 38)) andalso (H =/= 39)) andalso (H =/= 60)) andalso (H =/= 62)) -> + do_escape_normal(Rest@5, Skip, Original, Acc, Len + 8); + + <<_, Rest@6/bitstring>> -> + do_escape_normal(Rest@6, Skip, Original, Acc, Len + 1); + + <<>> -> + case Skip of + 0 -> + Original; + + _ -> + <> + end; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"do_escape_normal: non byte aligned string, all strings should be byte aligned"/utf8>>, + file => <>, + module => <<"houdini"/utf8>>, + function => <<"do_escape_normal"/utf8>>, + line => 175}) + end. + +-file("src/houdini.gleam", 37). +-spec do_escape(bitstring(), integer(), bitstring(), bitstring()) -> bitstring(). +do_escape(Bin, Skip, Original, Acc) -> + case Bin of + <<"<"/utf8, Rest/bitstring>> -> + Acc@1 = <>, + do_escape(Rest, Skip + 1, Original, Acc@1); + + <<">"/utf8, Rest@1/bitstring>> -> + Acc@2 = <>, + do_escape(Rest@1, Skip + 1, Original, Acc@2); + + <<"&"/utf8, Rest@2/bitstring>> -> + Acc@3 = <>, + do_escape(Rest@2, Skip + 1, Original, Acc@3); + + <<"\""/utf8, Rest@3/bitstring>> -> + Acc@4 = <>, + do_escape(Rest@3, Skip + 1, Original, Acc@4); + + <<"'"/utf8, Rest@4/bitstring>> -> + Acc@5 = <>, + do_escape(Rest@4, Skip + 1, Original, Acc@5); + + <<_, Rest@5/bitstring>> -> + do_escape_normal(Rest@5, Skip, Original, Acc, 1); + + <<>> -> + Acc; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"do_escape: non byte aligned string, all strings should be byte aligned"/utf8>>, + file => <>, + module => <<"houdini"/utf8>>, + function => <<"do_escape"/utf8>>, + line => 79}) + end. + +-file("src/houdini.gleam", 17). +?DOC( + " Escapes a string to be safely used inside an HTML document by escaping\n" + " the following characters:\n" + " - `<` becomes `<`\n" + " - `>` becomes `>`\n" + " - `&` becomes `&`\n" + " - `\"` becomes `"`\n" + " - `'` becomes `'`.\n" + "\n" + " ## Examples\n" + "\n" + " ```gleam\n" + " assert escape(\"wibble & wobble\") == \"wibble & wobble\"\n" + " assert escape(\"wibble > wobble\") == \"wibble > wobble\"\n" + " ```\n" +). +-spec escape(binary()) -> binary(). +escape(String) -> + Bits = <>, + Result = do_escape(Bits, 0, Bits, <<>>), + gleam@function:identity(Result). diff --git a/examples/http_interactions/build/packages/houdini/src/houdini.ffi.mjs b/examples/http_interactions/build/packages/houdini/src/houdini.ffi.mjs new file mode 100644 index 0000000..f147e47 --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/src/houdini.ffi.mjs @@ -0,0 +1,18 @@ +export function escape(string) { + return string.replaceAll(/[><&"']/g, (replaced) => { + switch (replaced) { + case ">": + return ">"; + case "<": + return "<"; + case "'": + return "'"; + case "&": + return "&"; + case '"': + return """; + default: + return replaced; + } + }); +} diff --git a/examples/http_interactions/build/packages/houdini/src/houdini.gleam b/examples/http_interactions/build/packages/houdini/src/houdini.gleam new file mode 100644 index 0000000..b0ce8de --- /dev/null +++ b/examples/http_interactions/build/packages/houdini/src/houdini.gleam @@ -0,0 +1,187 @@ +/// Escapes a string to be safely used inside an HTML document by escaping +/// the following characters: +/// - `<` becomes `<` +/// - `>` becomes `>` +/// - `&` becomes `&` +/// - `"` becomes `"` +/// - `'` becomes `'`. +/// +/// ## Examples +/// +/// ```gleam +/// assert escape("wibble & wobble") == "wibble & wobble" +/// assert escape("wibble > wobble") == "wibble > wobble" +/// ``` +/// +@external(javascript, "./houdini.ffi.mjs", "escape") +pub fn escape(string: String) -> String { + // This version is highly optimised for the Erlang target, it treats Strings + // as BitArrays and slices them to share as much as possible. You can find + // more details in `do_escape`. + let bits = <> + let result = do_escape(bits, 0, bits, <<>>) + + // we know that the BitArray we build is definitely a valid string, so we can + // skip verifying that again as well as dealing with the `Result`. + unsafe_bit_array_to_string(result) +} + +// A possible way to escape chars would be to split the string into graphemes, +// traverse those one by one and accumulate them back into a string escaping +// ">", "<", etc. as we see them. +// However, we can be a lot more performant by working directly on the +// `BitArray` used to represent a Gleam UTF-8 String: instead of popping a +// grapheme at a time, we can work directly on BitArray slices: this has the big +// advantage of making sure we share as much as possible with the original +// string without having to build a new one from scratch. +fn do_escape( + bin: BitArray, + skip: Int, + original: BitArray, + acc: BitArray, +) -> BitArray { + case bin { + // If we find a char to escape we just advance the `skip` counter so that + // it will be ignored in the following slice, then we append the escaped + // version to the accumulator. + <<"<", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + 1, original, acc) + } + + <<">", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + 1, original, acc) + } + + <<"&", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + 1, original, acc) + } + + <<"\"", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + 1, original, acc) + } + + <<"'", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + 1, original, acc) + } + + // For any other byte that doesn't need to be escaped we go into an inner + // loop, consuming as much "non-escapable" chars as possible. + <<_char, rest:bits>> -> do_escape_normal(rest, skip, original, acc, 1) + + <<>> -> acc + + _ -> + panic as "do_escape: non byte aligned string, all strings should be byte aligned" + } +} + +fn do_escape_normal( + bin: BitArray, + skip: Int, + original: BitArray, + acc: BitArray, + len: Int, +) -> BitArray { + // Remember, if we're here it means we've found a char that doesn't need to be + // escaped, so what we want to do is advance the `len` counter until we reach + // a char that _does_ need to be escaped and take the slice going from + // `skip`, with size `len`. + // + // Imagine we're escaping this string: "abc> -> { + let acc = <> + do_escape(rest, skip + len + 1, original, acc) + } + + <<">", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + len + 1, original, acc) + } + + <<"&", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + len + 1, original, acc) + } + + <<"\"", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + len + 1, original, acc) + } + + <<"'", rest:bits>> -> { + let acc = <> + do_escape(rest, skip + len + 1, original, acc) + } + + // Otherwise we know that the first byte doesn't need any escape. The easy + // thing to do would be to just advance by that one byte and keep going over + // over the string. As you might notice here we're doing something a bit + // more involved: we look at the following 7 bytes, and if none of those + // needs escaping, then we skip this whole chunk of bytes entirely. + // + // This doesn't change the behaviour of the program, but it can make it a + // whole load faster to go over the entire string. Especially if there's + // fewer characters that need escaping! + // + // This idea comes from the amazing talk "Engineering json - Achieving Top + // Performance on the BEAM" by Michał Muskala at Code BEAM Europe 2024: + // https://www.youtube.com/watch?v=Z0swkSXAPBE + <<_, b, c, d, e, f, g, h, rest:bits>> + if { b != 34 && b != 38 && b != 39 && b != 60 && b != 62 } + && { c != 34 && c != 38 && c != 39 && c != 60 && c != 62 } + && { d != 34 && d != 38 && d != 39 && d != 60 && d != 62 } + && { e != 34 && e != 38 && e != 39 && e != 60 && e != 62 } + && { f != 34 && f != 38 && f != 39 && f != 60 && f != 62 } + && { g != 34 && g != 38 && g != 39 && g != 60 && g != 62 } + && { h != 34 && h != 38 && h != 39 && h != 60 && h != 62 } + -> do_escape_normal(rest, skip, original, acc, len + 8) + + // However, if any of the following bytes needs escaping, we skip over just + // the first byte! + <<_, rest:bits>> -> do_escape_normal(rest, skip, original, acc, len + 1) + + <<>> -> + // We start from the start of the bit array and have consumed everything + // without finding a char that is not valid. This means that the entire + // string doesn't need any escaping, we can just return it as is! + case skip { + 0 -> original + _ -> <> + } + + _ -> + panic as "do_escape_normal: non byte aligned string, all strings should be byte aligned" + } +} + +@external(erlang, "gleam@function", "identity") +fn unsafe_bit_array_to_string(_bit_array: BitArray) -> String { + panic as "usafe_bit_array_to_string: this shouldn't be needed on the JavaScript target" +} + +@external(erlang, "binary", "part") +fn slice(_bit_array: BitArray, _from: Int, _size: Int) -> BitArray { + panic as "slice: this shouldn't be needed on the JavaScript target" +} diff --git a/examples/http_interactions/build/packages/hpack_erl/LICENSE b/examples/http_interactions/build/packages/hpack_erl/LICENSE new file mode 100644 index 0000000..3dd3ce7 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Joe DeVivo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/examples/http_interactions/build/packages/hpack_erl/README.md b/examples/http_interactions/build/packages/hpack_erl/README.md new file mode 100644 index 0000000..fd75ec2 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/README.md @@ -0,0 +1,111 @@ +# hpack + +HPACK Implementation for Erlang + +[HPACK RFC-7541](http://tools.ietf.org/html/rfc7541) + +This implementation was designed for use by +[Chatterbox](http://github.com/joedevivo/chatterbox), but could be +used by any HTTP/2 implementation (or, if something other than HTTP/2 +has need for HPACK, this would work as well). + +## Why Separate? + +* Use in other projects +* A separate RFC seemed like a really clear abstraction + +## What's Covered + +* Compression contexts ([RFC-7541 Section 2.2](http://tools.ietf.org/html/rfc7541#section-2.2)) +* Dynamic table management ([RFC-7541 Section 4](http://tools.ietf.org/html/rfc7541#section-4)) +* Primitive type representations ([RFC-7541 Section 5](http://tools.ietf.org/html/rfc7541#section-5)) +* Binary format ([RFC-7541 Section 6](http://tools.ietf.org/html/rfc7541#section-6)) + * Indexed Header Field Representation + * Literal Header Field Representation + * Dynamic Table Size Update + +## What's not covered + +### HTTP/2 Frames + +An HTTP/2 implementation should be responsible for anything you need +to do in order to read a compressed header block from various +PUSH_PROMISE, HEADERS, and/or CONTINUATION frames. Once you have a +complete block, you can use this library to turn it into something you +can use for fulfilling HTTP requests + +## API + +### Creating Contexts + +`hpack` provides a single type that represents both encoding and decoding +contexts. A new context can be created with `hpack:new_context/0` or +`hpack:new_context/1` (passing the size for the `HEADER_TABLE_SIZE` setting). + +### Changing table size + +If HTTP/2 settings get renegotiated, you can pass that information +along by calling `hpack:new_max_table_size/2`, like this: + +``` erlang +NewContext = hpack:new_max_table_size(NewSize, OldContext). +``` + +### Decoding Headers + +Decode a headers binary with `hpack:decode/2`. It's your job to +assemble the binary if it's coming from multiple HTTP/2 frames. + +``` erlang +{ok, {Headers, NewContext}} = hpack:decode(Binary, OldContext). +``` + +`Headers` is a list of headers, where each header is a `{binary(), binary()}` +tuple. + +### Encoding Headers + +Encoding headers works the same way, only a `[{binary(), binary()}]` +goes in, and a `binary()` comes out. + +``` erlang +{Bin, NewContext} = hpack:encode(Headers, OldContext), +``` + +### Soup to nuts + +Here's how to do the whole thing! + +``` erlang + +DecodeContext1 = hpack:new_context(), %% Server context +EncodeContext1 = hpack:new_context(), %% Client context + +ClientRequestHeaders = [ + {<<":method">>, <<"GET">>}, + {<<":path">>, <<"/">>}, + {<<"X-Whatev">>, <<"commands">>} + ], + +%% Client operation +{RequestHeadersBin, EncodeContext2} = hpack:encode( + ClientRequestHeaders, + EncodeContext1), + +%% Server operation, after receiving RequestHeadersBin +{ok, {ServerRequestHeaders, DecodeContext2}} = hpack:decode( + RequestHeadersBin, + DecodeContext1), + +%% Note the following truths: +ClientRequestHeaders = ServerRequestHeaders, +EncodeContext1 = DecodeContext1, +EncodeContext2 = DecodeContext2. + +``` + +The whole reason HPACK works is that the client and server both keep +their contexts in sync with each other. + +*Note*: I used the terms `client` and `server` here, but it could as +easily be `sender` and `receiver` if you're a proxy. diff --git a/examples/http_interactions/build/packages/hpack_erl/rebar.config b/examples/http_interactions/build/packages/hpack_erl/rebar.config new file mode 100644 index 0000000..8c929fd --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/rebar.config @@ -0,0 +1,21 @@ +%% -*- mode: erlang -*- +%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +{erl_opts, [ + warnings_as_errors, + debug_info +]}. + +{deps, []}. + +{cover_enabled, true}. + +{ct_opts, [{verbose,true}]}. + +{profiles, [ + {test, [{erl_opts, [ + {i,["include"]} + ]} + ] + }] +}. diff --git a/examples/http_interactions/build/packages/hpack_erl/rebar.lock b/examples/http_interactions/build/packages/hpack_erl/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/examples/http_interactions/build/packages/hpack_erl/src/hpack.app.src b/examples/http_interactions/build/packages/hpack_erl/src/hpack.app.src new file mode 100644 index 0000000..fddc418 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/hpack.app.src @@ -0,0 +1,10 @@ +{application,hpack, + [{description,"HPACK Implementation"}, + {vsn,"0.3.0"}, + {registered,[]}, + {applications,[kernel,stdlib]}, + {env,[]}, + {pkg_name,hpack_erl}, + {maintainers,["Joe DeVivo"]}, + {licenses,["MIT"]}, + {links,[{"Github","https://github.com/joedevivo/hpack"}]}]}. diff --git a/examples/http_interactions/build/packages/hpack_erl/src/hpack.erl b/examples/http_interactions/build/packages/hpack_erl/src/hpack.erl new file mode 100644 index 0000000..333753c --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/hpack.erl @@ -0,0 +1,282 @@ +%% @doc +%% The `hpack' module provides functions for working with HPACK as described in +%% RFC 7541. + +%% @reference RFC 7541 + +-module(hpack). + +%% API Exports +-export([ + new_context/0, + new_context/1, + decode/2, + encode/2, + new_max_table_size/2, + all_fields_indexed/2 + ]). + +%% Datatypes for the real world: +-record(hpack_context, + { + dynamic_table = hpack_index:new(), + connection_max_table_size = 4096 :: non_neg_integer() + }). +-type context() :: #hpack_context{}. + +-export_type([context/0]). + +-type header_name() :: binary(). +-type header_value() :: binary(). +-type header() :: {header_name(), header_value()}. +-type headers() :: [header()]. + +-export_type([ + header_name/0, + header_value/0, + header/0, + headers/0 + ]). + +%% @equiv new_context(4096) +-spec new_context() -> context(). +new_context() -> #hpack_context{}. + +%% @doc +%% Returns a new HPACK context with the given `MaxTableSize' as the max table size. +-spec new_context(non_neg_integer()) -> context(). +new_context(MaxTableSize) -> + #hpack_context{ + connection_max_table_size=MaxTableSize + }. + +%% @doc +%% Updates the max table size of the given HPACK context (`Context') to the given +%% `NewSize'. +%% +%% Useful when HTTP/2 settings are renegotiated. +-spec new_max_table_size(non_neg_integer(), context()) + -> context(). +new_max_table_size(NewSize, + #hpack_context{ + dynamic_table=T, + connection_max_table_size=OldSize + }=Context) -> + NewT = case OldSize > NewSize of + true -> + hpack_index:resize(NewSize, T); + _ -> + T + end, + Context#hpack_context{ + dynamic_table=NewT, + connection_max_table_size=NewSize + }. + +%% @doc +%% Encodes the given `Headers' using the given `Context'. +%% +%% When successful, returns a `{ok, {EncodedHeaders, NewContext}}' tuple where +%% `EncodedHeaders' is a binary representing the encoded headers and `NewContext' +%% is the new HPACK context. +%% +%% For example: +%% ``` +%% Headers = [{<<":method">>, <<"GET">>}], +%% {ok, {EncodedHeaders, NewContext}} = hpack:encode(Headers, hpack:new_context()). +%% ''' +-spec encode(headers(), + context()) + -> {ok, {binary(), context()}} + | {error, term()}. +encode(Headers, Context) -> + encode(Headers, <<>>, Context). + +%% @doc +%% Decodes the given binary into a list of headers using the given HPACK +%% context. +%% +%% If successful, returns a `{ok, {Headers, NewContext}}' tuple where `Headers' +%% are the decoded headers and `NewContext' is the new HPACK context. +%% +%% For example: +%% ``` +%% {Headers, NewContext} = hpack:decode(Binary, OldContext). +%% ''' +-spec decode(binary(), context()) -> + {ok, {headers(), context()}} + | {error, compression_error} + | {error, {compression_error, {bad_header_packet, binary()}}}. +decode(Bin, Context) -> + decode(Bin, [], Context). + +%% Private Functions + +-spec decode(binary(), headers(), context()) + -> + {ok, {headers(), context()}} + | {error, compression_error} + | {error, {compression_error, {bad_header_packet, binary()}}}. +%% We're done decoding, return headers +decode(<<>>, HeadersAcc, C) -> + {ok, {HeadersAcc, C}}; +%% First bit is '1', so it's an 'Indexed Header Feild' +%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.1 +decode(<<2#1:1,_/bits>>=B, HeaderAcc, Context) -> + decode_indexed_header(B, HeaderAcc, Context); +%% First two bits are '01' so it's a 'Literal Header Field with Incremental Indexing' +%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.1 +decode(<<2#01:2,_/bits>>=B, HeaderAcc, Context) -> + decode_literal_header_with_indexing(B, HeaderAcc, Context); + +%% First four bits are '0000' so it's a 'Literal Header Field without Indexing' +%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.2 +decode(<<2#0000:4,_/bits>>=B, HeaderAcc, Context) -> + decode_literal_header_without_indexing(B, HeaderAcc, Context); + +%% First four bits are '0001' so it's a 'Literal Header Field never Indexed' +%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.3 +decode(<<2#0001:4,_/bits>>=B, HeaderAcc, Context) -> + decode_literal_header_never_indexed(B, HeaderAcc, Context); + +%% First three bits are '001' so it's a 'Dynamic Table Size Update' +%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.3 +decode(<<2#001:3,_/bits>>=B, HeaderAcc, Context) -> + decode_dynamic_table_size_update(B, HeaderAcc, Context); + +%% Oops! +decode(<>, _HeaderAcc, _Context) -> + {error, {compression_error, {bad_header_packet, B}}}. + +decode_indexed_header(<<2#1:1,B1/bits>>, + Acc, + Context = #hpack_context{dynamic_table=T}) -> + {Index, B2} = hpack_integer:decode(B1, 7), + decode(B2, Acc ++ [hpack_index:lookup(Index, T)], Context). + +%% The case where the field isn't indexed yet, but should be. +decode_literal_header_with_indexing(<<2#01:2,2#000000:6>>, _Acc, _Context) -> + {error, compression_error}; +decode_literal_header_with_indexing(<<2#01:2,2#000000:6,B1/bits>>, Acc, + #hpack_context{ + dynamic_table=T + }=Context) -> + {Str, B2} = hpack_string:decode(B1), + {Value, B3} = hpack_string:decode(B2), + decode(B3, + Acc ++ [{Str, Value}], + Context#hpack_context{dynamic_table=hpack_index:add(Str, Value, T)}); +%% This is the case when the index is greater than 0, 0 being not yet +%% indexed +decode_literal_header_with_indexing(<<2#01:2,B1/bits>>, Acc, + Context = #hpack_context{dynamic_table=T}) -> + {Index, Rem} = hpack_integer:decode(B1,6), + {Str, B2} = hpack_string:decode(Rem), + {Name,_} = + case hpack_index:lookup(Index, T) of + undefined -> + throw(undefined); + {N, V} -> + {N, V} + end, + decode(B2, + Acc ++ [{Name, Str}], + Context#hpack_context{dynamic_table=hpack_index:add(Name, Str, T)}). + +decode_literal_header_without_indexing(<<2#0000:4,2#0000:4,B1/bits>>, Acc, + Context) -> + {Str, B2} = hpack_string:decode(B1), + {Value, B3} = hpack_string:decode(B2), + decode(B3, Acc ++ [{Str, Value}], Context); +decode_literal_header_without_indexing(<<2#0000:4,B1/bits>>, Acc, + Context = #hpack_context{dynamic_table=T}) -> + {Index, Rem} = hpack_integer:decode(B1,4), + {Str, B2} = hpack_string:decode(Rem), + {Name,_}= hpack_index:lookup(Index, T), + decode(B2, Acc ++ [{Name, Str}], Context). + +decode_literal_header_never_indexed(<<2#0001:4,2#0000:4,B1/bits>>, Acc, + Context) -> + {Str, B2} = hpack_string:decode(B1), + {Value, B3} = hpack_string:decode(B2), + decode(B3, Acc ++ [{Str, Value}], Context); +decode_literal_header_never_indexed(<<2#0001:4,B1/bits>>, Acc, + Context = #hpack_context{dynamic_table=T}) -> + {Index, Rem} = hpack_integer:decode(B1,4), + {Str, B2} = hpack_string:decode(Rem), + {Name,_}= hpack_index:lookup(Index, T), + decode(B2, Acc ++ [{Name, Str}], Context). + +decode_dynamic_table_size_update( + <<2#001:3,Bin/bits>>, []=Acc, + #hpack_context{ + dynamic_table=T, + connection_max_table_size=ConnMaxSize + }=Context + ) -> + {NewSize, Rem} = hpack_integer:decode(Bin,5), + case ConnMaxSize >= NewSize of + true -> + decode(Rem, + Acc, + Context#hpack_context{ + dynamic_table=hpack_index:resize(NewSize, T) + }); + _ -> + {error, compression_error} + end. + +-spec encode(headers(), + binary(), + context()) -> + {ok, {binary(), context()}} + | {error, term()}. +encode([], Acc, Context) -> + {ok, {Acc, Context}}; +encode([{HeaderName, HeaderValue}|Tail], B, Context = #hpack_context{dynamic_table=T}) -> + {BinToAdd, NewContext} = case hpack_index:match({HeaderName, HeaderValue}, T) of + {indexed, I} -> + {encode_indexed(I), Context}; + {literal_with_indexing, I} -> + {encode_literal_indexed(I, HeaderValue), + Context#hpack_context{dynamic_table=hpack_index:add(HeaderName, HeaderValue, T)}}; + {literal_wo_indexing, _X} -> + {encode_literal_wo_index(HeaderName, HeaderValue), + Context#hpack_context{dynamic_table=hpack_index:add(HeaderName, HeaderValue, T)}} + end, + NewB = <>, + encode(Tail, NewB, NewContext). + +encode_indexed(I) when I < 63 -> + <<2#1:1,I:7>>; +encode_indexed(I) -> + Encoded = hpack_integer:encode(I, 7), + <<2#1:1, Encoded/bits>>. + +encode_literal_indexed(I, Value) when I < 63 -> + BinToAdd = encode_literal(Value), + <<2#01:2,I:6,BinToAdd/binary>>; +encode_literal_indexed(I, Value) -> + Index = hpack_integer:encode(I, 6), + BinToAdd = encode_literal(Value), + <<2#01:2,Index/bits,BinToAdd/binary>>. + +encode_literal(Value) -> + L = hpack_integer:encode(size(Value),7), + <<2#0:1,L/bits,Value/binary>>. + +encode_literal_wo_index(Name, Value) -> + EncName = encode_literal(Name), + EncValue = encode_literal(Value), + <<2#01000000,EncName/binary,EncValue/binary>>. + +all_fields_indexed(Headers, #hpack_context{dynamic_table=T}) -> + lists:all(fun(Header) -> + case hpack_index:match(Header, T) of + {indexed, _} -> + true; + _ -> + false + end + end, Headers). + diff --git a/examples/http_interactions/build/packages/hpack_erl/src/hpack_index.erl b/examples/http_interactions/build/packages/hpack_erl/src/hpack_index.erl new file mode 100644 index 0000000..0f547a7 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/hpack_index.erl @@ -0,0 +1,292 @@ +%% @private + +-module(hpack_index). + +-export([ + new/0, + new/1, + add/3, + lookup/2, + resize/2, + table_size/1, + max_table_size/1, + match/2 +]). + +-type header_name() :: binary(). +-type header_value():: binary(). +-type header() :: {header_name(), header_value()}. +-export_type([header/0]). + +-define(DYNAMIC_TABLE_MIN_INDEX, 62). + +-record(dynamic_table, { + table = [] :: [{pos_integer(), header_name(), header_value()}], + %% max_size is the size allowed by the protocol + max_size = 4096 :: non_neg_integer(), + %% size is how big this thing really is + size = 0 :: non_neg_integer() + }). +-type dynamic_table() :: #dynamic_table{}. + +-spec new() -> dynamic_table(). +new() -> #dynamic_table{}. + +-spec new(pos_integer()) -> dynamic_table(). +new(S) -> #dynamic_table{max_size=S}. + +-spec entry_size({pos_integer(), header_name(), header_value()}) -> pos_integer(). +entry_size({_, Name, Value}) -> + entry_size(Name, Value). + +-spec entry_size(header_name(), header_value()) -> pos_integer(). +entry_size(Name, Value) -> + 32 + size(Name) + size(Value). + +-spec add(header_name(), header_value(), dynamic_table()) -> dynamic_table(). +add(Name, Value, DT) -> + add(Name, Value, entry_size(Name, Value), DT). + +-spec add(header_name(), header_value(), pos_integer(), dynamic_table()) -> dynamic_table(). +add(Name, Value, EntrySize, DT=#dynamic_table{table=T, size=S, max_size=MS}) + when EntrySize + S =< MS -> + TPlus = lists:map(fun({N,H,V}) -> {N+1, H,V} end, T), + DT#dynamic_table{size=S+EntrySize, table=[{?DYNAMIC_TABLE_MIN_INDEX, Name, Value}|TPlus]}; +add(_Name, _Value, EntrySize, DT=#dynamic_table{max_size=MS, table=[]}) + when EntrySize > MS -> + DT; +add(Name, Value, EntrySize, DT=#dynamic_table{size=S, max_size=MS}) + when EntrySize + S > MS -> + add(Name, Value, EntrySize, + droplast(DT)). + +-spec droplast(dynamic_table()) -> dynamic_table(). +droplast(DT=#dynamic_table{table=[]}) -> DT; +droplast(DT=#dynamic_table{table=T, size=S}) -> + [Last|NewTR] = lists:reverse(T), + DT#dynamic_table{size=S-entry_size(Last), table=lists:reverse(NewTR)}. + + +%% TODO: There's a problem here, where if you shink the dynamic table +%% size, you can never increase it. + +%% resize/2 sets the max_table_size, and truncates the table if it +%% needs to be smaller to fit in that maximum +-spec resize(pos_integer(), dynamic_table()) -> dynamic_table(). +resize(NewSize, DT=#dynamic_table{size=S}) + when NewSize >= S -> + DT#dynamic_table{max_size=NewSize}; +resize(NewSize, DT) -> + resize(NewSize, droplast(DT)). + +-spec table_size(dynamic_table()) -> pos_integer(). +table_size(#dynamic_table{size=S}) -> S. + +-spec max_table_size(dynamic_table()) -> pos_integer(). +max_table_size(#dynamic_table{max_size=S}) -> S. + +-spec lookup(pos_integer(), dynamic_table()) -> header() | undefined. +lookup(1 , _) -> {<<":authority">>, <<>>}; +lookup(2 , _) -> {<<":method">>, <<"GET">>}; +lookup(3 , _) -> {<<":method">>, <<"POST">>}; +lookup(4 , _) -> {<<":path">>, <<"/">>}; +lookup(5 , _) -> {<<":path">>, <<"/index.html">>}; +lookup(6 , _) -> {<<":scheme">>, <<"http">>}; +lookup(7 , _) -> {<<":scheme">>, <<"https">>}; +lookup(8 , _) -> {<<":status">>, <<"200">>}; +lookup(9 , _) -> {<<":status">>, <<"204">>}; +lookup(10, _) -> {<<":status">>, <<"206">>}; +lookup(11, _) -> {<<":status">>, <<"304">>}; +lookup(12, _) -> {<<":status">>, <<"400">>}; +lookup(13, _) -> {<<":status">>, <<"404">>}; +lookup(14, _) -> {<<":status">>, <<"500">>}; +lookup(15, _) -> {<<"accept-charset">>, <<>>}; +lookup(16, _) -> {<<"accept-encoding">>, <<"gzip, deflate">>}; +lookup(17, _) -> {<<"accept-language">>, <<>>}; +lookup(18, _) -> {<<"accept-ranges">>, <<>>}; +lookup(19, _) -> {<<"accept">>, <<>>}; +lookup(20, _) -> {<<"access-control-allow-origin">>, <<>>}; +lookup(21, _) -> {<<"age">>, <<>>}; +lookup(22, _) -> {<<"allow">>, <<>>}; +lookup(23, _) -> {<<"authorization">>, <<>>}; +lookup(24, _) -> {<<"cache-control">>, <<>>}; +lookup(25, _) -> {<<"content-disposition">>, <<>>}; +lookup(26, _) -> {<<"content-encoding">>, <<>>}; +lookup(27, _) -> {<<"content-language">>, <<>>}; +lookup(28, _) -> {<<"content-length">>, <<>>}; +lookup(29, _) -> {<<"content-location">>, <<>>}; +lookup(30, _) -> {<<"content-range">>, <<>>}; +lookup(31, _) -> {<<"content-type">>, <<>>}; +lookup(32, _) -> {<<"cookie">>, <<>>}; +lookup(33, _) -> {<<"date">>, <<>>}; +lookup(34, _) -> {<<"etag">>, <<>>}; +lookup(35, _) -> {<<"expect">>, <<>>}; +lookup(36, _) -> {<<"expires">>, <<>>}; +lookup(37, _) -> {<<"from">>, <<>>}; +lookup(38, _) -> {<<"host">>, <<>>}; +lookup(39, _) -> {<<"if-match">>, <<>>}; +lookup(40, _) -> {<<"if-modified-since">>, <<>>}; +lookup(41, _) -> {<<"if-none-match">>, <<>>}; +lookup(42, _) -> {<<"if-range">>, <<>>}; +lookup(43, _) -> {<<"if-unmodified-since">>, <<>>}; +lookup(44, _) -> {<<"last-modified">>, <<>>}; +lookup(45, _) -> {<<"link">>, <<>>}; +lookup(46, _) -> {<<"location">>, <<>>}; +lookup(47, _) -> {<<"max-forwards">>, <<>>}; +lookup(48, _) -> {<<"proxy-authenticate">>, <<>>}; +lookup(49, _) -> {<<"proxy-authorization">>, <<>>}; +lookup(50, _) -> {<<"range">>, <<>>}; +lookup(51, _) -> {<<"referer">>, <<>>}; +lookup(52, _) -> {<<"refresh">>, <<>>}; +lookup(53, _) -> {<<"retry-after">>, <<>>}; +lookup(54, _) -> {<<"server">>, <<>>}; +lookup(55, _) -> {<<"set-cookie">>, <<>>}; +lookup(56, _) -> {<<"strict-transport-security">>, <<>>}; +lookup(57, _) -> {<<"transfer-encoding">>, <<>>}; +lookup(58, _) -> {<<"user-agent">>, <<>>}; +lookup(59, _) -> {<<"vary">>, <<>>}; +lookup(60, _) -> {<<"via">>, <<>>}; +lookup(61, _) -> {<<"www-authenticate">>, <<>>}; +lookup(Idx, #dynamic_table{table=T}) -> + case lists:keyfind(Idx, 1, T) of + false -> + undefined; + {Idx, Name, V} -> + {Name, V} + end. + +-spec match(header(), dynamic_table()) -> {atom(), pos_integer()|undefined}. +match({<<":authority">>, <<>>}, _) -> {indexed, 1 }; +match({<<":method">>, <<"GET">>}, _) -> {indexed, 2 }; +match({<<":method">>, <<"POST">>}, _) -> {indexed, 3 }; +match({<<":path">>, <<"/">>}, _) -> {indexed, 4 }; +match({<<":path">>, <<"/index.html">>}, _) -> {indexed, 5 }; +match({<<":scheme">>, <<"http">>}, _) -> {indexed, 6 }; +match({<<":scheme">>, <<"https">>}, _) -> {indexed, 7 }; +match({<<":status">>, <<"200">>}, _) -> {indexed, 8 }; +match({<<":status">>, <<"204">>}, _) -> {indexed, 9 }; +match({<<":status">>, <<"206">>}, _) -> {indexed, 10}; +match({<<":status">>, <<"304">>}, _) -> {indexed, 11}; +match({<<":status">>, <<"400">>}, _) -> {indexed, 12}; +match({<<":status">>, <<"404">>}, _) -> {indexed, 13}; +match({<<":status">>, <<"500">>}, _) -> {indexed, 14}; +match({<<"accept-charset">>, <<>>}, _) -> {indexed, 15}; +match({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> {indexed, 16}; +match({<<"accept-language">>, <<>>}, _) -> {indexed, 17}; +match({<<"accept-ranges">>, <<>>}, _) -> {indexed, 18}; +match({<<"accept">>, <<>>}, _) -> {indexed, 19}; +match({<<"access-control-allow-origin">>, <<>>}, _) -> {indexed, 20}; +match({<<"age">>, <<>>}, _) -> {indexed, 21}; +match({<<"allow">>, <<>>}, _) -> {indexed, 22}; +match({<<"authorization">>, <<>>}, _) -> {indexed, 23}; +match({<<"cache-control">>, <<>>}, _) -> {indexed, 24}; +match({<<"content-disposition">>, <<>>}, _) -> {indexed, 25}; +match({<<"content-encoding">>, <<>>}, _) -> {indexed, 26}; +match({<<"content-language">>, <<>>}, _) -> {indexed, 27}; +match({<<"content-length">>, <<>>}, _) -> {indexed, 28}; +match({<<"content-location">>, <<>>}, _) -> {indexed, 29}; +match({<<"content-range">>, <<>>}, _) -> {indexed, 30}; +match({<<"content-type">>, <<>>}, _) -> {indexed, 31}; +match({<<"cookie">>, <<>>}, _) -> {indexed, 32}; +match({<<"date">>, <<>>}, _) -> {indexed, 33}; +match({<<"etag">>, <<>>}, _) -> {indexed, 34}; +match({<<"expect">>, <<>>}, _) -> {indexed, 35}; +match({<<"expires">>, <<>>}, _) -> {indexed, 36}; +match({<<"from">>, <<>>}, _) -> {indexed, 37}; +match({<<"host">>, <<>>}, _) -> {indexed, 38}; +match({<<"if-match">>, <<>>}, _) -> {indexed, 39}; +match({<<"if-modified-since">>, <<>>}, _) -> {indexed, 40}; +match({<<"if-none-match">>, <<>>}, _) -> {indexed, 41}; +match({<<"if-range">>, <<>>}, _) -> {indexed, 42}; +match({<<"if-unmodified-since">>, <<>>}, _) -> {indexed, 43}; +match({<<"last-modified">>, <<>>}, _) -> {indexed, 44}; +match({<<"link">>, <<>>}, _) -> {indexed, 45}; +match({<<"location">>, <<>>}, _) -> {indexed, 46}; +match({<<"max-forwards">>, <<>>}, _) -> {indexed, 47}; +match({<<"proxy-authenticate">>, <<>>}, _) -> {indexed, 48}; +match({<<"proxy-authorization">>, <<>>}, _) -> {indexed, 49}; +match({<<"range">>, <<>>}, _) -> {indexed, 50}; +match({<<"referer">>, <<>>}, _) -> {indexed, 51}; +match({<<"refresh">>, <<>>}, _) -> {indexed, 52}; +match({<<"retry-after">>, <<>>}, _) -> {indexed, 53}; +match({<<"server">>, <<>>}, _) -> {indexed, 54}; +match({<<"set-cookie">>, <<>>}, _) -> {indexed, 55}; +match({<<"strict-transport-security">>, <<>>}, _) -> {indexed, 56}; +match({<<"transfer-encoding">>, <<>>}, _) -> {indexed, 57}; +match({<<"user-agent">>, <<>>}, _) -> {indexed, 58}; +match({<<"vary">>, <<>>}, _) -> {indexed, 59}; +match({<<"via">>, <<>>}, _) -> {indexed, 60}; +match({<<"www-authenticate">>, <<>>}, _) -> {indexed, 61}; +match({Name, Value}, #dynamic_table{table=T}) -> + %% If there's a N/V match in the DT, return {indexed, Int} + %% If there's a N match, make sure there's not also a static_match + %% if so, use it, otherwise use the first element in the first match + %% If not + Found = lists:filter(fun({_,N,_}) -> N =:= Name end, T), + ExactFound = lists:filter(fun({_,_,V}) -> V =:= Value end, Found), + + case {ExactFound, Found, static_match(Name)} of + {[{I,Name,Value}|_], _, _} -> + {indexed, I}; + {[], [{I,Name,_}|_], undefined} -> + {literal_with_indexing, I}; + {[], _, I} when is_integer(I) -> + {literal_with_indexing, I}; + {[], [], undefined} -> + {literal_wo_indexing, undefined} + end. + +-spec static_match(header_name()) -> pos_integer() | undefined. +static_match(<<":authority">>) -> 1 ; +static_match(<<":method">>) -> 2 ; +static_match(<<":path">>) -> 4 ; +static_match(<<":scheme">>) -> 6 ; +static_match(<<":status">>) -> 8 ; +static_match(<<"accept-charset">>) -> 15; +static_match(<<"accept-encoding">>) -> 16; +static_match(<<"accept-language">>) -> 17; +static_match(<<"accept-ranges">>) -> 18; +static_match(<<"accept">>) -> 19; +static_match(<<"access-control-allow-origin">>) -> 20; +static_match(<<"age">>) -> 21; +static_match(<<"allow">>) -> 22; +static_match(<<"authorization">>) -> 23; +static_match(<<"cache-control">>) -> 24; +static_match(<<"content-disposition">>) -> 25; +static_match(<<"content-encoding">>) -> 26; +static_match(<<"content-language">>) -> 27; +static_match(<<"content-length">>) -> 28; +static_match(<<"content-location">>) -> 29; +static_match(<<"content-range">>) -> 30; +static_match(<<"content-type">>) -> 31; +static_match(<<"cookie">>) -> 32; +static_match(<<"date">>) -> 33; +static_match(<<"etag">>) -> 34; +static_match(<<"expect">>) -> 35; +static_match(<<"expires">>) -> 36; +static_match(<<"from">>) -> 37; +static_match(<<"host">>) -> 38; +static_match(<<"if-match">>) -> 39; +static_match(<<"if-modified-since">>) -> 40; +static_match(<<"if-none-match">>) -> 41; +static_match(<<"if-range">>) -> 42; +static_match(<<"if-unmodified-since">>) -> 43; +static_match(<<"last-modified">>) -> 44; +static_match(<<"link">>) -> 45; +static_match(<<"location">>) -> 46; +static_match(<<"max-forwards">>) -> 47; +static_match(<<"proxy-authenticate">>) -> 48; +static_match(<<"proxy-authorization">>) -> 49; +static_match(<<"range">>) -> 50; +static_match(<<"referer">>) -> 51; +static_match(<<"refresh">>) -> 52; +static_match(<<"retry-after">>) -> 53; +static_match(<<"server">>) -> 54; +static_match(<<"set-cookie">>) -> 55; +static_match(<<"strict-transport-security">>) -> 56; +static_match(<<"transfer-encoding">>) -> 57; +static_match(<<"user-agent">>) -> 58; +static_match(<<"vary">>) -> 59; +static_match(<<"via">>) -> 60; +static_match(<<"www-authenticate">>) -> 61; +static_match(_) -> undefined. diff --git a/examples/http_interactions/build/packages/hpack_erl/src/hpack_integer.erl b/examples/http_interactions/build/packages/hpack_erl/src/hpack_integer.erl new file mode 100644 index 0000000..b0a1045 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/hpack_integer.erl @@ -0,0 +1,106 @@ +%% @private + +-module(hpack_integer). + +%% @doc For encoding/decoding HPACK's primitive integer representation +%% as defined http://tools.ietf.org/html/rfc7541#section-5.1 + +-export([ + decode/2, + encode/2 + ]). + +%% To save every bit on the wire, an integer can fill a partial octet, +%% and usually does in HPACK. If that integer is small enough to fit, +%% then no further work is required. + +%% In the octet below, the first three bits were used for something +%% else. Doesn't matter what. What matters is that we have 5 bits left +%% for the integer, which means if it's less than 2^5-1, we're all +%% good! + +%% 0 1 2 3 4 5 6 7 +%% +---+---+---+---+---+---+---+---+ +%% | ? | ? | ? | Value | +%% +---+---+---+-------------------+ + +%% So what if it's greater than 2^5-1? + +%% 0 1 2 3 4 5 6 7 +%% +---+---+---+---+---+---+---+---+ +%% | ? | ? | ? | 1 1 1 1 1 | +%% +---+---+---+-------------------+ +%% | 1 | Value-(2^5-1) LSB | +%% +---+---------------------------+ +%% ... +%% +---+---------------------------+ +%% | 0 | Value-(2^5-1) MSB | +%% +---+---------------------------+ + +%% Then we don't want to waste those first 5 bits, so they're all set +%% to 1, and we know the integer is greater than 2^5-1. How much +%% greater? That's the question we answer with the following octets. + +%% "Value-(2^5-1)" is "how much greater?". Let's call it I. + +-spec decode(binary(), pos_integer()) -> {non_neg_integer(), binary()}. +decode(<<1:1,Bin/bits>>, 1) -> + prefix_plus(1, decode(Bin, 0, 0)); +decode(<<3:2,Bin/bits>>, 2) -> + prefix_plus(3, decode(Bin, 0, 0)); +decode(<<7:3,Bin/bits>>, 3) -> + prefix_plus(7, decode(Bin, 0, 0)); +decode(<<15:4,Bin/bits>>, 4) -> + prefix_plus(15, decode(Bin, 0, 0)); +decode(<<31:5,Bin/bits>>, 5) -> + prefix_plus(31, decode(Bin, 0, 0)); +decode(<<63:6,Bin/bits>>, 6) -> + prefix_plus(63, decode(Bin, 0, 0)); +decode(<<127:7,Bin/bits>>, 7) -> + prefix_plus(127, decode(Bin, 0, 0)); +decode(<<255:8,Bin/bits>>, 8) -> + prefix_plus(255, decode(Bin, 0, 0)); +%% This clause means we have something small enough to fit in prefix +decode(Bin, Prefix) -> + <> = Bin, + {Value, Rem}. + +-spec decode(binary(), non_neg_integer(), non_neg_integer()) -> {non_neg_integer(), binary()}. +decode(<<1:1,Int:7,Rem/binary>>, M, I) -> + decode(Rem, M+7, round(I + Int * math:pow(2, M))); +decode(<<0:1,Int:7,Rem/binary>>, M, I) -> + {round(I + Int * math:pow(2, M)), Rem}. + +-spec prefix_plus(pos_integer(), {non_neg_integer(), binary()}) -> {non_neg_integer(), binary()}. +prefix_plus(Prefix, {I, Rem}) -> + {Prefix+I, Rem}. + +-spec encode(non_neg_integer(), pos_integer()) -> binary(). +%% First clauses are performance optimizations for Int == 2^Prefix - 1 +encode( 1,1) -> << 1:1,0:8>>; +encode( 3,2) -> << 3:2,0:8>>; +encode( 7,3) -> << 7:3,0:8>>; +encode( 15,4) -> << 15:4,0:8>>; +encode( 31,5) -> << 31:5,0:8>>; +encode( 63,6) -> << 63:6,0:8>>; +encode(127,7) -> <<127:7,0:8>>; +encode(255,8) -> <<255,0>>; +encode(Int, N) when Int < (1 bsl N - 1) -> + <>; +encode(Int, N) -> + Prefix = 1 bsl N - 1, + Remaining = Int - Prefix, + Bin = encode_(Remaining, <<>>), + <>. + +-spec encode_(non_neg_integer(), binary()) -> binary(). +encode_(I, BinAcc) -> + LeastSigSeven = (I rem 128), + RestToEncode = I bsr 7, + case RestToEncode of + 0 -> + <>; + _ -> %% Adds the continuation bit + ThisByte = 128 + LeastSigSeven, + encode_(RestToEncode, <>) + end. diff --git a/examples/http_interactions/build/packages/hpack_erl/src/hpack_string.erl b/examples/http_interactions/build/packages/hpack_erl/src/hpack_string.erl new file mode 100644 index 0000000..5a7f451 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/hpack_string.erl @@ -0,0 +1,28 @@ +%% @private + +-module(hpack_string). + +-export([ + decode/1 + ]). + +-spec decode(binary()) -> {binary(), binary()}. +decode(<<>>) -> + {<<>>, <<>>}; +decode(<>) -> + {Length, Rem} = hpack_integer:decode(Bin, 7), + <> = Rem, + Literal = case Huff of + 1 -> + huffman:decode(RawLiteral); + 0 -> + RawLiteral + end, + {Literal, B2}. + +%% As the Huffman-encoded data doesn't always end at an octet +%% boundary, some padding is inserted after it, up to the next octet +%% boundary. To prevent this padding from being misinterpreted as +%% part of the string literal, the most significant bits of the code +%% corresponding to the EOS (end-of-string) symbol are used. +%% EOS (256) |11111111|11111111|11111111|111111 3fffffff [30] diff --git a/examples/http_interactions/build/packages/hpack_erl/src/huffman.erl b/examples/http_interactions/build/packages/hpack_erl/src/huffman.erl new file mode 100644 index 0000000..f932933 --- /dev/null +++ b/examples/http_interactions/build/packages/hpack_erl/src/huffman.erl @@ -0,0 +1,531 @@ +%% @private + +-module(huffman). + +-export([decode/1, encode/1]). + +decode(Bin) -> decode(Bin, []). +decode(<< 16#1ff8:13,Bits/bits>>, Acc) -> decode(Bits, [ 0|Acc]); +decode(<< 16#7fffd8:23,Bits/bits>>, Acc) -> decode(Bits, [ 1|Acc]); +decode(<< 16#fffffe2:28,Bits/bits>>, Acc) -> decode(Bits, [ 2|Acc]); +decode(<< 16#fffffe3:28,Bits/bits>>, Acc) -> decode(Bits, [ 3|Acc]); +decode(<< 16#fffffe4:28,Bits/bits>>, Acc) -> decode(Bits, [ 4|Acc]); +decode(<< 16#fffffe5:28,Bits/bits>>, Acc) -> decode(Bits, [ 5|Acc]); +decode(<< 16#fffffe6:28,Bits/bits>>, Acc) -> decode(Bits, [ 6|Acc]); +decode(<< 16#fffffe7:28,Bits/bits>>, Acc) -> decode(Bits, [ 7|Acc]); +decode(<< 16#fffffe8:28,Bits/bits>>, Acc) -> decode(Bits, [ 8|Acc]); +decode(<< 16#ffffea:24,Bits/bits>>, Acc) -> decode(Bits, [ 9|Acc]); +decode(<<16#3ffffffc:30,Bits/bits>>, Acc) -> decode(Bits, [ 10|Acc]); +decode(<< 16#fffffe9:28,Bits/bits>>, Acc) -> decode(Bits, [ 11|Acc]); +decode(<< 16#fffffea:28,Bits/bits>>, Acc) -> decode(Bits, [ 12|Acc]); +decode(<<16#3ffffffd:30,Bits/bits>>, Acc) -> decode(Bits, [ 13|Acc]); +decode(<< 16#fffffeb:28,Bits/bits>>, Acc) -> decode(Bits, [ 14|Acc]); +decode(<< 16#fffffec:28,Bits/bits>>, Acc) -> decode(Bits, [ 15|Acc]); +decode(<< 16#fffffed:28,Bits/bits>>, Acc) -> decode(Bits, [ 16|Acc]); +decode(<< 16#fffffee:28,Bits/bits>>, Acc) -> decode(Bits, [ 17|Acc]); +decode(<< 16#fffffef:28,Bits/bits>>, Acc) -> decode(Bits, [ 18|Acc]); +decode(<< 16#ffffff0:28,Bits/bits>>, Acc) -> decode(Bits, [ 19|Acc]); +decode(<< 16#ffffff1:28,Bits/bits>>, Acc) -> decode(Bits, [ 20|Acc]); +decode(<< 16#ffffff2:28,Bits/bits>>, Acc) -> decode(Bits, [ 21|Acc]); +decode(<<16#3ffffffe:30,Bits/bits>>, Acc) -> decode(Bits, [ 22|Acc]); +decode(<< 16#ffffff3:28,Bits/bits>>, Acc) -> decode(Bits, [ 23|Acc]); +decode(<< 16#ffffff4:28,Bits/bits>>, Acc) -> decode(Bits, [ 24|Acc]); +decode(<< 16#ffffff5:28,Bits/bits>>, Acc) -> decode(Bits, [ 25|Acc]); +decode(<< 16#ffffff6:28,Bits/bits>>, Acc) -> decode(Bits, [ 26|Acc]); +decode(<< 16#ffffff7:28,Bits/bits>>, Acc) -> decode(Bits, [ 27|Acc]); +decode(<< 16#ffffff8:28,Bits/bits>>, Acc) -> decode(Bits, [ 28|Acc]); +decode(<< 16#ffffff9:28,Bits/bits>>, Acc) -> decode(Bits, [ 29|Acc]); +decode(<< 16#ffffffa:28,Bits/bits>>, Acc) -> decode(Bits, [ 30|Acc]); +decode(<< 16#ffffffb:28,Bits/bits>>, Acc) -> decode(Bits, [ 31|Acc]); +decode(<< 16#14:6,Bits/bits>>, Acc) -> decode(Bits, [ 32|Acc]); +decode(<< 16#3f8:10,Bits/bits>>, Acc) -> decode(Bits, [ 33|Acc]); +decode(<< 16#3f9:10,Bits/bits>>, Acc) -> decode(Bits, [ 34|Acc]); +decode(<< 16#ffa:12,Bits/bits>>, Acc) -> decode(Bits, [ 35|Acc]); +decode(<< 16#1ff9:13,Bits/bits>>, Acc) -> decode(Bits, [ 36|Acc]); +decode(<< 16#15:6,Bits/bits>>, Acc) -> decode(Bits, [ 37|Acc]); +decode(<< 16#f8:8,Bits/bits>>, Acc) -> decode(Bits, [ 38|Acc]); +decode(<< 16#7fa:11,Bits/bits>>, Acc) -> decode(Bits, [ 39|Acc]); +decode(<< 16#3fa:10,Bits/bits>>, Acc) -> decode(Bits, [ 40|Acc]); +decode(<< 16#3fb:10,Bits/bits>>, Acc) -> decode(Bits, [ 41|Acc]); +decode(<< 16#f9:8,Bits/bits>>, Acc) -> decode(Bits, [ 42|Acc]); +decode(<< 16#7fb:11,Bits/bits>>, Acc) -> decode(Bits, [ 43|Acc]); +decode(<< 16#fa:8,Bits/bits>>, Acc) -> decode(Bits, [ 44|Acc]); +decode(<< 16#16:6,Bits/bits>>, Acc) -> decode(Bits, [ 45|Acc]); +decode(<< 16#17:6,Bits/bits>>, Acc) -> decode(Bits, [ 46|Acc]); +decode(<< 16#18:6,Bits/bits>>, Acc) -> decode(Bits, [ 47|Acc]); +decode(<< 16#0:5,Bits/bits>>, Acc) -> decode(Bits, [ 48|Acc]); +decode(<< 16#1:5,Bits/bits>>, Acc) -> decode(Bits, [ 49|Acc]); +decode(<< 16#2:5,Bits/bits>>, Acc) -> decode(Bits, [ 50|Acc]); +decode(<< 16#19:6,Bits/bits>>, Acc) -> decode(Bits, [ 51|Acc]); +decode(<< 16#1a:6,Bits/bits>>, Acc) -> decode(Bits, [ 52|Acc]); +decode(<< 16#1b:6,Bits/bits>>, Acc) -> decode(Bits, [ 53|Acc]); +decode(<< 16#1c:6,Bits/bits>>, Acc) -> decode(Bits, [ 54|Acc]); +decode(<< 16#1d:6,Bits/bits>>, Acc) -> decode(Bits, [ 55|Acc]); +decode(<< 16#1e:6,Bits/bits>>, Acc) -> decode(Bits, [ 56|Acc]); +decode(<< 16#1f:6,Bits/bits>>, Acc) -> decode(Bits, [ 57|Acc]); +decode(<< 16#5c:7,Bits/bits>>, Acc) -> decode(Bits, [ 58|Acc]); +decode(<< 16#fb:8,Bits/bits>>, Acc) -> decode(Bits, [ 59|Acc]); +decode(<< 16#7ffc:15,Bits/bits>>, Acc) -> decode(Bits, [ 60|Acc]); +decode(<< 16#20:6,Bits/bits>>, Acc) -> decode(Bits, [ 61|Acc]); +decode(<< 16#ffb:12,Bits/bits>>, Acc) -> decode(Bits, [ 62|Acc]); +decode(<< 16#3fc:10,Bits/bits>>, Acc) -> decode(Bits, [ 63|Acc]); +decode(<< 16#1ffa:13,Bits/bits>>, Acc) -> decode(Bits, [ 64|Acc]); +decode(<< 16#21:6,Bits/bits>>, Acc) -> decode(Bits, [ 65|Acc]); +decode(<< 16#5d:7,Bits/bits>>, Acc) -> decode(Bits, [ 66|Acc]); +decode(<< 16#5e:7,Bits/bits>>, Acc) -> decode(Bits, [ 67|Acc]); +decode(<< 16#5f:7,Bits/bits>>, Acc) -> decode(Bits, [ 68|Acc]); +decode(<< 16#60:7,Bits/bits>>, Acc) -> decode(Bits, [ 69|Acc]); +decode(<< 16#61:7,Bits/bits>>, Acc) -> decode(Bits, [ 70|Acc]); +decode(<< 16#62:7,Bits/bits>>, Acc) -> decode(Bits, [ 71|Acc]); +decode(<< 16#63:7,Bits/bits>>, Acc) -> decode(Bits, [ 72|Acc]); +decode(<< 16#64:7,Bits/bits>>, Acc) -> decode(Bits, [ 73|Acc]); +decode(<< 16#65:7,Bits/bits>>, Acc) -> decode(Bits, [ 74|Acc]); +decode(<< 16#66:7,Bits/bits>>, Acc) -> decode(Bits, [ 75|Acc]); +decode(<< 16#67:7,Bits/bits>>, Acc) -> decode(Bits, [ 76|Acc]); +decode(<< 16#68:7,Bits/bits>>, Acc) -> decode(Bits, [ 77|Acc]); +decode(<< 16#69:7,Bits/bits>>, Acc) -> decode(Bits, [ 78|Acc]); +decode(<< 16#6a:7,Bits/bits>>, Acc) -> decode(Bits, [ 79|Acc]); +decode(<< 16#6b:7,Bits/bits>>, Acc) -> decode(Bits, [ 80|Acc]); +decode(<< 16#6c:7,Bits/bits>>, Acc) -> decode(Bits, [ 81|Acc]); +decode(<< 16#6d:7,Bits/bits>>, Acc) -> decode(Bits, [ 82|Acc]); +decode(<< 16#6e:7,Bits/bits>>, Acc) -> decode(Bits, [ 83|Acc]); +decode(<< 16#6f:7,Bits/bits>>, Acc) -> decode(Bits, [ 84|Acc]); +decode(<< 16#70:7,Bits/bits>>, Acc) -> decode(Bits, [ 85|Acc]); +decode(<< 16#71:7,Bits/bits>>, Acc) -> decode(Bits, [ 86|Acc]); +decode(<< 16#72:7,Bits/bits>>, Acc) -> decode(Bits, [ 87|Acc]); +decode(<< 16#fc:8,Bits/bits>>, Acc) -> decode(Bits, [ 88|Acc]); +decode(<< 16#73:7,Bits/bits>>, Acc) -> decode(Bits, [ 89|Acc]); +decode(<< 16#fd:8,Bits/bits>>, Acc) -> decode(Bits, [ 90|Acc]); +decode(<< 16#1ffb:13,Bits/bits>>, Acc) -> decode(Bits, [ 91|Acc]); +decode(<< 16#7fff0:19,Bits/bits>>, Acc) -> decode(Bits, [ 92|Acc]); +decode(<< 16#1ffc:13,Bits/bits>>, Acc) -> decode(Bits, [ 93|Acc]); +decode(<< 16#3ffc:14,Bits/bits>>, Acc) -> decode(Bits, [ 94|Acc]); +decode(<< 16#22:6,Bits/bits>>, Acc) -> decode(Bits, [ 95|Acc]); +decode(<< 16#7ffd:15,Bits/bits>>, Acc) -> decode(Bits, [ 96|Acc]); +decode(<< 16#3:5,Bits/bits>>, Acc) -> decode(Bits, [ 97|Acc]); +decode(<< 16#23:6,Bits/bits>>, Acc) -> decode(Bits, [ 98|Acc]); +decode(<< 16#4:5,Bits/bits>>, Acc) -> decode(Bits, [ 99|Acc]); +decode(<< 16#24:6,Bits/bits>>, Acc) -> decode(Bits, [100|Acc]); +decode(<< 16#5:5,Bits/bits>>, Acc) -> decode(Bits, [101|Acc]); +decode(<< 16#25:6,Bits/bits>>, Acc) -> decode(Bits, [102|Acc]); +decode(<< 16#26:6,Bits/bits>>, Acc) -> decode(Bits, [103|Acc]); +decode(<< 16#27:6,Bits/bits>>, Acc) -> decode(Bits, [104|Acc]); +decode(<< 16#6:5,Bits/bits>>, Acc) -> decode(Bits, [105|Acc]); +decode(<< 16#74:7,Bits/bits>>, Acc) -> decode(Bits, [106|Acc]); +decode(<< 16#75:7,Bits/bits>>, Acc) -> decode(Bits, [107|Acc]); +decode(<< 16#28:6,Bits/bits>>, Acc) -> decode(Bits, [108|Acc]); +decode(<< 16#29:6,Bits/bits>>, Acc) -> decode(Bits, [109|Acc]); +decode(<< 16#2a:6,Bits/bits>>, Acc) -> decode(Bits, [110|Acc]); +decode(<< 16#7:5,Bits/bits>>, Acc) -> decode(Bits, [111|Acc]); +decode(<< 16#2b:6,Bits/bits>>, Acc) -> decode(Bits, [112|Acc]); +decode(<< 16#76:7,Bits/bits>>, Acc) -> decode(Bits, [113|Acc]); +decode(<< 16#2c:6,Bits/bits>>, Acc) -> decode(Bits, [114|Acc]); +decode(<< 16#8:5,Bits/bits>>, Acc) -> decode(Bits, [115|Acc]); +decode(<< 16#9:5,Bits/bits>>, Acc) -> decode(Bits, [116|Acc]); +decode(<< 16#2d:6,Bits/bits>>, Acc) -> decode(Bits, [117|Acc]); +decode(<< 16#77:7,Bits/bits>>, Acc) -> decode(Bits, [118|Acc]); +decode(<< 16#78:7,Bits/bits>>, Acc) -> decode(Bits, [119|Acc]); +decode(<< 16#79:7,Bits/bits>>, Acc) -> decode(Bits, [120|Acc]); +decode(<< 16#7a:7,Bits/bits>>, Acc) -> decode(Bits, [121|Acc]); +decode(<< 16#7b:7,Bits/bits>>, Acc) -> decode(Bits, [122|Acc]); +decode(<< 16#7ffe:15,Bits/bits>>, Acc) -> decode(Bits, [123|Acc]); +decode(<< 16#7fc:11,Bits/bits>>, Acc) -> decode(Bits, [124|Acc]); +decode(<< 16#3ffd:14,Bits/bits>>, Acc) -> decode(Bits, [125|Acc]); +decode(<< 16#1ffd:13,Bits/bits>>, Acc) -> decode(Bits, [126|Acc]); +decode(<< 16#ffffffc:28,Bits/bits>>, Acc) -> decode(Bits, [127|Acc]); +decode(<< 16#fffe6:20,Bits/bits>>, Acc) -> decode(Bits, [128|Acc]); +decode(<< 16#3fffd2:22,Bits/bits>>, Acc) -> decode(Bits, [129|Acc]); +decode(<< 16#fffe7:20,Bits/bits>>, Acc) -> decode(Bits, [130|Acc]); +decode(<< 16#fffe8:20,Bits/bits>>, Acc) -> decode(Bits, [131|Acc]); +decode(<< 16#3fffd3:22,Bits/bits>>, Acc) -> decode(Bits, [132|Acc]); +decode(<< 16#3fffd4:22,Bits/bits>>, Acc) -> decode(Bits, [133|Acc]); +decode(<< 16#3fffd5:22,Bits/bits>>, Acc) -> decode(Bits, [134|Acc]); +decode(<< 16#7fffd9:23,Bits/bits>>, Acc) -> decode(Bits, [135|Acc]); +decode(<< 16#3fffd6:22,Bits/bits>>, Acc) -> decode(Bits, [136|Acc]); +decode(<< 16#7fffda:23,Bits/bits>>, Acc) -> decode(Bits, [137|Acc]); +decode(<< 16#7fffdb:23,Bits/bits>>, Acc) -> decode(Bits, [138|Acc]); +decode(<< 16#7fffdc:23,Bits/bits>>, Acc) -> decode(Bits, [139|Acc]); +decode(<< 16#7fffdd:23,Bits/bits>>, Acc) -> decode(Bits, [140|Acc]); +decode(<< 16#7fffde:23,Bits/bits>>, Acc) -> decode(Bits, [141|Acc]); +decode(<< 16#ffffeb:24,Bits/bits>>, Acc) -> decode(Bits, [142|Acc]); +decode(<< 16#7fffdf:23,Bits/bits>>, Acc) -> decode(Bits, [143|Acc]); +decode(<< 16#ffffec:24,Bits/bits>>, Acc) -> decode(Bits, [144|Acc]); +decode(<< 16#ffffed:24,Bits/bits>>, Acc) -> decode(Bits, [145|Acc]); +decode(<< 16#3fffd7:22,Bits/bits>>, Acc) -> decode(Bits, [146|Acc]); +decode(<< 16#7fffe0:23,Bits/bits>>, Acc) -> decode(Bits, [147|Acc]); +decode(<< 16#ffffee:24,Bits/bits>>, Acc) -> decode(Bits, [148|Acc]); +decode(<< 16#7fffe1:23,Bits/bits>>, Acc) -> decode(Bits, [149|Acc]); +decode(<< 16#7fffe2:23,Bits/bits>>, Acc) -> decode(Bits, [150|Acc]); +decode(<< 16#7fffe3:23,Bits/bits>>, Acc) -> decode(Bits, [151|Acc]); +decode(<< 16#7fffe4:23,Bits/bits>>, Acc) -> decode(Bits, [152|Acc]); +decode(<< 16#1fffdc:21,Bits/bits>>, Acc) -> decode(Bits, [153|Acc]); +decode(<< 16#3fffd8:22,Bits/bits>>, Acc) -> decode(Bits, [154|Acc]); +decode(<< 16#7fffe5:23,Bits/bits>>, Acc) -> decode(Bits, [155|Acc]); +decode(<< 16#3fffd9:22,Bits/bits>>, Acc) -> decode(Bits, [156|Acc]); +decode(<< 16#7fffe6:23,Bits/bits>>, Acc) -> decode(Bits, [157|Acc]); +decode(<< 16#7fffe7:23,Bits/bits>>, Acc) -> decode(Bits, [158|Acc]); +decode(<< 16#ffffef:24,Bits/bits>>, Acc) -> decode(Bits, [159|Acc]); +decode(<< 16#3fffda:22,Bits/bits>>, Acc) -> decode(Bits, [160|Acc]); +decode(<< 16#1fffdd:21,Bits/bits>>, Acc) -> decode(Bits, [161|Acc]); +decode(<< 16#fffe9:20,Bits/bits>>, Acc) -> decode(Bits, [162|Acc]); +decode(<< 16#3fffdb:22,Bits/bits>>, Acc) -> decode(Bits, [163|Acc]); +decode(<< 16#3fffdc:22,Bits/bits>>, Acc) -> decode(Bits, [164|Acc]); +decode(<< 16#7fffe8:23,Bits/bits>>, Acc) -> decode(Bits, [165|Acc]); +decode(<< 16#7fffe9:23,Bits/bits>>, Acc) -> decode(Bits, [166|Acc]); +decode(<< 16#1fffde:21,Bits/bits>>, Acc) -> decode(Bits, [167|Acc]); +decode(<< 16#7fffea:23,Bits/bits>>, Acc) -> decode(Bits, [168|Acc]); +decode(<< 16#3fffdd:22,Bits/bits>>, Acc) -> decode(Bits, [169|Acc]); +decode(<< 16#3fffde:22,Bits/bits>>, Acc) -> decode(Bits, [170|Acc]); +decode(<< 16#fffff0:24,Bits/bits>>, Acc) -> decode(Bits, [171|Acc]); +decode(<< 16#1fffdf:21,Bits/bits>>, Acc) -> decode(Bits, [172|Acc]); +decode(<< 16#3fffdf:22,Bits/bits>>, Acc) -> decode(Bits, [173|Acc]); +decode(<< 16#7fffeb:23,Bits/bits>>, Acc) -> decode(Bits, [174|Acc]); +decode(<< 16#7fffec:23,Bits/bits>>, Acc) -> decode(Bits, [175|Acc]); +decode(<< 16#1fffe0:21,Bits/bits>>, Acc) -> decode(Bits, [176|Acc]); +decode(<< 16#1fffe1:21,Bits/bits>>, Acc) -> decode(Bits, [177|Acc]); +decode(<< 16#3fffe0:22,Bits/bits>>, Acc) -> decode(Bits, [178|Acc]); +decode(<< 16#1fffe2:21,Bits/bits>>, Acc) -> decode(Bits, [179|Acc]); +decode(<< 16#7fffed:23,Bits/bits>>, Acc) -> decode(Bits, [180|Acc]); +decode(<< 16#3fffe1:22,Bits/bits>>, Acc) -> decode(Bits, [181|Acc]); +decode(<< 16#7fffee:23,Bits/bits>>, Acc) -> decode(Bits, [182|Acc]); +decode(<< 16#7fffef:23,Bits/bits>>, Acc) -> decode(Bits, [183|Acc]); +decode(<< 16#fffea:20,Bits/bits>>, Acc) -> decode(Bits, [184|Acc]); +decode(<< 16#3fffe2:22,Bits/bits>>, Acc) -> decode(Bits, [185|Acc]); +decode(<< 16#3fffe3:22,Bits/bits>>, Acc) -> decode(Bits, [186|Acc]); +decode(<< 16#3fffe4:22,Bits/bits>>, Acc) -> decode(Bits, [187|Acc]); +decode(<< 16#7ffff0:23,Bits/bits>>, Acc) -> decode(Bits, [188|Acc]); +decode(<< 16#3fffe5:22,Bits/bits>>, Acc) -> decode(Bits, [189|Acc]); +decode(<< 16#3fffe6:22,Bits/bits>>, Acc) -> decode(Bits, [190|Acc]); +decode(<< 16#7ffff1:23,Bits/bits>>, Acc) -> decode(Bits, [191|Acc]); +decode(<< 16#3ffffe0:26,Bits/bits>>, Acc) -> decode(Bits, [192|Acc]); +decode(<< 16#3ffffe1:26,Bits/bits>>, Acc) -> decode(Bits, [193|Acc]); +decode(<< 16#fffeb:20,Bits/bits>>, Acc) -> decode(Bits, [194|Acc]); +decode(<< 16#7fff1:19,Bits/bits>>, Acc) -> decode(Bits, [195|Acc]); +decode(<< 16#3fffe7:22,Bits/bits>>, Acc) -> decode(Bits, [196|Acc]); +decode(<< 16#7ffff2:23,Bits/bits>>, Acc) -> decode(Bits, [197|Acc]); +decode(<< 16#3fffe8:22,Bits/bits>>, Acc) -> decode(Bits, [198|Acc]); +decode(<< 16#1ffffec:25,Bits/bits>>, Acc) -> decode(Bits, [199|Acc]); +decode(<< 16#3ffffe2:26,Bits/bits>>, Acc) -> decode(Bits, [200|Acc]); +decode(<< 16#3ffffe3:26,Bits/bits>>, Acc) -> decode(Bits, [201|Acc]); +decode(<< 16#3ffffe4:26,Bits/bits>>, Acc) -> decode(Bits, [202|Acc]); +decode(<< 16#7ffffde:27,Bits/bits>>, Acc) -> decode(Bits, [203|Acc]); +decode(<< 16#7ffffdf:27,Bits/bits>>, Acc) -> decode(Bits, [204|Acc]); +decode(<< 16#3ffffe5:26,Bits/bits>>, Acc) -> decode(Bits, [205|Acc]); +decode(<< 16#fffff1:24,Bits/bits>>, Acc) -> decode(Bits, [206|Acc]); +decode(<< 16#1ffffed:25,Bits/bits>>, Acc) -> decode(Bits, [207|Acc]); +decode(<< 16#7fff2:19,Bits/bits>>, Acc) -> decode(Bits, [208|Acc]); +decode(<< 16#1fffe3:21,Bits/bits>>, Acc) -> decode(Bits, [209|Acc]); +decode(<< 16#3ffffe6:26,Bits/bits>>, Acc) -> decode(Bits, [210|Acc]); +decode(<< 16#7ffffe0:27,Bits/bits>>, Acc) -> decode(Bits, [211|Acc]); +decode(<< 16#7ffffe1:27,Bits/bits>>, Acc) -> decode(Bits, [212|Acc]); +decode(<< 16#3ffffe7:26,Bits/bits>>, Acc) -> decode(Bits, [213|Acc]); +decode(<< 16#7ffffe2:27,Bits/bits>>, Acc) -> decode(Bits, [214|Acc]); +decode(<< 16#fffff2:24,Bits/bits>>, Acc) -> decode(Bits, [215|Acc]); +decode(<< 16#1fffe4:21,Bits/bits>>, Acc) -> decode(Bits, [216|Acc]); +decode(<< 16#1fffe5:21,Bits/bits>>, Acc) -> decode(Bits, [217|Acc]); +decode(<< 16#3ffffe8:26,Bits/bits>>, Acc) -> decode(Bits, [218|Acc]); +decode(<< 16#3ffffe9:26,Bits/bits>>, Acc) -> decode(Bits, [219|Acc]); +decode(<< 16#ffffffd:28,Bits/bits>>, Acc) -> decode(Bits, [220|Acc]); +decode(<< 16#7ffffe3:27,Bits/bits>>, Acc) -> decode(Bits, [221|Acc]); +decode(<< 16#7ffffe4:27,Bits/bits>>, Acc) -> decode(Bits, [222|Acc]); +decode(<< 16#7ffffe5:27,Bits/bits>>, Acc) -> decode(Bits, [223|Acc]); +decode(<< 16#fffec:20,Bits/bits>>, Acc) -> decode(Bits, [224|Acc]); +decode(<< 16#fffff3:24,Bits/bits>>, Acc) -> decode(Bits, [225|Acc]); +decode(<< 16#fffed:20,Bits/bits>>, Acc) -> decode(Bits, [226|Acc]); +decode(<< 16#1fffe6:21,Bits/bits>>, Acc) -> decode(Bits, [227|Acc]); +decode(<< 16#3fffe9:22,Bits/bits>>, Acc) -> decode(Bits, [228|Acc]); +decode(<< 16#1fffe7:21,Bits/bits>>, Acc) -> decode(Bits, [229|Acc]); +decode(<< 16#1fffe8:21,Bits/bits>>, Acc) -> decode(Bits, [230|Acc]); +decode(<< 16#7ffff3:23,Bits/bits>>, Acc) -> decode(Bits, [231|Acc]); +decode(<< 16#3fffea:22,Bits/bits>>, Acc) -> decode(Bits, [232|Acc]); +decode(<< 16#3fffeb:22,Bits/bits>>, Acc) -> decode(Bits, [233|Acc]); +decode(<< 16#1ffffee:25,Bits/bits>>, Acc) -> decode(Bits, [234|Acc]); +decode(<< 16#1ffffef:25,Bits/bits>>, Acc) -> decode(Bits, [235|Acc]); +decode(<< 16#fffff4:24,Bits/bits>>, Acc) -> decode(Bits, [236|Acc]); +decode(<< 16#fffff5:24,Bits/bits>>, Acc) -> decode(Bits, [237|Acc]); +decode(<< 16#3ffffea:26,Bits/bits>>, Acc) -> decode(Bits, [238|Acc]); +decode(<< 16#7ffff4:23,Bits/bits>>, Acc) -> decode(Bits, [239|Acc]); +decode(<< 16#3ffffeb:26,Bits/bits>>, Acc) -> decode(Bits, [240|Acc]); +decode(<< 16#7ffffe6:27,Bits/bits>>, Acc) -> decode(Bits, [241|Acc]); +decode(<< 16#3ffffec:26,Bits/bits>>, Acc) -> decode(Bits, [242|Acc]); +decode(<< 16#3ffffed:26,Bits/bits>>, Acc) -> decode(Bits, [243|Acc]); +decode(<< 16#7ffffe7:27,Bits/bits>>, Acc) -> decode(Bits, [244|Acc]); +decode(<< 16#7ffffe8:27,Bits/bits>>, Acc) -> decode(Bits, [245|Acc]); +decode(<< 16#7ffffe9:27,Bits/bits>>, Acc) -> decode(Bits, [246|Acc]); +decode(<< 16#7ffffea:27,Bits/bits>>, Acc) -> decode(Bits, [247|Acc]); +decode(<< 16#7ffffeb:27,Bits/bits>>, Acc) -> decode(Bits, [248|Acc]); +decode(<< 16#ffffffe:28,Bits/bits>>, Acc) -> decode(Bits, [249|Acc]); +decode(<< 16#7ffffec:27,Bits/bits>>, Acc) -> decode(Bits, [250|Acc]); +decode(<< 16#7ffffed:27,Bits/bits>>, Acc) -> decode(Bits, [251|Acc]); +decode(<< 16#7ffffee:27,Bits/bits>>, Acc) -> decode(Bits, [252|Acc]); +decode(<< 16#7ffffef:27,Bits/bits>>, Acc) -> decode(Bits, [253|Acc]); +decode(<< 16#7fffff0:27,Bits/bits>>, Acc) -> decode(Bits, [254|Acc]); +decode(<< 16#3ffffee:26,Bits/bits>>, Acc) -> decode(Bits, [255|Acc]); +decode(<<16#3fffffff:30,Bits/bits>>, Acc) -> decode(Bits, [256|Acc]); +decode(_, Acc) -> + list_to_binary(lists:reverse(Acc)). + +encode(Bin) -> encode(Bin, <<>>). +encode(<< 0:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 1:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 2:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 3:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 4:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 5:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 6:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 7:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 8:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 9:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 10:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 11:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 12:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 13:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 14:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 15:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 16:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 17:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 18:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 19:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 20:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 21:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 22:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 23:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 24:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 25:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 26:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 27:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 28:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 29:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 30:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 31:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 32:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 33:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 34:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 35:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 36:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 37:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 38:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 39:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 40:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 41:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 42:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 43:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 44:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 45:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 46:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 47:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 48:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 49:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 50:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 51:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 52:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 53:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 54:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 55:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 56:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 57:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 58:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 59:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 60:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 61:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 62:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 63:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 64:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 65:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 66:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 67:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 68:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 69:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 70:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 71:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 72:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 73:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 74:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 75:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 76:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 77:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 78:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 79:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 80:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 81:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 82:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 83:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 84:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 85:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 86:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 87:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 88:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 89:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 90:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 91:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 92:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 93:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 94:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 95:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 96:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 97:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 98:8,T/binary>>, Acc) -> encode(T, <>); +encode(<< 99:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<100:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<101:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<102:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<103:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<104:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<105:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<106:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<107:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<108:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<109:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<110:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<111:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<112:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<113:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<114:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<115:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<116:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<117:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<118:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<119:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<120:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<121:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<122:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<123:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<124:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<125:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<126:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<127:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<128:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<129:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<130:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<131:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<132:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<133:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<134:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<135:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<136:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<137:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<138:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<139:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<140:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<141:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<142:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<143:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<144:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<145:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<146:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<147:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<148:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<149:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<150:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<151:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<152:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<153:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<154:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<155:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<156:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<157:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<158:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<159:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<160:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<161:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<162:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<163:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<164:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<165:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<166:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<167:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<168:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<169:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<170:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<171:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<172:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<173:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<174:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<175:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<176:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<177:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<178:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<179:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<180:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<181:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<182:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<183:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<184:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<185:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<186:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<187:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<188:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<189:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<190:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<191:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<192:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<193:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<194:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<195:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<196:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<197:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<198:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<199:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<200:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<201:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<202:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<203:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<204:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<205:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<206:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<207:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<208:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<209:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<210:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<211:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<212:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<213:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<214:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<215:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<216:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<217:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<218:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<219:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<220:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<221:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<222:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<223:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<224:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<225:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<226:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<227:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<228:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<229:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<230:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<231:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<232:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<233:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<234:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<235:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<236:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<237:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<238:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<239:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<240:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<241:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<242:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<243:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<244:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<245:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<246:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<247:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<248:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<249:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<250:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<251:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<252:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<253:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<254:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<255:8,T/binary>>, Acc) -> encode(T, <>); +encode(<<256:9,T/binary>>, Acc) -> encode(T, <>); +%%TODO: This "256" EOS Behavior. I really need to dig into it. +encode(<<>>, Acc) when bit_size(Acc) rem 8 > 0 -> + NumberOfBits = 8 - (bit_size(Acc) rem 8), + Bits = round(math:pow(2,NumberOfBits) - 1), + <>; +encode(<<>>, Acc) -> Acc. diff --git a/examples/http_interactions/build/packages/kryptos/LICENSE b/examples/http_interactions/build/packages/kryptos/LICENSE new file mode 100644 index 0000000..4474274 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2026, John Downey. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/kryptos/README.md b/examples/http_interactions/build/packages/kryptos/README.md new file mode 100644 index 0000000..4f270ad --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/README.md @@ -0,0 +1,88 @@ +# kryptos + +[![Package Version](https://img.shields.io/hexpm/v/kryptos)](https://hex.pm/packages/kryptos) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/kryptos/) + +A cryptography library for Gleam targeting both Erlang and JavaScript runtimes. + +## Why kryptos? + +kryptos logo + +- **Dual-runtime support** — Works on both Erlang and JavaScript (Node.js), + wrapping each platform's native crypto APIs for consistent behavior. +- **No custom cryptography** — All cryptographic operations delegate to the + runtime's battle-tested implementations (Erlang's `crypto` module, + Node.js `crypto` module). This library is a wrapper, not a reimplementation. +- **Misuse-resistant API** — Inspired by Go's crypto library, the API guides + you toward safe defaults and makes dangerous operations explicit. +- **Tested against Wycheproof** — Validated against Google's + [Wycheproof](https://github.com/C2SP/wycheproof) test vectors to catch + edge-case vulnerabilities. + +> [!NOTE] +> **Browser JavaScript is not supported.** The WebCrypto API is promise-based, +> which doesn't fit the synchronous FFI model. For browser crypto, see +> [plinth](https://hexdocs.pm/plinth/index.html) which provides Gleam bindings +> to WebCrypto using promises. + +## Installation + +```sh +gleam add kryptos +``` + +## Requirements + +- **Erlang/OTP 27+** — Required for the Erlang target +- **Node.js 22+** — Required for the JavaScript target + +## Features + +| Module | Algorithms | +| ---------------------------------------------------------------------------- | ------------------------------------------------------- | +| [aead](https://hexdocs.pm/kryptos/kryptos/aead.html) | AES-GCM, AES-CCM, ChaCha20-Poly1305, XChaCha20-Poly1305 | +| [block](https://hexdocs.pm/kryptos/kryptos/block.html) | AES-128, AES-192, AES-256, AES Key Wrap (RFC 3394) | +| [crypto](https://hexdocs.pm/kryptos/kryptos/crypto.html) | HKDF, PBKDF2, random bytes | +| [ec](https://hexdocs.pm/kryptos/kryptos/ec.html) | Key generation, import/export, introspection | +| [ecdh](https://hexdocs.pm/kryptos/kryptos/ecdh.html) | P-256, P-384, P-521, secp256k1 | +| [ecdsa](https://hexdocs.pm/kryptos/kryptos/ecdsa.html) | P-256, P-384, P-521, secp256k1 | +| [eddsa](https://hexdocs.pm/kryptos/kryptos/eddsa.html) | Ed25519, Ed448 | +| [hash](https://hexdocs.pm/kryptos/kryptos/hash.html) | SHA-1, SHA-2, SHA-3, SHAKE, BLAKE2 | +| [hmac](https://hexdocs.pm/kryptos/kryptos/hmac.html) | All hash algorithms | +| [rsa](https://hexdocs.pm/kryptos/kryptos/rsa.html) | OAEP, PKCS#1 v1.5, PSS, PKCS#1 v1.5 signatures | +| [x509/certificate](https://hexdocs.pm/kryptos/kryptos/x509/certificate.html) | Certificate parsing and self-signed generation | +| [x509/csr](https://hexdocs.pm/kryptos/kryptos/x509/csr.html) | CSR generation with ECDSA/RSA/EdDSA, SANs | +| [xdh](https://hexdocs.pm/kryptos/kryptos/xdh.html) | X25519, X448 | + +## Getting Started + +Encrypt and decrypt data using AES-GCM: + +```gleam +import kryptos/aead +import kryptos/block +import kryptos/crypto + +pub fn main() { + // Generate a random 256-bit key + let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) + let ctx = aead.gcm(cipher) + + // Generate a random nonce (never reuse with the same key!) + let nonce = crypto.random_bytes(aead.nonce_size(ctx)) + + // Encrypt + let plaintext = <<"hello, world!":utf8>> + let assert Ok(#(ciphertext, tag)) = aead.seal(ctx, nonce:, plaintext:) + + // Decrypt + let assert Ok(decrypted) = aead.open(ctx, nonce:, ciphertext:, tag:) + // decrypted == plaintext +} +``` + +## Security + +For guidance on choosing cryptographic primitives, see +[Cryptographic Right Answers](https://latacora.micro.blog/2018/04/03/cryptographic-right-answers.html). diff --git a/examples/http_interactions/build/packages/kryptos/gleam.toml b/examples/http_interactions/build/packages/kryptos/gleam.toml new file mode 100644 index 0000000..94fbf19 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/gleam.toml @@ -0,0 +1,26 @@ +name = "kryptos" +version = "1.4.0" +description = "Cryptography library for Gleam targeting Erlang and JavaScript" +licences = ["Apache-2.0"] +repository = { type = "github", user = "jtdowney", repo = "kryptos" } + +[dependencies] +bigi = ">= 4.1.0 and < 5.0.0" +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +gleam_time = ">= 1.7.0 and < 2.0.0" + +[dev-dependencies] +birdie = ">= 1.5.3 and < 2.0.0" +filepath = ">= 1.1.2 and < 2.0.0" +gleam_json = ">= 3.1.0 and < 4.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" +qcheck = ">= 1.0.3 and < 2.0.0" +shellout = ">= 1.7.0 and < 2.0.0" +simplifile = ">= 2.3.2 and < 3.0.0" +unitest = ">= 1.0.0 and < 2.0.0" + +[javascript] +runtime = "node" + +[erlang] +extra_applications = ["crypto"] diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Ccm.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Ccm.hrl new file mode 100644 index 0000000..fae166c --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Ccm.hrl @@ -0,0 +1,5 @@ +-record(ccm, { + cipher :: kryptos@block:block_cipher(), + nonce_size :: integer(), + tag_size :: integer() +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_ChaCha20Poly1305.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_ChaCha20Poly1305.hrl new file mode 100644 index 0000000..8fbe611 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_ChaCha20Poly1305.hrl @@ -0,0 +1 @@ +-record(cha_cha20_poly1305, {key :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Gcm.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Gcm.hrl new file mode 100644 index 0000000..333fbad --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_Gcm.hrl @@ -0,0 +1 @@ +-record(gcm, {cipher :: kryptos@block:block_cipher(), nonce_size :: integer()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_XChaCha20Poly1305.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_XChaCha20Poly1305.hrl new file mode 100644 index 0000000..a1fca58 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@aead_XChaCha20Poly1305.hrl @@ -0,0 +1 @@ +-record(x_cha_cha20_poly1305, {key :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Aes.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Aes.hrl new file mode 100644 index 0000000..4eeff83 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Aes.hrl @@ -0,0 +1 @@ +-record(aes, {key_size :: integer(), key :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Cbc.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Cbc.hrl new file mode 100644 index 0000000..cb7864f --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Cbc.hrl @@ -0,0 +1 @@ +-record(cbc, {cipher :: kryptos@block:block_cipher(), iv :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ctr.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ctr.hrl new file mode 100644 index 0000000..fad65f7 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ctr.hrl @@ -0,0 +1 @@ +-record(ctr, {cipher :: kryptos@block:block_cipher(), nonce :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ecb.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ecb.hrl new file mode 100644 index 0000000..121ddf4 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@block_Ecb.hrl @@ -0,0 +1 @@ +-record(ecb, {cipher :: kryptos@block:block_cipher()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake128.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake128.hrl new file mode 100644 index 0000000..67f699e --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake128.hrl @@ -0,0 +1 @@ +-record(shake128, {output_length :: integer()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake256.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake256.hrl new file mode 100644 index 0000000..7b7599f --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@hash_Shake256.hrl @@ -0,0 +1 @@ +-record(shake256, {output_length :: integer()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@internal@x509_SigAlgInfo.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@internal@x509_SigAlgInfo.hrl new file mode 100644 index 0000000..8a1b936 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@internal@x509_SigAlgInfo.hrl @@ -0,0 +1,4 @@ +-record(sig_alg_info, { + oid :: kryptos@x509:oid(), + include_null_params :: boolean() +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@rsa_Oaep.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@rsa_Oaep.hrl new file mode 100644 index 0000000..35f512a --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@rsa_Oaep.hrl @@ -0,0 +1 @@ +-record(oaep, {hash :: kryptos@hash:hash_algorithm(), label :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_Builder.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_Builder.hrl new file mode 100644 index 0000000..faabcfa --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_Builder.hrl @@ -0,0 +1,12 @@ +-record(builder, { + subject :: kryptos@x509:name(), + validity :: gleam@option:option(kryptos@x509:validity()), + basic_constraints :: gleam@option:option({boolean(), + gleam@option:option(integer())}), + key_usage :: list(kryptos@x509:key_usage()), + extended_key_usage :: list(kryptos@x509:extended_key_usage()), + subject_alt_names :: list(kryptos@x509:subject_alt_name()), + serial_number :: gleam@option:option(bitstring()), + subject_key_identifier :: gleam@option:option(kryptos@x509@certificate:subject_key_identifier_config()), + authority_key_identifier :: kryptos@x509@certificate:authority_key_identifier_config() +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_BuiltCertificate.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_BuiltCertificate.hrl new file mode 100644 index 0000000..e3471bc --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_BuiltCertificate.hrl @@ -0,0 +1 @@ +-record(built_certificate, {der :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_ParsedCertificate.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_ParsedCertificate.hrl new file mode 100644 index 0000000..c2abdbf --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@certificate_ParsedCertificate.hrl @@ -0,0 +1,19 @@ +-record(parsed_certificate, { + der :: bitstring(), + tbs_bytes :: bitstring(), + signature :: bitstring(), + version :: integer(), + serial_number :: bitstring(), + signature_algorithm :: kryptos@x509:signature_algorithm(), + issuer :: kryptos@x509:name(), + validity :: kryptos@x509:validity(), + subject :: kryptos@x509:name(), + public_key :: kryptos@x509:public_key(), + basic_constraints :: gleam@option:option(kryptos@x509:basic_constraints()), + key_usage :: list(kryptos@x509:key_usage()), + extended_key_usage :: list(kryptos@x509:extended_key_usage()), + subject_alt_names :: list(kryptos@x509:subject_alt_name()), + subject_key_identifier :: gleam@option:option(bitstring()), + authority_key_identifier :: gleam@option:option(kryptos@x509:authority_key_identifier()), + extensions :: list({kryptos@x509:oid(), boolean(), bitstring()}) +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_Builder.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_Builder.hrl new file mode 100644 index 0000000..fb78656 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_Builder.hrl @@ -0,0 +1,4 @@ +-record(builder, { + subject :: kryptos@x509:name(), + extensions :: kryptos@x509:extensions() +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_BuiltCsr.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_BuiltCsr.hrl new file mode 100644 index 0000000..65a0d38 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_BuiltCsr.hrl @@ -0,0 +1 @@ +-record(built_csr, {der :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_ParsedCsr.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_ParsedCsr.hrl new file mode 100644 index 0000000..ac1aa4b --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509@csr_ParsedCsr.hrl @@ -0,0 +1,10 @@ +-record(parsed_csr, { + der :: bitstring(), + version :: integer(), + subject :: kryptos@x509:name(), + public_key :: kryptos@x509:public_key(), + signature_algorithm :: kryptos@x509:signature_algorithm(), + subject_alt_names :: list(kryptos@x509:subject_alt_name()), + extensions :: list({kryptos@x509:oid(), boolean(), bitstring()}), + attributes :: list({kryptos@x509:oid(), bitstring()}) +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_AuthorityKeyIdentifier.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_AuthorityKeyIdentifier.hrl new file mode 100644 index 0000000..e00dade --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_AuthorityKeyIdentifier.hrl @@ -0,0 +1,5 @@ +-record(authority_key_identifier, { + key_identifier :: gleam@option:option(bitstring()), + authority_cert_issuer :: gleam@option:option(list(kryptos@x509:subject_alt_name())), + authority_cert_serial_number :: gleam@option:option(bitstring()) +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_BasicConstraints.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_BasicConstraints.hrl new file mode 100644 index 0000000..c14e819 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_BasicConstraints.hrl @@ -0,0 +1,4 @@ +-record(basic_constraints, { + ca :: boolean(), + path_len_constraint :: gleam@option:option(integer()) +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Extensions.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Extensions.hrl new file mode 100644 index 0000000..81299b7 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Extensions.hrl @@ -0,0 +1 @@ +-record(extensions, {subject_alt_names :: list(kryptos@x509:subject_alt_name())}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Name.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Name.hrl new file mode 100644 index 0000000..ec39f95 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Name.hrl @@ -0,0 +1 @@ +-record(name, {rdns :: list(kryptos@x509:rdn())}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Oid.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Oid.hrl new file mode 100644 index 0000000..1a11cbe --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Oid.hrl @@ -0,0 +1 @@ +-record(oid, {components :: list(integer())}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_OtherName.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_OtherName.hrl new file mode 100644 index 0000000..d318ba9 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_OtherName.hrl @@ -0,0 +1 @@ +-record(other_name, {oid :: kryptos@x509:oid(), value :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Rdn.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Rdn.hrl new file mode 100644 index 0000000..8d7780b --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Rdn.hrl @@ -0,0 +1,3 @@ +-record(rdn, { + attributes :: list({kryptos@x509:oid(), kryptos@x509:attribute_value()}) +}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Unknown.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Unknown.hrl new file mode 100644 index 0000000..69318a0 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Unknown.hrl @@ -0,0 +1 @@ +-record(unknown, {tag :: integer(), value :: bitstring()}). diff --git a/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Validity.hrl b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Validity.hrl new file mode 100644 index 0000000..f36d418 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/include/kryptos@x509_Validity.hrl @@ -0,0 +1,4 @@ +-record(validity, { + not_before :: gleam@time@timestamp:timestamp(), + not_after :: gleam@time@timestamp:timestamp() +}). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos.app.src b/examples/http_interactions/build/packages/kryptos/src/kryptos.app.src new file mode 100644 index 0000000..a8b4b8c --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos.app.src @@ -0,0 +1,34 @@ +{application, kryptos, [ + {vsn, "1.4.0"}, + {applications, [bigi, + crypto, + gleam_stdlib, + gleam_time]}, + {description, "Cryptography library for Gleam targeting Erlang and JavaScript"}, + {modules, [kryptos@@main, + kryptos@aead, + kryptos@block, + kryptos@crypto, + kryptos@ec, + kryptos@ecdh, + kryptos@ecdsa, + kryptos@eddsa, + kryptos@hash, + kryptos@hmac, + kryptos@internal@concat_kdf, + kryptos@internal@der, + kryptos@internal@hchacha20, + kryptos@internal@hkdf, + kryptos@internal@pbkdf2, + kryptos@internal@rsa_crt, + kryptos@internal@subtle, + kryptos@internal@utils, + kryptos@internal@x509, + kryptos@rsa, + kryptos@x509, + kryptos@x509@certificate, + kryptos@x509@csr, + kryptos@xdh, + kryptos_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/aead.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/aead.gleam new file mode 100644 index 0000000..c5c6eef --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/aead.gleam @@ -0,0 +1,328 @@ +//// Authenticated Encryption with Associated Data (AEAD). +//// +//// AEAD provides both confidentiality and integrity for data, with optional +//// authenticated additional data (AAD) that is integrity-protected but not +//// encrypted. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/aead +//// import kryptos/block +//// import kryptos/crypto +//// +//// let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) +//// let ctx = aead.gcm(cipher) +//// let nonce = crypto.random_bytes(aead.nonce_size(ctx)) +//// let assert Ok(#(ciphertext, tag)) = aead.seal(ctx, nonce:, plaintext: <<"secret":utf8>>) +//// ``` + +import gleam/bit_array +import gleam/int +import gleam/list +import gleam/result +import kryptos/block.{type BlockCipher} +import kryptos/internal/hchacha20 + +/// AEAD context with its configuration. +/// +/// Use the provided constructor functions to create contexts: +/// +/// - `gcm()` / `gcm_with_nonce_size()` for AES-GCM +/// - `ccm()` / `ccm_with_sizes()` for AES-CCM +/// - `chacha20_poly1305()` for ChaCha20-Poly1305 +/// - `xchacha20_poly1305()` for XChaCha20-Poly1305 +pub opaque type AeadContext { + /// AES-GCM with the specified cipher and nonce size. + Gcm(cipher: BlockCipher, nonce_size: Int) + /// AES-CCM with configurable nonce and tag sizes (RFC 3610). + Ccm(cipher: BlockCipher, nonce_size: Int, tag_size: Int) + /// ChaCha20-Poly1305 with a 256-bit key (RFC 8439). + ChaCha20Poly1305(key: BitArray) + /// XChaCha20-Poly1305 with a 256-bit key and 24-byte nonce. + XChaCha20Poly1305(key: BitArray) +} + +/// Creates an AES-GCM context with the given block cipher. +/// +/// Uses standard parameters: 16-byte (128-bit) authentication tag and +/// 12-byte (96-bit) nonce. +/// +/// **Note:** This library only supports the full 16-byte authentication tag. +/// Truncated tags (as permitted by NIST SP 800-38D) are not supported due to +/// their reduced security guarantees. +pub fn gcm(cipher: BlockCipher) -> AeadContext { + Gcm(cipher:, nonce_size: 12) +} + +/// Creates an AES-GCM context with a custom nonce size. +/// +/// GCM supports variable nonce sizes, though 12 bytes is strongly recommended. +/// This function is primarily useful for compatibility testing with test vectors. +pub fn gcm_with_nonce_size( + cipher: BlockCipher, + nonce_size: Int, +) -> Result(AeadContext, Nil) { + case nonce_size >= 1 && nonce_size <= 64 { + True -> Ok(Gcm(cipher:, nonce_size:)) + False -> Error(Nil) + } +} + +/// Creates an AES-CCM context with the given block cipher. +/// +/// Uses standard parameters: 16-byte (128-bit) authentication tag and +/// 13-byte (104-bit) nonce, which allows messages up to 64KB. +pub fn ccm(cipher: BlockCipher) -> AeadContext { + Ccm(cipher:, nonce_size: 13, tag_size: 16) +} + +/// Creates an AES-CCM context with custom nonce and tag sizes. +/// +/// CCM allows flexible nonce and tag sizes per RFC 3610: +/// - Nonce size affects maximum message length (larger nonce = smaller max message) +/// - Tag size affects authentication strength (larger tag = stronger) +/// +/// Nonce must be 7-13 bytes. Tag must be 4, 6, 8, 10, 12, 14, or 16 bytes. +pub fn ccm_with_sizes( + cipher: BlockCipher, + nonce_size nonce_size: Int, + tag_size tag_size: Int, +) -> Result(AeadContext, Nil) { + let valid_nonce = nonce_size >= 7 && nonce_size <= 13 + let valid_tag = list.contains([4, 6, 8, 10, 12, 14, 16], tag_size) + case valid_nonce && valid_tag { + True -> Ok(Ccm(cipher:, nonce_size:, tag_size:)) + False -> Error(Nil) + } +} + +/// Creates a ChaCha20-Poly1305 AEAD context with the given key. +/// +/// Uses standard parameters per RFC 8439: 12-byte (96-bit) nonce and +/// 16-byte (128-bit) authentication tag. The key must be exactly 32 bytes. +pub fn chacha20_poly1305(key: BitArray) -> Result(AeadContext, Nil) { + case bit_array.byte_size(key) == 32 { + True -> Ok(ChaCha20Poly1305(key)) + False -> Error(Nil) + } +} + +/// Creates an XChaCha20-Poly1305 AEAD context with the given key. +/// +/// Uses extended parameters: 24-byte (192-bit) nonce and 16-byte (128-bit) +/// authentication tag. The extended nonce provides better collision resistance +/// when generating random nonces. The key must be exactly 32 bytes. +pub fn xchacha20_poly1305(key: BitArray) -> Result(AeadContext, Nil) { + case bit_array.byte_size(key) == 32 { + True -> Ok(XChaCha20Poly1305(key)) + False -> Error(Nil) + } +} + +/// Returns the required nonce size in bytes for an AEAD context. +pub fn nonce_size(ctx: AeadContext) -> Int { + case ctx { + Gcm(nonce_size:, ..) -> nonce_size + Ccm(nonce_size:, ..) -> nonce_size + ChaCha20Poly1305(..) -> 12 + XChaCha20Poly1305(..) -> 24 + } +} + +/// Returns the authentication tag size in bytes for an AEAD context. +pub fn tag_size(ctx: AeadContext) -> Int { + case ctx { + Gcm(..) -> 16 + Ccm(tag_size:, ..) -> tag_size + ChaCha20Poly1305(..) -> 16 + XChaCha20Poly1305(..) -> 16 + } +} + +/// Encrypts and authenticates plaintext using AEAD. +/// +/// The nonce must be exactly `nonce_size` bytes. Never reuse a nonce with +/// the same key. +pub fn seal( + ctx: AeadContext, + nonce nonce: BitArray, + plaintext plaintext: BitArray, +) -> Result(#(BitArray, BitArray), Nil) { + seal_with_aad(ctx, nonce, plaintext, <<>>) +} + +/// Encrypts and authenticates plaintext with additional authenticated data. +/// +/// The AAD is authenticated but not encrypted. It can be used for headers, +/// metadata, or context that should be tamper-proof but remain readable. +pub fn seal_with_aad( + ctx: AeadContext, + nonce nonce: BitArray, + plaintext plaintext: BitArray, + additional_data aad: BitArray, +) -> Result(#(BitArray, BitArray), Nil) { + let nonce_len = bit_array.byte_size(nonce) + case nonce_len > 0 && nonce_len == nonce_size(ctx) { + True -> + case ctx { + XChaCha20Poly1305(key) -> { + use #(subkey, chacha_nonce) <- result.try(xchacha20_derive(key, nonce)) + do_seal(ChaCha20Poly1305(subkey), chacha_nonce, plaintext, aad) + } + Gcm(..) | Ccm(..) | ChaCha20Poly1305(..) -> + do_seal(ctx, nonce, plaintext, aad) + } + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "aead_seal") +@external(javascript, "../kryptos_ffi.mjs", "aeadSeal") +fn do_seal( + ctx: AeadContext, + nonce: BitArray, + plaintext: BitArray, + aad: BitArray, +) -> Result(#(BitArray, BitArray), Nil) + +/// Decrypts and verifies AEAD-encrypted data. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/aead +/// import kryptos/block +/// import kryptos/crypto +/// +/// let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) +/// let ctx = aead.gcm(cipher) +/// let nonce = crypto.random_bytes(aead.nonce_size(ctx)) +/// let assert Ok(#(ciphertext, tag)) = aead.seal(ctx, nonce:, plaintext: <<"secret":utf8>>) +/// let assert Ok(plaintext) = aead.open(ctx, nonce:, tag:, ciphertext:) +/// ``` +pub fn open( + ctx: AeadContext, + nonce nonce: BitArray, + tag tag: BitArray, + ciphertext ciphertext: BitArray, +) -> Result(BitArray, Nil) { + open_with_aad(ctx, nonce, tag, ciphertext, <<>>) +} + +/// Decrypts and verifies AEAD-encrypted data with additional authenticated data. +/// +/// The AAD must match exactly what was provided during encryption. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/aead +/// import kryptos/block +/// import kryptos/crypto +/// +/// let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) +/// let ctx = aead.gcm(cipher) +/// let nonce = crypto.random_bytes(aead.nonce_size(ctx)) +/// let aad = <<"header":utf8>> +/// let assert Ok(#(ciphertext, tag)) = +/// aead.seal_with_aad(ctx, nonce:, plaintext: <<"secret":utf8>>, additional_data: aad) +/// let assert Ok(plaintext) = +/// aead.open_with_aad(ctx, nonce:, tag:, ciphertext:, additional_data: aad) +/// ``` +pub fn open_with_aad( + ctx: AeadContext, + nonce nonce: BitArray, + tag tag: BitArray, + ciphertext ciphertext: BitArray, + additional_data aad: BitArray, +) -> Result(BitArray, Nil) { + let nonce_len = bit_array.byte_size(nonce) + let tag_len = bit_array.byte_size(tag) + case + nonce_len > 0 && nonce_len == nonce_size(ctx) && tag_len == tag_size(ctx) + { + True -> + case ctx { + XChaCha20Poly1305(key) -> { + use #(subkey, chacha_nonce) <- result.try(xchacha20_derive(key, nonce)) + do_open(ChaCha20Poly1305(subkey), chacha_nonce, tag, ciphertext, aad) + } + Gcm(..) | Ccm(..) | ChaCha20Poly1305(..) -> + do_open(ctx, nonce, tag, ciphertext, aad) + } + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "aead_open") +@external(javascript, "../kryptos_ffi.mjs", "aeadOpen") +fn do_open( + ctx: AeadContext, + nonce: BitArray, + tag: BitArray, + ciphertext: BitArray, + aad: BitArray, +) -> Result(BitArray, Nil) + +/// Derives the subkey and ChaCha20 nonce for XChaCha20-Poly1305. +fn xchacha20_derive( + key: BitArray, + nonce: BitArray, +) -> Result(#(BitArray, BitArray), Nil) { + // Split 24-byte nonce: first 16 bytes for HChaCha20, last 8 bytes for ChaCha20 + case nonce { + <> -> { + // Derive 32-byte subkey using HChaCha20 + let subkey = hchacha20.subkey(key, hchacha_input) + // Construct 12-byte ChaCha20 nonce: 4 zero bytes + last 8 bytes of original nonce + let chacha_nonce = <<0:32, nonce_suffix:bits>> + Ok(#(subkey, chacha_nonce)) + } + _ -> Error(Nil) + } +} + +@internal +pub fn cipher_name(ctx: AeadContext) -> String { + case ctx { + Gcm(cipher:, ..) -> + "aes-" <> int.to_string(block.key_size(cipher)) <> "-gcm" + Ccm(cipher:, ..) -> + "aes-" <> int.to_string(block.key_size(cipher)) <> "-ccm" + ChaCha20Poly1305(..) -> "chacha20-poly1305" + XChaCha20Poly1305(..) -> "chacha20-poly1305" + } +} + +@internal +pub fn cipher_key(ctx: AeadContext) -> BitArray { + case ctx { + Gcm(cipher:, ..) | Ccm(cipher:, ..) -> block.aes_key(cipher) + ChaCha20Poly1305(key:) | XChaCha20Poly1305(key:) -> key + } +} + +@internal +pub fn is_ccm(ctx: AeadContext) -> Bool { + case ctx { + Ccm(..) -> True + _ -> False + } +} + +@internal +pub fn is_gcm(ctx: AeadContext) -> Bool { + case ctx { + Gcm(..) -> True + _ -> False + } +} + +@internal +pub fn is_chacha20_poly1305(ctx: AeadContext) -> Bool { + case ctx { + ChaCha20Poly1305(..) -> True + _ -> False + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/block.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/block.gleam new file mode 100644 index 0000000..09cafa9 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/block.gleam @@ -0,0 +1,436 @@ +//// Block cipher implementations and modes of operation. +//// +//// AES block ciphers and modes of operation (ECB, CBC, CTR). +//// +//// **IMPORTANT SECURITY WARNING:** +//// ECB, CBC, and CTR modes do NOT provide authentication. An attacker can modify +//// ciphertext without detection. For most applications, you should use +//// authenticated encryption modes like AES-GCM or ChaCha20-Poly1305 +//// from the `kryptos/aead` module instead. +//// +//// Use these modes only when: +//// - Interoperating with legacy systems that require them +//// - Implementing higher-level protocols that provide their own authentication +//// - You fully understand the security implications +//// +//// ## Modes Overview +//// +//// - ECB (Electronic Codebook): Encrypts each block independently. +//// INSECURE for most uses - reveals patterns in data. Only use for +//// single-block encryption or specific legacy requirements. +//// - CBC (Cipher Block Chaining): Each block XORed with previous ciphertext. +//// Requires random IV per encryption. Uses PKCS7 padding automatically. +//// - CTR (Counter): Converts block cipher to stream cipher. +//// Nonce reuse is catastrophic - NEVER reuse a nonce with the same key. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/block +//// import kryptos/crypto +//// +//// // CBC encryption with random IV +//// let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) +//// let assert Ok(ctx) = block.cbc(cipher, iv: crypto.random_bytes(16)) +//// let assert Ok(ciphertext) = block.encrypt(ctx, <<"secret":utf8>>) +//// let assert Ok(decrypted) = block.decrypt(ctx, ciphertext) +//// // decrypted == <<"secret":utf8>> +//// ``` + +import gleam/bit_array +import gleam/int +@target(erlang) +import gleam/list +@target(erlang) +import kryptos/crypto + +@target(erlang) +const aes_key_wrap_iv = <<0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6>> + +/// A block cipher with its associated key material. +pub opaque type BlockCipher { + Aes(key_size: Int, key: BitArray) +} + +/// Context for block cipher modes of operation. +/// +/// Use the provided constructor functions to create contexts: +/// +/// - `ecb()` for ECB mode +/// - `cbc()` for CBC mode +/// - `ctr()` for CTR mode +pub opaque type CipherContext { + /// Electronic Codebook mode. + Ecb(cipher: BlockCipher) + + /// Cipher Block Chaining mode with PKCS7 padding. + Cbc(cipher: BlockCipher, iv: BitArray) + + /// Counter mode (streaming cipher). + Ctr(cipher: BlockCipher, nonce: BitArray) +} + +/// Returns the key size in bits for a block cipher. +pub fn key_size(cipher: BlockCipher) -> Int { + case cipher { + Aes(key_size:, ..) -> key_size + } +} + +/// Returns the block size in bytes for a block cipher. +pub fn block_size(cipher: BlockCipher) -> Int { + case cipher { + Aes(..) -> 16 + } +} + +/// Creates a new AES-128 block cipher with the given key. +/// +/// The key must be exactly 16 bytes. +pub fn aes_128(key: BitArray) -> Result(BlockCipher, Nil) { + case bit_array.byte_size(key) == 16 { + True -> Ok(Aes(128, key)) + False -> Error(Nil) + } +} + +/// Creates a new AES-192 block cipher with the given key. +/// +/// The key must be exactly 24 bytes. +pub fn aes_192(key: BitArray) -> Result(BlockCipher, Nil) { + case bit_array.byte_size(key) == 24 { + True -> Ok(Aes(192, key)) + False -> Error(Nil) + } +} + +/// Creates a new AES-256 block cipher with the given key. +/// +/// The key must be exactly 32 bytes. +pub fn aes_256(key: BitArray) -> Result(BlockCipher, Nil) { + case bit_array.byte_size(key) == 32 { + True -> Ok(Aes(256, key)) + False -> Error(Nil) + } +} + +/// Creates an ECB mode context for the given cipher. +/// +/// **SECURITY WARNING:** ECB mode is insecure for most use cases. +/// Identical plaintext blocks produce identical ciphertext blocks, +/// revealing patterns in the data. +pub fn ecb(cipher: BlockCipher) -> CipherContext { + Ecb(cipher) +} + +/// Creates a CBC mode context with the given cipher and IV. +/// +/// The IV must be exactly 16 bytes, random, and unique per encryption. +pub fn cbc(cipher: BlockCipher, iv iv: BitArray) -> Result(CipherContext, Nil) { + case bit_array.byte_size(iv) == 16 { + True -> Ok(Cbc(cipher, iv)) + False -> Error(Nil) + } +} + +/// Creates a CTR mode context with the given cipher and nonce. +/// +/// **SECURITY WARNING:** Nonce reuse is catastrophic in CTR mode. +/// NEVER reuse a nonce with the same key. +/// +/// The nonce must be exactly 16 bytes. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/block +/// import kryptos/crypto +/// +/// let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32)) +/// let assert Ok(ctx) = block.ctr(cipher, nonce: crypto.random_bytes(16)) +/// let assert Ok(ciphertext) = block.encrypt(ctx, <<"secret":utf8>>) +/// let assert Ok(plaintext) = block.decrypt(ctx, ciphertext) +/// ``` +pub fn ctr( + cipher: BlockCipher, + nonce nonce: BitArray, +) -> Result(CipherContext, Nil) { + case bit_array.byte_size(nonce) == 16 { + True -> Ok(Ctr(cipher, nonce)) + False -> Error(Nil) + } +} + +/// Encrypts plaintext using the cipher mode. +/// +/// ## Notes +/// - ECB: No IV required +/// - CBC: Automatically applies PKCS7 padding; ciphertext may be larger than plaintext +/// - CTR: No padding needed; ciphertext is same size as plaintext +pub fn encrypt(ctx: CipherContext, plaintext: BitArray) -> Result(BitArray, Nil) { + case validate_iv(ctx) { + True -> do_encrypt(ctx, plaintext) + False -> Error(Nil) + } +} + +fn validate_iv(ctx: CipherContext) -> Bool { + case ctx { + Ecb(..) -> True + Cbc(iv:, ..) -> bit_array.byte_size(iv) == 16 + Ctr(nonce:, ..) -> bit_array.byte_size(nonce) == 16 + } +} + +@external(erlang, "kryptos_ffi", "block_cipher_encrypt") +@external(javascript, "../kryptos_ffi.mjs", "blockCipherEncrypt") +fn do_encrypt(ctx: CipherContext, plaintext: BitArray) -> Result(BitArray, Nil) + +/// Decrypts ciphertext using the cipher mode. +/// +/// ## Notes +/// - ECB: No IV required +/// - CBC: Automatically removes PKCS7 padding; returns error if padding is invalid +/// - CTR: No padding; ciphertext size equals plaintext size +pub fn decrypt( + ctx: CipherContext, + ciphertext: BitArray, +) -> Result(BitArray, Nil) { + case validate_iv(ctx) { + True -> do_decrypt(ctx, ciphertext) + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "block_cipher_decrypt") +@external(javascript, "../kryptos_ffi.mjs", "blockCipherDecrypt") +fn do_decrypt(ctx: CipherContext, ciphertext: BitArray) -> Result(BitArray, Nil) + +@internal +pub fn cipher_name(ctx: CipherContext) -> String { + case ctx { + Ecb(cipher: Aes(key_size:, ..)) -> + "aes-" <> int.to_string(key_size) <> "-ecb" + Cbc(cipher: Aes(key_size:, ..), ..) -> + "aes-" <> int.to_string(key_size) <> "-cbc" + Ctr(cipher: Aes(key_size:, ..), ..) -> + "aes-" <> int.to_string(key_size) <> "-ctr" + } +} + +@internal +pub fn cipher_key(ctx: CipherContext) -> BitArray { + case ctx { + Ecb(cipher: Aes(key:, ..)) + | Cbc(cipher: Aes(key:, ..), ..) + | Ctr(cipher: Aes(key:, ..), ..) -> key + } +} + +@internal +pub fn cipher_iv(ctx: CipherContext) -> BitArray { + case ctx { + Ecb(..) -> <<>> + Cbc(iv:, ..) -> iv + Ctr(nonce:, ..) -> nonce + } +} + +/// Wraps key material using AES Key Wrap (RFC 3394). +/// +/// Key wrapping is used to protect cryptographic keys when they need to be +/// transported or stored. Unlike general encryption, key wrapping: +/// - Does not require an IV (uses a default IV internally) +/// - Provides integrity protection +/// - Output is always 8 bytes larger than input +/// +/// The plaintext must be a multiple of 8 bytes, minimum 16 bytes. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/block +/// import kryptos/crypto +/// +/// let assert Ok(kek) = block.aes_256(crypto.random_bytes(32)) +/// let key_to_wrap = crypto.random_bytes(32) +/// let assert Ok(wrapped) = block.wrap(kek, key_to_wrap) +/// ``` +pub fn wrap(cipher: BlockCipher, plaintext: BitArray) -> Result(BitArray, Nil) { + let size = bit_array.byte_size(plaintext) + case size >= 16 && size % 8 == 0 { + True -> do_wrap(cipher, plaintext) + False -> Error(Nil) + } +} + +@target(javascript) +@external(javascript, "../kryptos_ffi.mjs", "blockCipherWrap") +fn do_wrap(cipher: BlockCipher, plaintext: BitArray) -> Result(BitArray, Nil) + +@target(erlang) +fn do_wrap(cipher: BlockCipher, plaintext: BitArray) -> Result(BitArray, Nil) { + let n = bit_array.byte_size(plaintext) / 8 + let r = split_into_blocks(plaintext, []) + + let #(a, r_final) = wrap_rounds(cipher, aes_key_wrap_iv, r, n, 0) + Ok(bit_array.concat([a, ..r_final])) +} + +@target(erlang) +fn wrap_rounds( + cipher: BlockCipher, + a: BitArray, + r: List(BitArray), + n: Int, + j: Int, +) -> #(BitArray, List(BitArray)) { + case j < 6 { + False -> #(a, r) + True -> { + let #(a_new, r_new) = wrap_inner(cipher, a, r, n, j, 1, []) + wrap_rounds(cipher, a_new, r_new, n, j + 1) + } + } +} + +@target(erlang) +fn wrap_inner( + cipher: BlockCipher, + a: BitArray, + r: List(BitArray), + n: Int, + j: Int, + i: Int, + acc: List(BitArray), +) -> #(BitArray, List(BitArray)) { + case r { + [] -> #(a, list.reverse(acc)) + [ri, ..rest] -> { + let b = aes_encrypt_block(cipher, <>) + let assert <> = b + let t = n * j + i + let a_xored = xor_with_counter(a_new, t) + wrap_inner(cipher, a_xored, rest, n, j, i + 1, [ri_new, ..acc]) + } + } +} + +/// Unwraps key material using AES Key Wrap (RFC 3394). +/// +/// The ciphertext must be a multiple of 8 bytes, minimum 24 bytes. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/block +/// +/// let assert Ok(kek) = block.aes_256(kek_bytes) +/// let assert Ok(unwrapped) = block.unwrap(kek, wrapped_key) +/// ``` +pub fn unwrap( + cipher: BlockCipher, + ciphertext: BitArray, +) -> Result(BitArray, Nil) { + let size = bit_array.byte_size(ciphertext) + case size >= 24 && size % 8 == 0 { + True -> do_unwrap(cipher, ciphertext) + False -> Error(Nil) + } +} + +@target(javascript) +@external(javascript, "../kryptos_ffi.mjs", "blockCipherUnwrap") +fn do_unwrap(cipher: BlockCipher, ciphertext: BitArray) -> Result(BitArray, Nil) + +@target(erlang) +fn do_unwrap(cipher: BlockCipher, ciphertext: BitArray) -> Result(BitArray, Nil) { + let assert <> = ciphertext + let n = bit_array.byte_size(rest) / 8 + let r = split_into_blocks(rest, []) + + let #(a_final, r_final) = unwrap_rounds(cipher, a, r, n, 5) + case crypto.constant_time_equal(a_final, aes_key_wrap_iv) { + True -> Ok(bit_array.concat(r_final)) + False -> Error(Nil) + } +} + +@target(erlang) +fn unwrap_rounds( + cipher: BlockCipher, + a: BitArray, + r: List(BitArray), + n: Int, + j: Int, +) -> #(BitArray, List(BitArray)) { + case j >= 0 { + False -> #(a, r) + True -> { + let #(a_new, r_new) = + unwrap_inner(cipher, a, list.reverse(r), n, j, n, []) + unwrap_rounds(cipher, a_new, r_new, n, j - 1) + } + } +} + +@target(erlang) +fn unwrap_inner( + cipher: BlockCipher, + a: BitArray, + r: List(BitArray), + n: Int, + j: Int, + i: Int, + acc: List(BitArray), +) -> #(BitArray, List(BitArray)) { + case r { + [] -> #(a, acc) + [ri, ..rest] -> { + let t = n * j + i + let a_xored = xor_with_counter(a, t) + let b = aes_decrypt_block(cipher, <>) + let assert <> = b + unwrap_inner(cipher, a_new, rest, n, j, i - 1, [ri_new, ..acc]) + } + } +} + +@target(erlang) +fn split_into_blocks(data: BitArray, acc: List(BitArray)) -> List(BitArray) { + case data { + <> -> + split_into_blocks(rest, [block, ..acc]) + _ -> list.reverse(acc) + } +} + +@target(erlang) +@external(erlang, "kryptos_ffi", "aes_encrypt_block") +fn aes_encrypt_block(cipher: BlockCipher, block: BitArray) -> BitArray + +@target(erlang) +@external(erlang, "kryptos_ffi", "aes_decrypt_block") +fn aes_decrypt_block(cipher: BlockCipher, block: BitArray) -> BitArray + +@target(erlang) +fn xor_with_counter(a: BitArray, t: Int) -> BitArray { + let assert <> = a + let result = int.bitwise_exclusive_or(a_int, t) + <> +} + +@internal +pub fn aes_key(cipher: BlockCipher) -> BitArray { + case cipher { + Aes(key:, ..) -> key + } +} + +@internal +pub fn is_ctr(ctx: CipherContext) -> Bool { + case ctx { + Ctr(..) -> True + _ -> False + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/crypto.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/crypto.gleam new file mode 100644 index 0000000..b5f7f9d --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/crypto.gleam @@ -0,0 +1,261 @@ +//// Convenience wrappers for hashing, key derivation, random bytes, and constant-time comparison. +//// +//// - One-shot hashing via `hash()` and HMAC via `hmac()` +//// - Key derivation: HKDF (RFC 5869), PBKDF2 (RFC 8018), Concat KDF (NIST SP 800-56A) +//// - Random bytes via `random_bytes()` and UUID v4 via `random_uuid()` +//// - Constant-time comparison via `constant_time_equal()` +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/crypto +//// +//// // Generate 32 random bytes (suitable for a 256-bit key) +//// let key = crypto.random_bytes(32) +//// ``` + +import gleam/bit_array +import gleam/bytes_tree +import gleam/int +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string +import kryptos/hash.{type HashAlgorithm} +import kryptos/hmac +import kryptos/internal/concat_kdf +import kryptos/internal/hkdf +import kryptos/internal/pbkdf2 +import kryptos/internal/subtle + +/// Computes the hash digest of input data in one call. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/crypto +/// import kryptos/hash +/// +/// let assert Ok(digest) = crypto.hash(hash.Sha256, <<"hello":utf8>>) +/// ``` +pub fn hash(algorithm: HashAlgorithm, data: BitArray) -> Result(BitArray, Nil) { + use hasher <- result.try(hash.new(algorithm)) + hasher + |> hash.update(data) + |> hash.final() + |> Ok +} + +/// Computes the HMAC of input data in one call. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/crypto +/// import kryptos/hash +/// +/// let assert Ok(mac) = crypto.hmac(hash.Sha256, key: <<"secret":utf8>>, data: <<"hello":utf8>>) +/// ``` +pub fn hmac( + algorithm: HashAlgorithm, + key key: BitArray, + data data: BitArray, +) -> Result(BitArray, Nil) { + use hmac <- result.try(hmac.new(algorithm, key)) + + hmac + |> hmac.update(data) + |> hmac.final() + |> Ok +} + +/// Derives key material using HKDF (RFC 5869). +/// +/// HKDF combines an extract-then-expand approach to derive cryptographically +/// strong key material from input key material. The algorithm must be +/// HMAC-compatible. Maximum output length is 255 * hash_length bytes. +/// A `None` salt uses hash-length zeros per RFC 5869. +/// +/// ## Example +/// +/// ```gleam +/// import gleam/option +/// import kryptos/crypto +/// import kryptos/hash +/// +/// let ikm = crypto.random_bytes(32) +/// let salt = option.Some(crypto.random_bytes(16)) +/// let assert Ok(derived) = +/// crypto.hkdf(hash.Sha256, input: ikm, salt:, info: <<"app":utf8>>, length: 32) +/// ``` +pub fn hkdf( + algorithm: HashAlgorithm, + input ikm: BitArray, + salt salt: Option(BitArray), + info info: BitArray, + length length: Int, +) -> Result(BitArray, Nil) { + let hash_len = hash.byte_size(algorithm) + let max_length = 255 * hash_len + let salt_bytes = + option.lazy_unwrap(salt, fn() { + list.repeat(<<0>>, hash_len) |> bit_array.concat + }) + + case hmac.supported_hash(algorithm), length > 0, length <= max_length { + True, True, True -> hkdf.do_derive(algorithm, ikm, salt_bytes, info, length) + _, _, _ -> Error(Nil) + } +} + +/// Derives key material using Concat KDF (NIST SP 800-56A). Also called the +/// single-step or one-step key derivation function. +/// +/// Concat KDF uses a hash function to derive key material from a shared secret +/// and context-specific information. Supports SHA-1, SHA-2, and SHA-3 family +/// algorithms. Maximum output length is 255 * hash_length bytes. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/crypto +/// import kryptos/hash +/// +/// let secret = crypto.random_bytes(32) +/// let assert Ok(derived) = +/// crypto.concat_kdf(hash.Sha256, secret:, info: <<"context":utf8>>, length: 32) +/// ``` +pub fn concat_kdf( + algorithm: HashAlgorithm, + secret secret: BitArray, + info info: BitArray, + length length: Int, +) -> Result(BitArray, Nil) { + let max_length = 255 * hash.byte_size(algorithm) + + case concat_kdf_supported_hash(algorithm), length > 0, length <= max_length { + True, True, True -> + concat_kdf.derive_loop( + algorithm, + secret, + info, + length, + 1, + bytes_tree.new(), + ) + _, _, _ -> Error(Nil) + } +} + +fn concat_kdf_supported_hash(algorithm: HashAlgorithm) -> Bool { + case algorithm { + hash.Sha1 -> True + hash.Sha256 -> True + hash.Sha384 -> True + hash.Sha512 -> True + hash.Sha512x224 -> True + hash.Sha512x256 -> True + hash.Sha3x224 -> True + hash.Sha3x256 -> True + hash.Sha3x384 -> True + hash.Sha3x512 -> True + hash.Blake2b -> False + hash.Blake2s -> False + hash.Md5 -> False + hash.Shake128(_) -> False + hash.Shake256(_) -> False + } +} + +/// Derives key material from a password using PBKDF2 (RFC 8018). +/// +/// PBKDF2 applies a pseudorandom function (HMAC) to derive keys from passwords. +/// It is designed to be computationally expensive to resist brute-force attacks. +/// +/// **Note:** For password hashing in production applications, consider using +/// [Argus](https://github.com/Pevensie/argus) which provides Argon2 an +/// algorithm specifically designed for password storage. PBKDF2 is primarily +/// useful for interoperability with systems that require it. +/// +/// The algorithm must be HMAC-compatible. SHA-256 or stronger is recommended; +/// MD5 and SHA-1 are weak for password hashing. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/crypto +/// import kryptos/hash +/// +/// let salt = crypto.random_bytes(16) +/// let assert Ok(derived) = +/// crypto.pbkdf2( +/// hash.Sha256, +/// password: <<"hunter2":utf8>>, +/// salt:, +/// iterations: 100_000, +/// length: 32, +/// ) +/// ``` +pub fn pbkdf2( + algorithm: HashAlgorithm, + password password: BitArray, + salt salt: BitArray, + iterations iterations: Int, + length length: Int, +) -> Result(BitArray, Nil) { + case hmac.supported_hash(algorithm), iterations > 0, length > 0 { + True, True, True -> + pbkdf2.do_derive(algorithm, password, salt, iterations, length) + _, _, _ -> Error(Nil) + } +} + +/// Generates cryptographically secure random bytes using the platform's +/// cryptographically secure random number generator. +/// +/// A negative length returns an empty `BitArray`. +@external(erlang, "kryptos_ffi", "random_bytes") +@external(javascript, "../kryptos_ffi.mjs", "randomBytes") +pub fn random_bytes(length: Int) -> BitArray + +/// Generates a cryptographically secure random UUID v4. +@external(javascript, "../kryptos_ffi.mjs", "randomUuid") +pub fn random_uuid() -> String { + let assert <> = random_bytes(16) + let c = int.bitwise_or(int.bitwise_and(c_raw, 0x0FFF), 0x4000) + let d = int.bitwise_or(int.bitwise_and(d_raw, 0x3FFF), 0x8000) + + let uuid = + string.pad_start(int.to_base16(a), 8, "0") + <> "-" + <> string.pad_start(int.to_base16(b), 4, "0") + <> "-" + <> string.pad_start(int.to_base16(c), 4, "0") + <> "-" + <> string.pad_start(int.to_base16(d), 4, "0") + <> "-" + <> string.pad_start(int.to_base16(e), 12, "0") + + string.lowercase(uuid) +} + +/// Compares two `BitArray` in constant time. +/// +/// Use this function when comparing secrets like MACs, password hashes, +/// API tokens, or any other security-sensitive data. The comparison takes +/// the same amount of time regardless of where the arrays differ, preventing +/// timing attacks. +/// +/// ## Example +/// +/// ```gleam +/// let expected_mac = compute_mac(message, key) +/// let received_mac = get_mac_from_request() +/// +/// // Safe: constant-time comparison prevents timing attacks +/// case crypto.constant_time_equal(expected_mac, received_mac) { +/// True -> accept_message() +/// False -> reject_message() +/// } +/// ``` +pub const constant_time_equal = subtle.constant_time_equal diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/ec.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/ec.gleam new file mode 100644 index 0000000..cafe298 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/ec.gleam @@ -0,0 +1,214 @@ +//// Elliptic Curve Cryptography key generation and management. +//// +//// Key pair generation and management for elliptic curve cryptography, +//// supporting standard NIST curves and secp256k1. EC keys can be used for +//// both ECDSA signatures and ECDH key agreement. +//// +//// ## Key Generation +//// +//// ```gleam +//// import kryptos/ec +//// +//// let #(private_key, public_key) = ec.generate_key_pair(ec.P256) +//// ``` +//// +//// ## Import/Export +//// +//// ```gleam +//// import kryptos/ec +//// +//// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +//// let assert Ok(pem) = ec.to_pem(private_key) +//// let assert Ok(#(imported_private, _)) = ec.from_pem(pem) +//// ``` + +import gleam/result +import gleam/string + +/// An elliptic curve private key. +pub type PrivateKey + +/// An elliptic curve public key. +pub type PublicKey + +/// Supported elliptic curves for key generation. +pub type Curve { + /// NIST P-256 curve (secp256r1, prime256v1). 256-bit key size. + P256 + /// NIST P-384 curve (secp384r1). 384-bit key size. + P384 + /// NIST P-521 curve (secp521r1). 521-bit key size. + P521 + /// Koblitz curve used by Bitcoin and Ethereum. 256-bit key size. + Secp256k1 +} + +/// Returns the coordinate size in bytes for the given curve. +/// +/// This is the size of each coordinate (x or y) in an EC point. +pub fn coordinate_size(curve: Curve) -> Int { + case curve { + P256 | Secp256k1 -> 32 + P384 -> 48 + P521 -> 66 + } +} + +/// Generates a new elliptic curve key pair. +@external(erlang, "kryptos_ffi", "ec_generate_key_pair") +@external(javascript, "../kryptos_ffi.mjs", "ecGenerateKeyPair") +pub fn generate_key_pair(curve: Curve) -> #(PrivateKey, PublicKey) + +/// Imports an EC private key from PEM-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "ec_import_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "ecImportPrivateKeyPem") +pub fn from_pem(pem: String) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Imports an EC private key from DER-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "ec_import_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "ecImportPrivateKeyDer") +pub fn from_der(der: BitArray) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports an EC private key to PEM format. +/// +/// The key is exported in PKCS#8 format. +pub fn to_pem(key: PrivateKey) -> Result(String, Nil) { + do_to_pem(key) |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "ec_export_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "ecExportPrivateKeyPem") +fn do_to_pem(key: PrivateKey) -> Result(String, Nil) + +/// Exports an EC private key to DER format. +/// +/// The key is exported in PKCS#8 format. +@external(erlang, "kryptos_ffi", "ec_export_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "ecExportPrivateKeyDer") +pub fn to_der(key: PrivateKey) -> Result(BitArray, Nil) + +/// Imports an EC public key from PEM-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "ec_import_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "ecImportPublicKeyPem") +pub fn public_key_from_pem(pem: String) -> Result(PublicKey, Nil) + +/// Imports an EC public key from DER-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "ec_import_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "ecImportPublicKeyDer") +pub fn public_key_from_der(der: BitArray) -> Result(PublicKey, Nil) + +/// Imports an EC public key from an uncompressed SEC1 point. +/// +/// The point must be in uncompressed format: `0x04 || x || y` +/// where x and y are the coordinates padded to the curve's coordinate size. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// +/// let #(_private_key, public_key) = ec.generate_key_pair(ec.P256) +/// let point = ec.public_key_to_raw_point(public_key) +/// let assert Ok(imported) = ec.public_key_from_raw_point(ec.P256, point) +/// ``` +@external(erlang, "kryptos_ffi", "ec_public_key_from_raw_point") +@external(javascript, "../kryptos_ffi.mjs", "ecPublicKeyFromRawPoint") +pub fn public_key_from_raw_point( + curve: Curve, + point: BitArray, +) -> Result(PublicKey, Nil) + +/// Exports a public key to uncompressed SEC1 point format. +/// +/// Returns a BitArray in the format: `0x04 || X || Y` where X and Y are +/// the coordinates of the public key point, each padded to the curve's +/// coordinate size. +/// +/// If the key was imported with a compressed point format, it will be +/// automatically decompressed. +/// +/// This is the inverse of `public_key_from_raw_point`. +@external(erlang, "kryptos_ffi", "ec_public_key_to_raw_point") +@external(javascript, "../kryptos_ffi.mjs", "ecPublicKeyToRawPoint") +pub fn public_key_to_raw_point(key: PublicKey) -> BitArray + +/// Exports an EC public key to PEM format. +/// +/// The key is exported in SPKI format. +pub fn public_key_to_pem(key: PublicKey) -> Result(String, Nil) { + do_public_key_to_pem(key) + |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "ec_export_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "ecExportPublicKeyPem") +fn do_public_key_to_pem(key: PublicKey) -> Result(String, Nil) + +/// Exports an EC public key to DER format. +/// +/// The key is exported in SPKI format. +@external(erlang, "kryptos_ffi", "ec_export_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "ecExportPublicKeyDer") +pub fn public_key_to_der(key: PublicKey) -> Result(BitArray, Nil) + +/// Derives the public key from an EC private key. +@external(erlang, "kryptos_ffi", "ec_public_key_from_private") +@external(javascript, "../kryptos_ffi.mjs", "ecPublicKeyFromPrivate") +pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey + +/// Returns the curve for an EC private key. +@external(erlang, "kryptos_ffi", "ec_private_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "ecPrivateKeyCurve") +pub fn curve(key: PrivateKey) -> Curve + +/// Returns the curve for an EC public key. +@external(erlang, "kryptos_ffi", "ec_public_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "ecPublicKeyCurve") +pub fn public_key_curve(key: PublicKey) -> Curve + +/// Exports an EC private key to raw scalar bytes. +/// +/// Returns the private scalar (the "d" value in JWK terminology) as +/// big-endian bytes. The size matches the curve's coordinate size. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// +/// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +/// let scalar = ec.to_bytes(private_key) +/// ``` +@external(erlang, "kryptos_ffi", "ec_private_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "ecPrivateKeyToBytes") +pub fn to_bytes(key: PrivateKey) -> BitArray + +/// Imports an EC private key from raw scalar bytes. +/// +/// The scalar should be in big-endian format with size matching the +/// curve's coordinate size (32 bytes for P256/Secp256k1, 48 for P384, +/// 66 for P521). +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// +/// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +/// let scalar = ec.to_bytes(private_key) +/// let assert Ok(#(imported, _pub)) = ec.from_bytes(ec.P256, scalar) +/// ``` +@external(erlang, "kryptos_ffi", "ec_private_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "ecPrivateKeyFromBytes") +pub fn from_bytes( + curve: Curve, + private_bytes: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdh.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdh.gleam new file mode 100644 index 0000000..1eb9785 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdh.gleam @@ -0,0 +1,40 @@ +//// Elliptic Curve Diffie-Hellman (ECDH) key agreement. +//// +//// ECDH allows two parties to establish a shared secret over an insecure +//// channel using elliptic curve key pairs. The shared secret can then be +//// used with a key derivation function (KDF) to derive symmetric keys. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/ec +//// import kryptos/ecdh +//// +//// // Alice generates a key pair +//// let #(alice_private, alice_public) = ec.generate_key_pair(ec.P256) +//// +//// // Bob generates a key pair +//// let #(bob_private, bob_public) = ec.generate_key_pair(ec.P256) +//// +//// // Both compute the same shared secret +//// let assert Ok(alice_shared) = ecdh.compute_shared_secret(alice_private, bob_public) +//// let assert Ok(bob_shared) = ecdh.compute_shared_secret(bob_private, alice_public) +//// // alice_shared == bob_shared +//// ``` + +import kryptos/ec + +/// Computes a shared secret using ECDH key agreement. +/// +/// Both parties compute the same shared secret by combining their private key +/// with the other party's public key. The result is the x-coordinate of the +/// resulting elliptic curve point, returned as raw bytes. +/// +/// The raw shared secret should be passed through a KDF (like HKDF) before +/// use as a symmetric key. Both keys must use the same curve. +@external(erlang, "kryptos_ffi", "ecdh_compute_shared_secret") +@external(javascript, "../kryptos_ffi.mjs", "ecdhComputeSharedSecret") +pub fn compute_shared_secret( + private_key: ec.PrivateKey, + peer_public_key: ec.PublicKey, +) -> Result(BitArray, Nil) diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdsa.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdsa.gleam new file mode 100644 index 0000000..fcb3503 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/ecdsa.gleam @@ -0,0 +1,184 @@ +//// Elliptic Curve Digital Signature Algorithm (ECDSA). +//// +//// ECDSA provides digital signatures using elliptic curve cryptography, +//// offering strong security with smaller key sizes compared to RSA. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/ec +//// import kryptos/ecdsa +//// import kryptos/hash +//// +//// let #(private_key, public_key) = ec.generate_key_pair(ec.P256) +//// let message = <<"hello world":utf8>> +//// let signature = ecdsa.sign(private_key, message, hash.Sha256) +//// let valid = ecdsa.verify(public_key, message, signature, hash.Sha256) +//// // valid == True +//// ``` + +import gleam/bit_array +import gleam/bool +import gleam/result +import kryptos/ec +import kryptos/hash.{type HashAlgorithm} +import kryptos/internal/der +import kryptos/internal/utils + +/// Signs a message using ECDSA with the specified hash algorithm. +/// +/// The message is hashed internally before signing. Returns a DER-encoded +/// signature. Signatures may be non-deterministic depending on platform +/// (Erlang uses random nonces, some platforms may use deterministic +/// RFC 6979 nonces). +@external(erlang, "kryptos_ffi", "ecdsa_sign") +@external(javascript, "../kryptos_ffi.mjs", "ecdsaSign") +pub fn sign( + private_key: ec.PrivateKey, + message: BitArray, + hash: HashAlgorithm, +) -> BitArray + +/// Verifies an ECDSA signature against a message. +/// +/// The message is hashed internally before verification. The same hash +/// algorithm used during signing must be used for verification. +@external(erlang, "kryptos_ffi", "ecdsa_verify") +@external(javascript, "../kryptos_ffi.mjs", "ecdsaVerify") +pub fn verify( + public_key: ec.PublicKey, + message: BitArray, + signature signature: BitArray, + hash hash: HashAlgorithm, +) -> Bool + +/// Signs a message and returns the signature in R||S format (IEEE P1363). +/// +/// In R||S format, the signature is the concatenation of r and s values, +/// each padded to the curve's coordinate size. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// import kryptos/ecdsa +/// import kryptos/hash +/// +/// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +/// let signature = ecdsa.sign_rs(private_key, <<"hello":utf8>>, hash.Sha256) +/// ``` +pub fn sign_rs( + private_key: ec.PrivateKey, + message: BitArray, + hash: HashAlgorithm, +) -> BitArray { + let der_sig = sign(private_key, message, hash) + let curve = ec.curve(private_key) + let assert Ok(rs_sig) = der_to_rs(der_sig, curve) + rs_sig +} + +/// Verifies an R||S format signature against a message. +/// +/// The R||S format is the concatenation of r and s values, each padded +/// to the curve's coordinate size. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// import kryptos/ecdsa +/// import kryptos/hash +/// +/// let #(private_key, public_key) = ec.generate_key_pair(ec.P256) +/// let message = <<"hello":utf8>> +/// let signature = ecdsa.sign_rs(private_key, message, hash.Sha256) +/// let valid = ecdsa.verify_rs(public_key, message, signature, hash.Sha256) +/// // valid == True +/// ``` +pub fn verify_rs( + public_key: ec.PublicKey, + message: BitArray, + signature: BitArray, + hash: HashAlgorithm, +) -> Bool { + let curve = ec.public_key_curve(public_key) + case rs_to_der(signature, curve) { + Ok(der_sig) -> verify(public_key, message, der_sig, hash) + Error(Nil) -> False + } +} + +/// Converts a DER-encoded ECDSA signature to R||S format. +/// +/// R||S format concatenates the r and s integer values, each padded +/// to the curve's coordinate size with leading zeros. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// import kryptos/ecdsa +/// import kryptos/hash +/// +/// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +/// let der_sig = ecdsa.sign(private_key, <<"hello":utf8>>, hash.Sha256) +/// let assert Ok(rs_sig) = ecdsa.der_to_rs(der_sig, ec.P256) +/// ``` +pub fn der_to_rs(der_sig: BitArray, curve: ec.Curve) -> Result(BitArray, Nil) { + let coord_size = ec.coordinate_size(curve) + + use #(content, remaining) <- result.try(der.parse_sequence(der_sig)) + use <- bool.guard( + when: bit_array.byte_size(remaining) != 0, + return: Error(Nil), + ) + + use #(r_bytes, remaining) <- result.try(der.parse_integer(content)) + use #(s_bytes, remaining) <- result.try(der.parse_integer(remaining)) + use <- bool.guard( + when: bit_array.byte_size(remaining) != 0, + return: Error(Nil), + ) + + let r = utils.strip_leading_zeros(r_bytes) + let s = utils.strip_leading_zeros(s_bytes) + let r_ok = bit_array.byte_size(r) <= coord_size + let s_ok = bit_array.byte_size(s) <= coord_size + use <- bool.guard(when: !r_ok || !s_ok, return: Error(Nil)) + + Ok( + bit_array.concat([ + utils.pad_left(r, coord_size), + utils.pad_left(s, coord_size), + ]), + ) +} + +/// Converts an R||S format signature to DER encoding. +/// +/// The R||S input must be exactly 2 * coordinate_size bytes. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/ec +/// import kryptos/ecdsa +/// import kryptos/hash +/// +/// let #(private_key, _public_key) = ec.generate_key_pair(ec.P256) +/// let rs_sig = ecdsa.sign_rs(private_key, <<"hello":utf8>>, hash.Sha256) +/// let assert Ok(der_sig) = ecdsa.rs_to_der(rs_sig, ec.P256) +/// ``` +pub fn rs_to_der(rs: BitArray, curve: ec.Curve) -> Result(BitArray, Nil) { + let coord_size = ec.coordinate_size(curve) + + case rs { + <> -> { + use r_encoded <- result.try(der.encode_integer(r)) + use s_encoded <- result.try(der.encode_integer(s)) + der.encode_sequence(bit_array.concat([r_encoded, s_encoded])) + } + _ -> Error(Nil) + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/eddsa.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/eddsa.gleam new file mode 100644 index 0000000..cc7088b --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/eddsa.gleam @@ -0,0 +1,198 @@ +//// Edwards-curve Digital Signature Algorithm (EdDSA). +//// +//// EdDSA provides digital signatures using Edwards curves Ed25519 and Ed448. +//// Unlike ECDSA, EdDSA has built-in hashing (SHA-512 for Ed25519, SHAKE256 +//// for Ed448) and produces deterministic signatures. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/eddsa +//// +//// let #(private_key, public_key) = eddsa.generate_key_pair(eddsa.Ed25519) +//// let message = <<"hello world":utf8>> +//// let signature = eddsa.sign(private_key, message) +//// let valid = eddsa.verify(public_key, message, signature) +//// // valid == True +//// ``` + +import gleam/result +import gleam/string + +/// An EdDSA private key. +pub type PrivateKey + +/// An EdDSA public key. +pub type PublicKey + +/// Supported curves for EdDSA signatures. +pub type Curve { + /// Ed25519 curve. 32-byte keys, 64-byte signatures. + Ed25519 + /// Ed448 curve. 57-byte keys, 114-byte signatures. + Ed448 +} + +/// Returns the key size in bytes for the given curve. +pub fn key_size(curve: Curve) -> Int { + case curve { + Ed25519 -> 32 + Ed448 -> 57 + } +} + +/// Generates a new EdDSA key pair. +@external(erlang, "kryptos_ffi", "eddsa_generate_key_pair") +@external(javascript, "../kryptos_ffi.mjs", "eddsaGenerateKeyPair") +pub fn generate_key_pair(curve: Curve) -> #(PrivateKey, PublicKey) + +/// Signs a message using EdDSA. +/// +/// The message is hashed internally using the curve's built-in hash function +/// (SHA-512 for Ed25519, SHAKE256 for Ed448). Signatures are deterministic: +/// signing the same message with the same key always produces the same signature. +@external(erlang, "kryptos_ffi", "eddsa_sign") +@external(javascript, "../kryptos_ffi.mjs", "eddsaSign") +pub fn sign(private_key: PrivateKey, message: BitArray) -> BitArray + +/// Verifies an EdDSA signature against a message. +@external(erlang, "kryptos_ffi", "eddsa_verify") +@external(javascript, "../kryptos_ffi.mjs", "eddsaVerify") +pub fn verify( + public_key: PublicKey, + message: BitArray, + signature signature: BitArray, +) -> Bool + +/// Imports a private key from raw bytes. +/// +/// The bytes should be the raw private key seed: +/// - Ed25519: 32 bytes +/// - Ed448: 57 bytes +/// +/// Returns the private key and its corresponding public key, or `Error(Nil)` +/// if the bytes are invalid. +@external(erlang, "kryptos_ffi", "eddsa_private_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPrivateKeyFromBytes") +pub fn from_bytes( + curve: Curve, + private_bytes: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports a private key to raw bytes. +/// +/// Returns the raw private key seed: +/// - Ed25519: 32 bytes +/// - Ed448: 57 bytes +@external(erlang, "kryptos_ffi", "eddsa_private_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPrivateKeyToBytes") +pub fn to_bytes(key: PrivateKey) -> BitArray + +/// Imports a public key from raw bytes. +/// +/// The bytes should be the raw public key point: +/// - Ed25519: 32 bytes +/// - Ed448: 57 bytes +/// +/// Returns the public key or `Error(Nil)` if the bytes have an invalid length. +/// +/// **Security note:** This function only validates the byte length, not that the +/// bytes encode a valid curve point or that the key is in the prime-order +/// subgroup. Callers that accept public keys from untrusted sources should +/// perform their own validation to reject small-order or invalid points, as +/// such keys can allow trivial signature forgery. +@external(erlang, "kryptos_ffi", "eddsa_public_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPublicKeyFromBytes") +pub fn public_key_from_bytes( + curve: Curve, + public_bytes: BitArray, +) -> Result(PublicKey, Nil) + +/// Exports a public key to raw bytes. +/// +/// Returns the raw public key point: +/// - Ed25519: 32 bytes +/// - Ed448: 57 bytes +@external(erlang, "kryptos_ffi", "eddsa_public_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPublicKeyToBytes") +pub fn public_key_to_bytes(key: PublicKey) -> BitArray + +/// Imports an EdDSA private key from PEM-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "eddsa_import_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "eddsaImportPrivateKeyPem") +pub fn from_pem(pem: String) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Imports an EdDSA private key from DER-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "eddsa_import_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "eddsaImportPrivateKeyDer") +pub fn from_der(der: BitArray) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports an EdDSA private key to PEM format. +/// +/// The key is exported in PKCS#8 format. +pub fn to_pem(key: PrivateKey) -> Result(String, Nil) { + do_to_pem(key) |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "eddsa_export_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "eddsaExportPrivateKeyPem") +fn do_to_pem(key: PrivateKey) -> Result(String, Nil) + +/// Exports an EdDSA private key to DER format. +/// +/// The key is exported in PKCS#8 format. +@external(erlang, "kryptos_ffi", "eddsa_export_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "eddsaExportPrivateKeyDer") +pub fn to_der(key: PrivateKey) -> Result(BitArray, Nil) + +/// Imports an EdDSA public key from PEM-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "eddsa_import_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "eddsaImportPublicKeyPem") +pub fn public_key_from_pem(pem: String) -> Result(PublicKey, Nil) + +/// Imports an EdDSA public key from DER-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "eddsa_import_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "eddsaImportPublicKeyDer") +pub fn public_key_from_der(der: BitArray) -> Result(PublicKey, Nil) + +/// Exports an EdDSA public key to PEM format. +/// +/// The key is exported in SPKI format. +pub fn public_key_to_pem(key: PublicKey) -> Result(String, Nil) { + do_public_key_to_pem(key) + |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "eddsa_export_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "eddsaExportPublicKeyPem") +fn do_public_key_to_pem(key: PublicKey) -> Result(String, Nil) + +/// Exports an EdDSA public key to DER format. +/// +/// The key is exported in SPKI format. +@external(erlang, "kryptos_ffi", "eddsa_export_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "eddsaExportPublicKeyDer") +pub fn public_key_to_der(key: PublicKey) -> Result(BitArray, Nil) + +/// Derives the public key from an EdDSA private key. +@external(erlang, "kryptos_ffi", "eddsa_public_key_from_private") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPublicKeyFromPrivate") +pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey + +/// Returns the curve for an EdDSA private key. +@external(erlang, "kryptos_ffi", "eddsa_private_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPrivateKeyCurve") +pub fn curve(key: PrivateKey) -> Curve + +/// Returns the curve for an EdDSA public key. +@external(erlang, "kryptos_ffi", "eddsa_public_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "eddsaPublicKeyCurve") +pub fn public_key_curve(key: PublicKey) -> Curve diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/hash.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/hash.gleam new file mode 100644 index 0000000..49f8699 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/hash.gleam @@ -0,0 +1,160 @@ +//// Cryptographic hash functions. +//// +//// Hash functions take arbitrary input data and produce a fixed-size digest. +//// Use these for data integrity verification, fingerprinting, and as building +//// blocks for other cryptographic constructs like HMAC. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/hash +//// +//// let assert Ok(h) = hash.new(hash.Sha256) +//// let digest = h |> hash.update(<<"hello":utf8>>) |> hash.final() +//// ``` + +/// Supported cryptographic hash algorithms. +pub type HashAlgorithm { + /// BLAKE2b (512-bit output) + Blake2b + /// BLAKE2s (256-bit output) + Blake2s + /// MD5 (128-bit output), cryptographically broken - use only for legacy compatibility. + Md5 + /// SHA-1 (160-bit output) + Sha1 + /// SHA-256 (256-bit output) + Sha256 + /// SHA-384 (384-bit output) + Sha384 + /// SHA-512 (512-bit output) + Sha512 + /// SHA-512/224 (224-bit output), truncated SHA-512. + Sha512x224 + /// SHA-512/256 (256-bit output), truncated SHA-512. + Sha512x256 + /// SHA3-224 (224-bit output) + Sha3x224 + /// SHA3-256 (256-bit output) + Sha3x256 + /// SHA3-384 (384-bit output) + Sha3x384 + /// SHA3-512 (512-bit output) + Sha3x512 + /// SHAKE128 extendable-output function (128-bit security). + /// The output_length parameter specifies the desired digest length in bytes. + /// Prefer using the `shake_128` smart constructor to validate the output length. + Shake128(output_length: Int) + /// SHAKE256 extendable-output function (256-bit security). + /// The output_length parameter specifies the desired digest length in bytes. + /// Prefer using the `shake_256` smart constructor to validate the output length. + Shake256(output_length: Int) +} + +/// Creates a SHAKE128 hash algorithm with the given output length in bytes. +/// +/// The output length must be greater than zero. +pub fn shake_128(output_length length: Int) -> Result(HashAlgorithm, Nil) { + case length > 0 { + True -> Ok(Shake128(length)) + False -> Error(Nil) + } +} + +/// Creates a SHAKE256 hash algorithm with the given output length in bytes. +/// +/// The output length must be greater than zero. +pub fn shake_256(output_length length: Int) -> Result(HashAlgorithm, Nil) { + case length > 0 { + True -> Ok(Shake256(length)) + False -> Error(Nil) + } +} + +@internal +pub fn algorithm_name(algorithm: HashAlgorithm) -> String { + case algorithm { + Blake2b -> "blake2b512" + Blake2s -> "blake2s256" + Md5 -> "md5" + Sha1 -> "sha1" + Sha256 -> "sha256" + Sha384 -> "sha384" + Sha512 -> "sha512" + Sha512x224 -> "sha512-224" + Sha512x256 -> "sha512-256" + Sha3x224 -> "sha3-224" + Sha3x256 -> "sha3-256" + Sha3x384 -> "sha3-384" + Sha3x512 -> "sha3-512" + Shake128(_) -> "shake128" + Shake256(_) -> "shake256" + } +} + +/// Returns the output size in bytes for a hash algorithm. +pub fn byte_size(algorithm: HashAlgorithm) -> Int { + case algorithm { + Blake2b -> 64 + Blake2s -> 32 + Md5 -> 16 + Sha1 -> 20 + Sha256 -> 32 + Sha384 -> 48 + Sha512 -> 64 + Sha512x224 -> 28 + Sha512x256 -> 32 + Sha3x224 -> 28 + Sha3x256 -> 32 + Sha3x384 -> 48 + Sha3x512 -> 64 + Shake128(output_length) -> output_length + Shake256(output_length) -> output_length + } +} + +/// Represents an in-progress hash computation. +/// +/// Use `new` to create a hasher, `update` to add data, and `final` to get the digest. +pub type Hasher + +/// Creates a new hasher for incremental hashing. +/// +/// Use this when you need to hash data in chunks, such as when streaming +/// or when the full input isn't available at once. +pub fn new(algorithm: HashAlgorithm) -> Result(Hasher, Nil) { + case algorithm { + Shake128(output_length) | Shake256(output_length) if output_length <= 0 -> + Error(Nil) + _ -> do_new(algorithm) + } +} + +@external(erlang, "kryptos_ffi", "hash_new") +@external(javascript, "../kryptos_ffi.mjs", "hashNew") +fn do_new(algorithm: HashAlgorithm) -> Result(Hasher, Nil) + +/// Adds data to an in-progress hash computation. +/// +/// Can be called multiple times to incrementally hash data. +@external(erlang, "kryptos_ffi", "hash_update") +@external(javascript, "../kryptos_ffi.mjs", "hashUpdate") +pub fn update(hasher: Hasher, data: BitArray) -> Hasher + +/// Finalizes the hash computation and returns the digest. +/// +/// After calling this function, the hasher should not be reused. +@external(erlang, "kryptos_ffi", "hash_final") +@external(javascript, "../kryptos_ffi.mjs", "hashFinal") +pub fn final(hasher: Hasher) -> BitArray + +/// Checks if a hash algorithm is supported by the current runtime. +/// +/// Some algorithms may not be available depending on the platform or +/// OpenSSL/crypto library version. +pub fn is_supported(algorithm: HashAlgorithm) -> Bool { + case new(algorithm) { + Ok(_) -> True + Error(_) -> False + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/hmac.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/hmac.gleam new file mode 100644 index 0000000..07e42a0 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/hmac.gleam @@ -0,0 +1,94 @@ +//// Hash-based Message Authentication Code (HMAC). +//// +//// HMAC provides message authentication using a cryptographic hash function +//// combined with a secret key. Use it to verify both data integrity and +//// authenticity. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/hmac +//// import kryptos/hash +//// +//// let assert Ok(h) = hmac.new(hash.Sha256, <<"secret key":utf8>>) +//// let mac = h |> hmac.update(<<"hello":utf8>>) |> hmac.final() +//// ``` + +import gleam/result +import kryptos/hash.{type HashAlgorithm} +import kryptos/internal/subtle + +/// Checks if a hash algorithm is supported for HMAC operations. +pub fn supported_hash(algorithm: HashAlgorithm) -> Bool { + case algorithm { + hash.Md5 -> True + hash.Sha1 -> True + hash.Sha256 -> True + hash.Sha384 -> True + hash.Sha512 -> True + hash.Sha512x224 -> True + hash.Sha512x256 -> True + hash.Blake2b -> False + hash.Blake2s -> False + hash.Sha3x224 -> False + hash.Sha3x256 -> False + hash.Sha3x384 -> False + hash.Sha3x512 -> False + hash.Shake128(_) -> False + hash.Shake256(_) -> False + } +} + +/// Represents an in-progress HMAC computation. +/// +/// Use `new` to create an HMAC, `update` to add data, and `final` to get the MAC. +pub type Hmac + +/// Creates a new HMAC for incremental authentication. +/// +/// Use this when you need to authenticate data in chunks, such as when streaming +/// or when the full input isn't available at once. +pub fn new(algorithm: HashAlgorithm, key: BitArray) -> Result(Hmac, Nil) { + case supported_hash(algorithm) { + True -> do_new(algorithm, key) + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "hmac_new") +@external(javascript, "../kryptos_ffi.mjs", "hmacNew") +fn do_new(algorithm: HashAlgorithm, key: BitArray) -> Result(Hmac, Nil) + +/// Adds data to an in-progress HMAC computation. +/// +/// Can be called multiple times to incrementally authenticate data. +@external(erlang, "crypto", "mac_update") +@external(javascript, "../kryptos_ffi.mjs", "hmacUpdate") +pub fn update(hmac: Hmac, data: BitArray) -> Hmac + +/// Finalizes the HMAC computation and returns the authentication code. +/// +/// After calling this function, the HMAC should not be reused. +@external(erlang, "crypto", "mac_final") +@external(javascript, "../kryptos_ffi.mjs", "hmacFinal") +pub fn final(hmac: Hmac) -> BitArray + +/// Verifies that a MAC matches the expected value using constant-time comparison. +/// +/// Computes the HMAC and compares it to the expected value in constant time +/// to prevent timing attacks. +pub fn verify( + algorithm: HashAlgorithm, + key key: BitArray, + data data: BitArray, + expected expected: BitArray, +) -> Result(Bool, Nil) { + use hmac_state <- result.try(new(algorithm, key)) + + let actual = + hmac_state + |> update(data) + |> final() + + Ok(subtle.constant_time_equal(actual, expected)) +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/concat_kdf.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/concat_kdf.gleam new file mode 100644 index 0000000..eee77c6 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/concat_kdf.gleam @@ -0,0 +1,53 @@ +//// Internal Concat KDF (NIST SP 800-56A) implementation. +//// +//// Implements the single-step key derivation function from NIST SP 800-56A. +//// Used internally by the crypto module for deriving cryptographic keys +//// from shared secrets (e.g., from ECDH key agreement). + +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree.{type BytesTree} +import gleam/result +import kryptos/hash.{type HashAlgorithm} + +pub fn derive_loop( + algorithm: HashAlgorithm, + secret: BitArray, + info: BitArray, + remaining: Int, + counter: Int, + acc: BytesTree, +) -> Result(BitArray, Nil) { + use <- bool.guard( + when: remaining <= 0, + return: Ok(bytes_tree.to_bit_array(acc)), + ) + + // Hash(counter_32bit_BE || secret || OtherInfo) + let input = bit_array.concat([<>, secret, info]) + use hasher <- result.try(hash.new(algorithm)) + let block = + hasher + |> hash.update(input) + |> hash.final() + + let length = bit_array.byte_size(block) + case remaining <= length { + True -> { + let assert Ok(final_block) = bit_array.slice(block, 0, remaining) + bytes_tree.append(acc, final_block) + |> bytes_tree.to_bit_array + |> Ok + } + False -> { + derive_loop( + algorithm, + secret, + info, + remaining - length, + counter + 1, + bytes_tree.append(acc, block), + ) + } + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/der.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/der.gleam new file mode 100644 index 0000000..83ac1e8 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/der.gleam @@ -0,0 +1,784 @@ +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree +import gleam/int +import gleam/list +import gleam/result +import gleam/string +import gleam/time/calendar +import gleam/time/timestamp.{type Timestamp} +import kryptos/internal/utils + +const boolean_tag = 0x01 + +const integer_tag = 0x02 + +const bit_string_tag = 0x03 + +const octet_string_tag = 0x04 + +const oid_tag = 0x06 + +const utf8_string_tag = 0x0c + +const printable_string_tag = 0x13 + +const teletex_string_tag = 0x14 + +const ia5_string_tag = 0x16 + +const utc_time_tag = 0x17 + +const generalized_time_tag = 0x18 + +const universal_string_tag = 0x1c + +const bmp_string_tag = 0x1e + +const sequence_tag = 0x30 + +const set_tag = 0x31 + +/// Encode a length in DER format. +/// +/// Supports lengths up to 65,535 bytes (sufficient for X.509 structures). +/// Returns Error(Nil) for lengths exceeding this limit. +pub fn encode_length(len: Int) -> Result(BitArray, Nil) { + case len { + l if l < 0 -> Error(Nil) + l if l < 128 -> Ok(<>) + l if l < 256 -> Ok(<<0x81, l:8>>) + l if l <= 65_535 -> Ok(<<0x82, l:16>>) + _ -> Error(Nil) + } +} + +/// Parse DER length encoding, returning (length, remaining bytes). +/// +/// Supports short form (1 byte) and long form (0x81 + 1 byte, 0x82 + 2 bytes). +/// Rejects non-canonical encodings (e.g., 0x81 for values < 128). +pub fn parse_length(bytes: BitArray) -> Result(#(Int, BitArray), Nil) { + case bytes { + <> if len < 128 -> Ok(#(len, rest)) + <<0x81, len:8, rest:bits>> if len >= 128 -> Ok(#(len, rest)) + <<0x82, len:16, rest:bits>> if len >= 256 -> Ok(#(len, rest)) + _ -> Error(Nil) + } +} + +/// Encode a boolean as a DER BOOLEAN. +pub fn encode_bool(value: Bool) -> BitArray { + case value { + True -> <> + False -> <> + } +} + +/// Parse a DER BOOLEAN, returning (value, remaining bytes). +/// +/// Accepts any non-zero value as TRUE for BER interoperability, +/// as some certificates in the wild use non-0xFF values for TRUE. +pub fn parse_bool(bytes: BitArray) -> Result(#(Bool, BitArray), Nil) { + use rest <- require_tag(bytes, boolean_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len != 1, return: Error(Nil)) + + case content { + <<0x00, remaining:bits>> -> Ok(#(False, remaining)) + <<_value:8, remaining:bits>> -> Ok(#(True, remaining)) + _ -> Error(Nil) + } +} + +/// Encode bytes as a DER INTEGER. +/// +/// Strips leading zeros and adds 0x00 prefix if high bit is set (to keep positive). +pub fn encode_integer(value: BitArray) -> Result(BitArray, Nil) { + let stripped = utils.strip_leading_zeros(value) + + let int_bytes = case stripped { + <> if high >= 128 -> bit_array.concat([<<0x00>>, stripped]) + <<>> -> <<0x00>> + _ -> stripped + } + + use len_bytes <- result.try(encode_length(bit_array.byte_size(int_bytes))) + Ok(bit_array.concat([<>, len_bytes, int_bytes])) +} + +/// Encode a non-negative Int as a DER INTEGER. +/// +/// Supports values from 0 to 0xFFFFFFFF (4 bytes). +/// Returns Error(Nil) for negative values or values exceeding 32 bits. +pub fn encode_small_int(n: Int) -> Result(BitArray, Nil) { + case n { + _ if n < 0 -> Error(Nil) + _ if n < 0x100 -> encode_integer(<>) + _ if n < 0x10000 -> encode_integer(<>) + _ if n < 0x1000000 -> encode_integer(<>) + _ if n < 0x1_0000_0000 -> encode_integer(<>) + _ -> Error(Nil) + } +} + +/// Parse a DER INTEGER, returning (value bytes, remaining bytes). +/// +/// The returned value bytes may have a leading 0x00 if the high bit was set. +/// Rejects zero-length integers and non-minimal leading zero padding. +pub fn parse_integer(bytes: BitArray) -> Result(#(BitArray, BitArray), Nil) { + use rest <- require_tag(bytes, integer_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len <= 0, return: Error(Nil)) + + let content_size = bit_array.byte_size(content) + use <- bool.guard(when: content_size < len, return: Error(Nil)) + + // Safety: Prior guard ensures content_size >= len, so slice succeeds + let assert Ok(value) = bit_array.slice(content, 0, len) + use <- reject_non_minimal_zeros(value) + + // Safety: Prior guard ensures content_size >= len, so slice succeeds + let assert Ok(remaining) = bit_array.slice(content, len, content_size - len) + Ok(#(value, remaining)) +} + +/// Wrap content in a DER SEQUENCE. +pub fn encode_sequence(content: BitArray) -> Result(BitArray, Nil) { + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse the content of a DER SEQUENCE, returning (inner bytes, remaining bytes). +pub fn parse_sequence(bytes: BitArray) -> Result(#(BitArray, BitArray), Nil) { + use rest <- require_tag(bytes, sequence_tag) + use #(len, content) <- result.try(parse_length(rest)) + + let content_size = bit_array.byte_size(content) + use <- bool.guard(when: content_size < len, return: Error(Nil)) + + // Safety: Prior guard ensures content_size >= len, so slices succeed + let assert Ok(inner) = bit_array.slice(content, 0, len) + let assert Ok(remaining) = bit_array.slice(content, len, content_size - len) + Ok(#(inner, remaining)) +} + +/// Wrap content in a DER SET. +pub fn encode_set(content: BitArray) -> Result(BitArray, Nil) { + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse the content of a DER SET, returning (inner bytes, remaining bytes). +pub fn parse_set(bytes: BitArray) -> Result(#(BitArray, BitArray), Nil) { + use rest <- require_tag(bytes, set_tag) + use #(len, content) <- result.try(parse_length(rest)) + parse_content(content, len) +} + +/// Encode a BIT STRING. +/// +/// Handles non-byte-aligned bit arrays by calculating and encoding +/// the appropriate padding bits. +pub fn encode_bit_string(value: BitArray) -> Result(BitArray, Nil) { + let bit_size = bit_array.bit_size(value) + let unused_bits = case bit_size % 8 { + 0 -> 0 + remainder -> 8 - remainder + } + let padded = bit_array.pad_to_bytes(value) + let content = <> + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse a DER BIT STRING, returning (value bytes, remaining bytes). +/// +/// The first byte of a BIT STRING indicates unused bits; this function +/// only accepts 0 unused bits and strips that byte from the result. +pub fn parse_bit_string(bytes: BitArray) -> Result(#(BitArray, BitArray), Nil) { + use rest <- require_tag(bytes, bit_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len < 1, return: Error(Nil)) + + let content_size = bit_array.byte_size(content) + use <- bool.guard(when: content_size < len, return: Error(Nil)) + + case content { + <<0x00, value:bytes-size(len - 1), remaining:bits>> -> + Ok(#(value, remaining)) + _ -> Error(Nil) + } +} + +/// Encode an OCTET STRING. +pub fn encode_octet_string(value: BitArray) -> Result(BitArray, Nil) { + use len_bytes <- result.try(encode_length(bit_array.byte_size(value))) + Ok(bit_array.concat([<>, len_bytes, value])) +} + +/// Parse a DER OCTET STRING, returning (value bytes, remaining bytes). +pub fn parse_octet_string(bytes: BitArray) -> Result(#(BitArray, BitArray), Nil) { + use rest <- require_tag(bytes, octet_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + parse_content(content, len) +} + +/// Encode a UTF8String. +pub fn encode_utf8_string(value: String) -> Result(BitArray, Nil) { + let content = bit_array.from_string(value) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse a DER UTF8String, returning (string value, remaining bytes). +pub fn parse_utf8_string(bytes: BitArray) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, utf8_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(bit_array.to_string(value_bytes)) + Ok(#(value, remaining)) +} + +/// Check if a codepoint is valid for PrintableString per RFC 5280. +fn is_printable_char(codepoint: Int) -> Bool { + case codepoint { + // A-Z + c if c >= 65 && c <= 90 -> True + // a-z + c if c >= 97 && c <= 122 -> True + // 0-9 + c if c >= 48 && c <= 57 -> True + // space + 32 -> True + // ' + 39 -> True + // ( ) + 40 | 41 -> True + // + + 43 -> True + // , + 44 -> True + // - + 45 -> True + // . + 46 -> True + // / + 47 -> True + // : + 58 -> True + // = + 61 -> True + // ? + 63 -> True + _ -> False + } +} + +fn is_valid_printable_string(value: BitArray) -> Bool { + case value { + <<>> -> True + <> -> + is_printable_char(byte) && is_valid_printable_string(rest) + _ -> False + } +} + +/// Encode a PrintableString (ASCII subset per RFC 5280). +/// Returns Error(Nil) if the string contains characters not allowed +/// in PrintableString. +pub fn encode_printable_string(value: String) -> Result(BitArray, Nil) { + let content = bit_array.from_string(value) + use <- bool.guard( + when: !is_valid_printable_string(content), + return: Error(Nil), + ) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse a DER PrintableString, returning (string value, remaining bytes). +/// Note: does not enforce the PrintableString charset (A-Z, a-z, 0-9, +/// space, '()+,-./:=?) to remain liberal in what we accept per Postel's law. +pub fn parse_printable_string( + bytes: BitArray, +) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, printable_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(bit_array.to_string(value_bytes)) + Ok(#(value, remaining)) +} + +/// Encode an IA5String (ASCII). +pub fn encode_ia5_string(value: String) -> Result(BitArray, Nil) { + let content = bit_array.from_string(value) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse a DER IA5String, returning (string value, remaining bytes). +/// Note: does not enforce the IA5 (ASCII-only) constraint to remain +/// liberal in what we accept per Postel's law. +pub fn parse_ia5_string(bytes: BitArray) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, ia5_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(bit_array.to_string(value_bytes)) + Ok(#(value, remaining)) +} + +/// Parse a DER TeletexString (T61String), returning (string value, remaining bytes). +/// +/// TeletexString uses ISO 8859-1 (Latin-1) encoding, where each byte represents +/// one character. This is a decode-only function for legacy certificate compatibility. +pub fn parse_teletex_string(bytes: BitArray) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, teletex_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(latin1_to_utf8(value_bytes)) + Ok(#(value, remaining)) +} + +/// Parse a DER BMPString, returning (string value, remaining bytes). +/// +/// BMPString uses UCS-2 big-endian encoding (2 bytes per character). +/// This is a decode-only function for legacy certificate compatibility. +pub fn parse_bmp_string(bytes: BitArray) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, bmp_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len % 2 != 0, return: Error(Nil)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(ucs2_to_utf8(value_bytes)) + Ok(#(value, remaining)) +} + +/// Parse a DER UniversalString, returning (string value, remaining bytes). +/// +/// UniversalString uses UCS-4 big-endian encoding (4 bytes per character). +/// This is a decode-only function for legacy certificate compatibility. +pub fn parse_universal_string( + bytes: BitArray, +) -> Result(#(String, BitArray), Nil) { + use rest <- require_tag(bytes, universal_string_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len % 4 != 0, return: Error(Nil)) + use #(value_bytes, remaining) <- result.try(parse_content(content, len)) + use value <- result.try(ucs4_to_utf8(value_bytes)) + Ok(#(value, remaining)) +} + +/// Encode a DER Timestamp, returning a BitArray. +pub fn encode_timestamp(timestamp: Timestamp) -> Result(BitArray, Nil) { + let #(date, _time) = timestamp.to_calendar(timestamp, calendar.utc_offset) + case date.year >= 1950 && date.year < 2050 { + True -> encode_utc_time(timestamp) + False -> encode_generalized_time(timestamp) + } +} + +/// Format: YYMMDDHHMMSSZ +fn encode_utc_time(timestamp: Timestamp) -> Result(BitArray, Nil) { + let #(date, time) = timestamp.to_calendar(timestamp, calendar.utc_offset) + let yy = date.year % 100 + let pad2 = utils.int_to_padded_string(_, 2) + let content = + pad2(yy) + <> pad2(calendar.month_to_int(date.month)) + <> pad2(date.day) + <> pad2(time.hours) + <> pad2(time.minutes) + <> pad2(time.seconds) + <> "Z" + let bytes = bit_array.from_string(content) + use len_bytes <- result.try(encode_length(bit_array.byte_size(bytes))) + Ok(bit_array.concat([<>, len_bytes, bytes])) +} + +/// Parse a UTCTime, returning (Timestamp, remaining). +/// UTCTime uses 2-digit years: 00-49 = 2000-2049, 50-99 = 1950-1999. +pub fn parse_utc_time(bytes: BitArray) -> Result(#(Timestamp, BitArray), Nil) { + use rest <- require_tag(bytes, utc_time_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len != 13, return: Error(Nil)) + use #(time_bytes, remaining) <- result.try(parse_content(content, len)) + use time_str <- result.try(bit_array.to_string(time_bytes)) + use <- bool.guard(when: !string.ends_with(time_str, "Z"), return: Error(Nil)) + + use yy <- result.try(int.parse(string.slice(time_str, 0, 2))) + use month_int <- result.try(int.parse(string.slice(time_str, 2, 2))) + use month <- result.try(calendar.month_from_int(month_int)) + use day <- result.try(int.parse(string.slice(time_str, 4, 2))) + use hour <- result.try(int.parse(string.slice(time_str, 6, 2))) + use minute <- result.try(int.parse(string.slice(time_str, 8, 2))) + use second <- result.try(int.parse(string.slice(time_str, 10, 2))) + + let year = case yy >= 50 { + True -> 1900 + yy + False -> 2000 + yy + } + + let ts = + timestamp.from_calendar( + calendar.Date(year:, month:, day:), + calendar.TimeOfDay( + hours: hour, + minutes: minute, + seconds: second, + nanoseconds: 0, + ), + calendar.utc_offset, + ) + + Ok(#(ts, remaining)) +} + +/// Format: YYYYMMDDHHMMSSZ +pub fn encode_generalized_time(timestamp: Timestamp) -> Result(BitArray, Nil) { + let #(date, time) = timestamp.to_calendar(timestamp, calendar.utc_offset) + let pad2 = utils.int_to_padded_string(_, 2) + let pad4 = utils.int_to_padded_string(_, 4) + let content = + pad4(date.year) + <> pad2(calendar.month_to_int(date.month)) + <> pad2(date.day) + <> pad2(time.hours) + <> pad2(time.minutes) + <> pad2(time.seconds) + <> "Z" + let bytes = bit_array.from_string(content) + use len_bytes <- result.try(encode_length(bit_array.byte_size(bytes))) + Ok(bit_array.concat([<>, len_bytes, bytes])) +} + +/// Parse a GeneralizedTime, returning (Timestamp, remaining). +pub fn parse_generalized_time( + bytes: BitArray, +) -> Result(#(Timestamp, BitArray), Nil) { + use rest <- require_tag(bytes, generalized_time_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len != 15, return: Error(Nil)) + use #(time_bytes, remaining) <- result.try(parse_content(content, len)) + use time_str <- result.try(bit_array.to_string(time_bytes)) + use <- bool.guard(when: !string.ends_with(time_str, "Z"), return: Error(Nil)) + + use year <- result.try(int.parse(string.slice(time_str, 0, 4))) + use month_int <- result.try(int.parse(string.slice(time_str, 4, 2))) + use month <- result.try(calendar.month_from_int(month_int)) + use day <- result.try(int.parse(string.slice(time_str, 6, 2))) + use hour <- result.try(int.parse(string.slice(time_str, 8, 2))) + use minute <- result.try(int.parse(string.slice(time_str, 10, 2))) + use second <- result.try(int.parse(string.slice(time_str, 12, 2))) + + let timestamp = + timestamp.from_calendar( + calendar.Date(year:, month:, day:), + calendar.TimeOfDay( + hours: hour, + minutes: minute, + seconds: second, + nanoseconds: 0, + ), + calendar.utc_offset, + ) + + Ok(#(timestamp, remaining)) +} + +/// Encode an OID (Object Identifier). +/// +/// OID components are encoded per ITU-T X.690 Section 8.19.4: first*40 + second +/// for the first subidentifier, then base-128 with continuation bits for the rest. +/// Returns Error(Nil) for invalid OIDs (fewer than 2 components, or root arc +/// values violating ITU-T X.660: first must be 0-2, second must be 0-39 when +/// first is 0 or 1). +pub fn encode_oid(components: List(Int)) -> Result(BitArray, Nil) { + case components { + [first, second, ..rest] + if first >= 0 + && first <= 2 + && second >= 0 + && { first == 2 || second <= 39 } + -> { + let first_value = first * 40 + second + let first_bytes = encode_oid_component(first_value) + let rest_bytes = list.flat_map(rest, encode_oid_component) + let content = + bit_array.concat([ + bytes_from_list(first_bytes), + bytes_from_list(rest_bytes), + ]) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) + } + _ -> Error(Nil) + } +} + +/// Parse a DER OID, returning (components list, remaining bytes). +pub fn parse_oid(bytes: BitArray) -> Result(#(List(Int), BitArray), Nil) { + use rest <- require_tag(bytes, oid_tag) + use #(len, content) <- result.try(parse_length(rest)) + use <- bool.guard(when: len < 1, return: Error(Nil)) + + let content_size = bit_array.byte_size(content) + use <- bool.guard(when: content_size < len, return: Error(Nil)) + + // Safety: Prior guard ensures content_size >= len, so slices succeed + let assert Ok(oid_bytes) = bit_array.slice(content, 0, len) + let assert Ok(remaining) = bit_array.slice(content, len, content_size - len) + + use components <- result.try(decode_oid_components(oid_bytes)) + Ok(#(components, remaining)) +} + +/// Encode a context-specific tag (e.g., [0], [1]). +/// +/// Uses constructed form (tag | 0xA0). +pub fn encode_context_tag(tag: Int, content: BitArray) -> Result(BitArray, Nil) { + let tag_byte = int.bitwise_or(0xa0, tag) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse a context-specific constructed tag (e.g., [0], [1]). +/// +/// Returns (inner bytes, remaining bytes) if the tag matches. +pub fn parse_context_tag( + bytes: BitArray, + tag: Int, +) -> Result(#(BitArray, BitArray), Nil) { + let tag_byte = int.bitwise_or(0xa0, tag) + use rest <- require_tag(bytes, tag_byte) + use #(len, content) <- result.try(parse_length(rest)) + parse_content(content, len) +} + +/// Encode a context-specific primitive tag (e.g., [0], [2] for SANs). +/// +/// Uses primitive form (tag | 0x80). +pub fn encode_context_primitive_tag( + tag: Int, + content: BitArray, +) -> Result(BitArray, Nil) { + let tag_byte = int.bitwise_or(0x80, tag) + use len_bytes <- result.try(encode_length(bit_array.byte_size(content))) + Ok(bit_array.concat([<>, len_bytes, content])) +} + +/// Parse content of a given length, returning (value, remaining bytes). +pub fn parse_content( + content: BitArray, + len: Int, +) -> Result(#(BitArray, BitArray), Nil) { + let content_size = bit_array.byte_size(content) + use <- bool.guard(when: content_size < len, return: Error(Nil)) + // Safety: Prior guard ensures content_size >= len, so slices succeed + let assert Ok(inner) = bit_array.slice(content, 0, len) + let assert Ok(remaining) = bit_array.slice(content, len, content_size - len) + Ok(#(inner, remaining)) +} + +/// Parse a TLV element, returning (tag, value, remaining bytes). +pub fn parse_tlv(bytes: BitArray) -> Result(#(Int, BitArray, BitArray), Nil) { + case bytes { + <> -> { + use #(len, content) <- result.try(parse_length(rest)) + use #(value, remaining) <- result.try(parse_content(content, len)) + Ok(#(tag, value, remaining)) + } + _ -> Error(Nil) + } +} + +fn require_tag( + bytes: BitArray, + tag: Int, + next: fn(BitArray) -> Result(a, Nil), +) -> Result(a, Nil) { + case bytes { + <> if t == tag -> next(rest) + _ -> Error(Nil) + } +} + +fn reject_non_minimal_zeros( + value: BitArray, + next: fn() -> Result(a, Nil), +) -> Result(a, Nil) { + case value { + <<0x00, second:8, _:bits>> if second < 128 -> Error(Nil) + _ -> next() + } +} + +fn bytes_from_list(bytes: List(Int)) -> BitArray { + bytes + |> list.fold(bytes_tree.new(), fn(tree, byte) { + bytes_tree.append(tree, <>) + }) + |> bytes_tree.to_bit_array +} + +fn encode_oid_component(value: Int) -> List(Int) { + case value < 128 { + True -> [value] + False -> encode_oid_component_base128(value, []) + } +} + +fn encode_oid_component_base128(value: Int, acc: List(Int)) -> List(Int) { + case value { + 0 -> acc + _ -> { + let byte = int.bitwise_and(value, 0x7f) + let next_value = int.bitwise_shift_right(value, 7) + let new_byte = case acc { + [] -> byte + _ -> int.bitwise_or(byte, 0x80) + } + encode_oid_component_base128(next_value, [new_byte, ..acc]) + } + } +} + +/// Decode raw OID content bytes (without tag/length prefix) into components. +/// +/// Used for implicitly tagged OIDs where the tag/length have already been stripped. +pub fn decode_oid_components(bytes: BitArray) -> Result(List(Int), Nil) { + use #(first_value, rest) <- result.try(decode_first_oid_component(bytes, 0)) + + let first = case first_value { + v if v < 40 -> 0 + v if v < 80 -> 1 + _ -> 2 + } + let second = first_value - first * 40 + + use rest_components <- result.try(decode_oid_rest(rest, 0, [])) + Ok([first, second, ..rest_components]) +} + +fn decode_first_oid_component( + bytes: BitArray, + acc: Int, +) -> Result(#(Int, BitArray), Nil) { + case bytes { + <<>> -> Error(Nil) + <> -> { + let value = + int.bitwise_or( + int.bitwise_shift_left(acc, 7), + int.bitwise_and(byte, 0x7f), + ) + let is_continuation = int.bitwise_and(byte, 0x80) != 0 + case is_continuation { + True -> decode_first_oid_component(rest, value) + False -> Ok(#(value, rest)) + } + } + _ -> Error(Nil) + } +} + +fn decode_oid_rest( + bytes: BitArray, + acc: Int, + components: List(Int), +) -> Result(List(Int), Nil) { + case bytes { + <<>> if acc == 0 -> Ok(list.reverse(components)) + <<>> -> Error(Nil) + <> -> { + let value = + int.bitwise_or( + int.bitwise_shift_left(acc, 7), + int.bitwise_and(byte, 0x7f), + ) + let is_continuation = int.bitwise_and(byte, 0x80) != 0 + case is_continuation { + True -> decode_oid_rest(rest, value, components) + False -> decode_oid_rest(rest, 0, [value, ..components]) + } + } + _ -> Error(Nil) + } +} + +/// Convert ISO 8859-1 (Latin-1) bytes to a UTF-8 string. +/// +/// Each byte in Latin-1 represents a single Unicode codepoint (0x00-0xFF), +/// which maps directly to Unicode. +fn latin1_to_utf8(bytes: BitArray) -> Result(String, Nil) { + latin1_to_utf8_loop(bytes, []) + |> result.map(fn(codepoints) { + codepoints |> list.reverse |> string.from_utf_codepoints + }) +} + +fn latin1_to_utf8_loop( + bytes: BitArray, + acc: List(UtfCodepoint), +) -> Result(List(UtfCodepoint), Nil) { + case bytes { + <<>> -> Ok(acc) + <> -> { + case string.utf_codepoint(byte) { + Ok(cp) -> latin1_to_utf8_loop(rest, [cp, ..acc]) + Error(_) -> Error(Nil) + } + } + _ -> Error(Nil) + } +} + +/// Convert UCS-2 big-endian bytes to a UTF-8 string. +/// +/// Each 2-byte unit represents a Unicode codepoint in the Basic Multilingual Plane. +fn ucs2_to_utf8(bytes: BitArray) -> Result(String, Nil) { + ucs2_to_utf8_loop(bytes, []) + |> result.map(fn(codepoints) { + codepoints |> list.reverse |> string.from_utf_codepoints + }) +} + +fn ucs2_to_utf8_loop( + bytes: BitArray, + acc: List(UtfCodepoint), +) -> Result(List(UtfCodepoint), Nil) { + case bytes { + <<>> -> Ok(acc) + <> -> { + case string.utf_codepoint(codepoint) { + Ok(cp) -> ucs2_to_utf8_loop(rest, [cp, ..acc]) + Error(_) -> Error(Nil) + } + } + _ -> Error(Nil) + } +} + +/// Convert UCS-4 big-endian bytes to a UTF-8 string. +/// +/// Each 4-byte unit represents a Unicode codepoint. +fn ucs4_to_utf8(bytes: BitArray) -> Result(String, Nil) { + ucs4_to_utf8_loop(bytes, []) + |> result.map(fn(codepoints) { + codepoints |> list.reverse |> string.from_utf_codepoints + }) +} + +fn ucs4_to_utf8_loop( + bytes: BitArray, + acc: List(UtfCodepoint), +) -> Result(List(UtfCodepoint), Nil) { + case bytes { + <<>> -> Ok(acc) + <> -> { + case string.utf_codepoint(codepoint) { + Ok(cp) -> ucs4_to_utf8_loop(rest, [cp, ..acc]) + Error(_) -> Error(Nil) + } + } + _ -> Error(Nil) + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20.gleam new file mode 100644 index 0000000..08eb1af --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20.gleam @@ -0,0 +1,165 @@ +//// Internal HChaCha20 implementation for XChaCha20-Poly1305. +//// +//// HChaCha20 derives a 256-bit subkey from a 256-bit key and 128-bit input. +//// It uses the ChaCha20 quarter-round function but outputs different words. + +import gleam/int + +/// HChaCha20 key derivation function. +/// Takes a 32-byte key and 16-byte input, returns 32-byte subkey. +@external(javascript, "./hchacha20_ffi.mjs", "subkey") +pub fn subkey(key: BitArray, input: BitArray) -> BitArray { + // Parse key as 8 little-endian 32-bit words + let assert << + k0:32-little-unsigned, + k1:32-little-unsigned, + k2:32-little-unsigned, + k3:32-little-unsigned, + k4:32-little-unsigned, + k5:32-little-unsigned, + k6:32-little-unsigned, + k7:32-little-unsigned, + >> = key + + // Parse input as 4 little-endian 32-bit words + let assert << + n0:32-little-unsigned, + n1:32-little-unsigned, + n2:32-little-unsigned, + n3:32-little-unsigned, + >> = input + + // Initialize state with ChaCha20 constants, key, and input + // Constants: "expand 32-byte k" in little-endian + let state = + State( + s0: 0x61707865, + s1: 0x3320646E, + s2: 0x79622D32, + s3: 0x6B206574, + s4: k0, + s5: k1, + s6: k2, + s7: k3, + s8: k4, + s9: k5, + s10: k6, + s11: k7, + s12: n0, + s13: n1, + s14: n2, + s15: n3, + ) + + // Perform 20 rounds (10 double-rounds) + let state = perform_rounds(state, 10) + + // Return words 0-3 and 12-15 as the 32-byte subkey + << + state.s0:32-little, + state.s1:32-little, + state.s2:32-little, + state.s3:32-little, + state.s12:32-little, + state.s13:32-little, + state.s14:32-little, + state.s15:32-little, + >> +} + +type State { + State( + s0: Int, + s1: Int, + s2: Int, + s3: Int, + s4: Int, + s5: Int, + s6: Int, + s7: Int, + s8: Int, + s9: Int, + s10: Int, + s11: Int, + s12: Int, + s13: Int, + s14: Int, + s15: Int, + ) +} + +fn perform_rounds(state: State, remaining: Int) -> State { + case remaining <= 0 { + True -> state + False -> { + // Column round: QR(0,4,8,12), QR(1,5,9,13), QR(2,6,10,14), QR(3,7,11,15) + let #(s0, s4, s8, s12) = + quarter_round(state.s0, state.s4, state.s8, state.s12) + let #(s1, s5, s9, s13) = + quarter_round(state.s1, state.s5, state.s9, state.s13) + let #(s2, s6, s10, s14) = + quarter_round(state.s2, state.s6, state.s10, state.s14) + let #(s3, s7, s11, s15) = + quarter_round(state.s3, state.s7, state.s11, state.s15) + + // Diagonal round: QR(0,5,10,15), QR(1,6,11,12), QR(2,7,8,13), QR(3,4,9,14) + let #(s0, s5, s10, s15) = quarter_round(s0, s5, s10, s15) + let #(s1, s6, s11, s12) = quarter_round(s1, s6, s11, s12) + let #(s2, s7, s8, s13) = quarter_round(s2, s7, s8, s13) + let #(s3, s4, s9, s14) = quarter_round(s3, s4, s9, s14) + + let state = + State( + s0:, + s1:, + s2:, + s3:, + s4:, + s5:, + s6:, + s7:, + s8:, + s9:, + s10:, + s11:, + s12:, + s13:, + s14:, + s15:, + ) + perform_rounds(state, remaining - 1) + } + } +} + +/// The ChaCha20 quarter-round function. +/// Operates on 4 32-bit words in the state. +fn quarter_round(a: Int, b: Int, c: Int, d: Int) -> #(Int, Int, Int, Int) { + // a += b; d ^= a; d <<<= 16; + let a = add_modulo_32(a, b) + let d = rotate_left_32(int.bitwise_exclusive_or(d, a), 16) + + // c += d; b ^= c; b <<<= 12; + let c = add_modulo_32(c, d) + let b = rotate_left_32(int.bitwise_exclusive_or(b, c), 12) + + // a += b; d ^= a; d <<<= 8; + let a = add_modulo_32(a, b) + let d = rotate_left_32(int.bitwise_exclusive_or(d, a), 8) + + // c += d; b ^= c; b <<<= 7; + let c = add_modulo_32(c, d) + let b = rotate_left_32(int.bitwise_exclusive_or(b, c), 7) + + #(a, b, c, d) +} + +fn add_modulo_32(a: Int, b: Int) -> Int { + int.bitwise_and(a + b, 0xFFFFFFFF) +} + +fn rotate_left_32(x: Int, n: Int) -> Int { + let shifted_left = int.bitwise_shift_left(x, n) + let shifted_right = int.bitwise_shift_right(x, 32 - n) + int.bitwise_and(int.bitwise_or(shifted_left, shifted_right), 0xFFFFFFFF) +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20_ffi.mjs b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20_ffi.mjs new file mode 100644 index 0000000..aa7950a --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hchacha20_ffi.mjs @@ -0,0 +1,148 @@ +import { BitArray$BitArray } from "../../gleam.mjs"; + +export function subkey(key, input) { + const kv = new DataView(key.rawBuffer.buffer, key.rawBuffer.byteOffset, 32); + const iv = new DataView( + input.rawBuffer.buffer, + input.rawBuffer.byteOffset, + 16, + ); + + // "expand 32-byte k" constants + let s0 = 0x61707865; + let s1 = 0x3320646e; + let s2 = 0x79622d32; + let s3 = 0x6b206574; + let s4 = kv.getUint32(0, true); + let s5 = kv.getUint32(4, true); + let s6 = kv.getUint32(8, true); + let s7 = kv.getUint32(12, true); + let s8 = kv.getUint32(16, true); + let s9 = kv.getUint32(20, true); + let s10 = kv.getUint32(24, true); + let s11 = kv.getUint32(28, true); + let s12 = iv.getUint32(0, true); + let s13 = iv.getUint32(4, true); + let s14 = iv.getUint32(8, true); + let s15 = iv.getUint32(12, true); + + for (let i = 0; i < 10; i++) { + // Column round: QR(0,4,8,12) QR(1,5,9,13) QR(2,6,10,14) QR(3,7,11,15) + s0 = (s0 + s4) | 0; + s12 ^= s0; + s12 = (s12 << 16) | (s12 >>> 16); + s8 = (s8 + s12) | 0; + s4 ^= s8; + s4 = (s4 << 12) | (s4 >>> 20); + s0 = (s0 + s4) | 0; + s12 ^= s0; + s12 = (s12 << 8) | (s12 >>> 24); + s8 = (s8 + s12) | 0; + s4 ^= s8; + s4 = (s4 << 7) | (s4 >>> 25); + + s1 = (s1 + s5) | 0; + s13 ^= s1; + s13 = (s13 << 16) | (s13 >>> 16); + s9 = (s9 + s13) | 0; + s5 ^= s9; + s5 = (s5 << 12) | (s5 >>> 20); + s1 = (s1 + s5) | 0; + s13 ^= s1; + s13 = (s13 << 8) | (s13 >>> 24); + s9 = (s9 + s13) | 0; + s5 ^= s9; + s5 = (s5 << 7) | (s5 >>> 25); + + s2 = (s2 + s6) | 0; + s14 ^= s2; + s14 = (s14 << 16) | (s14 >>> 16); + s10 = (s10 + s14) | 0; + s6 ^= s10; + s6 = (s6 << 12) | (s6 >>> 20); + s2 = (s2 + s6) | 0; + s14 ^= s2; + s14 = (s14 << 8) | (s14 >>> 24); + s10 = (s10 + s14) | 0; + s6 ^= s10; + s6 = (s6 << 7) | (s6 >>> 25); + + s3 = (s3 + s7) | 0; + s15 ^= s3; + s15 = (s15 << 16) | (s15 >>> 16); + s11 = (s11 + s15) | 0; + s7 ^= s11; + s7 = (s7 << 12) | (s7 >>> 20); + s3 = (s3 + s7) | 0; + s15 ^= s3; + s15 = (s15 << 8) | (s15 >>> 24); + s11 = (s11 + s15) | 0; + s7 ^= s11; + s7 = (s7 << 7) | (s7 >>> 25); + + // Diagonal round: QR(0,5,10,15) QR(1,6,11,12) QR(2,7,8,13) QR(3,4,9,14) + s0 = (s0 + s5) | 0; + s15 ^= s0; + s15 = (s15 << 16) | (s15 >>> 16); + s10 = (s10 + s15) | 0; + s5 ^= s10; + s5 = (s5 << 12) | (s5 >>> 20); + s0 = (s0 + s5) | 0; + s15 ^= s0; + s15 = (s15 << 8) | (s15 >>> 24); + s10 = (s10 + s15) | 0; + s5 ^= s10; + s5 = (s5 << 7) | (s5 >>> 25); + + s1 = (s1 + s6) | 0; + s12 ^= s1; + s12 = (s12 << 16) | (s12 >>> 16); + s11 = (s11 + s12) | 0; + s6 ^= s11; + s6 = (s6 << 12) | (s6 >>> 20); + s1 = (s1 + s6) | 0; + s12 ^= s1; + s12 = (s12 << 8) | (s12 >>> 24); + s11 = (s11 + s12) | 0; + s6 ^= s11; + s6 = (s6 << 7) | (s6 >>> 25); + + s2 = (s2 + s7) | 0; + s13 ^= s2; + s13 = (s13 << 16) | (s13 >>> 16); + s8 = (s8 + s13) | 0; + s7 ^= s8; + s7 = (s7 << 12) | (s7 >>> 20); + s2 = (s2 + s7) | 0; + s13 ^= s2; + s13 = (s13 << 8) | (s13 >>> 24); + s8 = (s8 + s13) | 0; + s7 ^= s8; + s7 = (s7 << 7) | (s7 >>> 25); + + s3 = (s3 + s4) | 0; + s14 ^= s3; + s14 = (s14 << 16) | (s14 >>> 16); + s9 = (s9 + s14) | 0; + s4 ^= s9; + s4 = (s4 << 12) | (s4 >>> 20); + s3 = (s3 + s4) | 0; + s14 ^= s3; + s14 = (s14 << 8) | (s14 >>> 24); + s9 = (s9 + s14) | 0; + s4 ^= s9; + s4 = (s4 << 7) | (s4 >>> 25); + } + + const out = new DataView(new ArrayBuffer(32)); + out.setUint32(0, s0, true); + out.setUint32(4, s1, true); + out.setUint32(8, s2, true); + out.setUint32(12, s3, true); + out.setUint32(16, s12, true); + out.setUint32(20, s13, true); + out.setUint32(24, s14, true); + out.setUint32(28, s15, true); + + return BitArray$BitArray(new Uint8Array(out.buffer)); +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hkdf.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hkdf.gleam new file mode 100644 index 0000000..5eaa35a --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/hkdf.gleam @@ -0,0 +1,89 @@ +//// Internal HKDF (HMAC-based Key Derivation Function) implementation. +//// +//// Implements RFC 5869 extract-and-expand key derivation. Used internally +//// by the crypto module for deriving cryptographic keys from input keying material. + +import gleam/bit_array +import gleam/bytes_tree.{type BytesTree} +import gleam/result +import kryptos/hash.{type HashAlgorithm} +import kryptos/hmac + +@external(javascript, "../../kryptos_ffi.mjs", "hkdfDerive") +pub fn do_derive( + algorithm: HashAlgorithm, + ikm: BitArray, + salt: BitArray, + info: BitArray, + length: Int, +) -> Result(BitArray, Nil) { + // Step 1: Extract + // PRK = HMAC-Hash(salt, IKM) + use hmac_state <- result.try(hmac.new(algorithm, salt)) + let prk = + hmac_state + |> hmac.update(ikm) + |> hmac.final() + + // Step 2: Expand + // T = T(1) || T(2) || ... || T(N) + // T(0) = empty + // T(i) = HMAC-Hash(PRK, T(i-1) || info || i) + // OKM = first length octets of T + expand(algorithm, prk, info, length) +} + +fn expand( + algorithm: HashAlgorithm, + prk: BitArray, + info: BitArray, + length: Int, +) -> Result(BitArray, Nil) { + expand_loop(algorithm, prk, info, length, <<>>, 1, bytes_tree.new()) +} + +fn expand_loop( + algorithm: HashAlgorithm, + prk: BitArray, + info: BitArray, + remaining: Int, + prev: BitArray, + counter: Int, + acc: BytesTree, +) -> Result(BitArray, Nil) { + case remaining <= 0 { + True -> Ok(bytes_tree.to_bit_array(acc)) + False -> { + // T(i) = HMAC-Hash(PRK, T(i-1) || info || counter) + let input = bit_array.concat([prev, info, <>]) + use hmac_state <- result.try(hmac.new(algorithm, prk)) + let t = + hmac_state + |> hmac.update(input) + |> hmac.final() + + let t_len = bit_array.byte_size(t) + case remaining <= t_len { + True -> { + // Final block - take what we need + let assert Ok(final_block) = bit_array.slice(t, 0, remaining) + bytes_tree.append(acc, final_block) + |> bytes_tree.to_bit_array + |> Ok + } + False -> { + // Accumulate and continue + expand_loop( + algorithm, + prk, + info, + remaining - t_len, + t, + counter + 1, + bytes_tree.append(acc, t), + ) + } + } + } + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/pbkdf2.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/pbkdf2.gleam new file mode 100644 index 0000000..89878b2 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/pbkdf2.gleam @@ -0,0 +1,16 @@ +//// Internal PBKDF2 (Password-Based Key Derivation Function 2) implementation. +//// +//// Implements RFC 2898/8018 password-based key derivation. Used internally +//// by the crypto module for deriving keys from passwords with configurable iterations. + +import kryptos/hash.{type HashAlgorithm} + +@external(erlang, "kryptos_ffi", "pbkdf2_derive") +@external(javascript, "../../kryptos_ffi.mjs", "pbkdf2Derive") +pub fn do_derive( + algorithm: HashAlgorithm, + password: BitArray, + salt: BitArray, + iterations: Int, + length: Int, +) -> Result(BitArray, Nil) diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/rsa_crt.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/rsa_crt.gleam new file mode 100644 index 0000000..3685282 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/rsa_crt.gleam @@ -0,0 +1,270 @@ +import bigi.{type BigInt} +import gleam/bit_array +import gleam/order +import gleam/result +import kryptos/crypto +import kryptos/internal/utils + +/// Compute CRT parameters from minimal RSA components. This is used for +/// loading RSA keys where only the modulus, public exponent, and private +/// exponent are known. The JS implementation requires the precomputed +/// parameters to be provided as well. +pub fn compute_crt_params( + n_bytes: BitArray, + e_bytes: BitArray, + d_bytes: BitArray, +) -> Result(#(BitArray, BitArray, BitArray, BitArray, BitArray), Nil) { + use n <- result.try(bigi.from_bytes(n_bytes, bigi.BigEndian, bigi.Unsigned)) + use e <- result.try(bigi.from_bytes(e_bytes, bigi.BigEndian, bigi.Unsigned)) + use d <- result.try(bigi.from_bytes(d_bytes, bigi.BigEndian, bigi.Unsigned)) + + use <- validate_components(n, e, d) + + let byte_len = bit_array.byte_size(n_bytes) + + use #(p, q) <- result.try(factor_rsa_modulus(n, e, d, byte_len, 500)) + + let one = bigi.from_int(1) + let dp = bigi.modulo(d, bigi.subtract(p, one)) + let dq = bigi.modulo(d, bigi.subtract(q, one)) + + use qi <- result.try(mod_inverse(q, p)) + + use p_bytes <- result.try(to_bytes_trimmed(p, byte_len)) + use q_bytes <- result.try(to_bytes_trimmed(q, byte_len)) + use dp_bytes <- result.try(to_bytes_trimmed(dp, byte_len)) + use dq_bytes <- result.try(to_bytes_trimmed(dq, byte_len)) + use qi_bytes <- result.try(to_bytes_trimmed(qi, byte_len)) + + Ok(#(p_bytes, q_bytes, dp_bytes, dq_bytes, qi_bytes)) +} + +fn validate_components( + n: BigInt, + e: BigInt, + d: BigInt, + next: fn() -> Result(a, Nil), +) -> Result(a, Nil) { + let zero = bigi.from_int(0) + let one = bigi.from_int(1) + + case + bigi.compare(n, one) == order.Gt + && bigi.compare(e, one) == order.Gt + && bigi.compare(d, zero) == order.Gt + { + True -> next() + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "mod_pow") +@external(javascript, "../../kryptos_ffi.mjs", "modPow") +fn mod_pow_ffi(base: BitArray, exp: BitArray, mod: BitArray) -> BitArray + +fn mod_pow( + base: BigInt, + exp: BigInt, + mod: BigInt, + byte_len: Int, +) -> Result(BigInt, Nil) { + use base_bytes <- result.try(bigi.to_bytes( + base, + bigi.BigEndian, + bigi.Unsigned, + byte_len, + )) + use exp_bytes <- result.try(to_bytes_minimal(exp)) + use mod_bytes <- result.try(bigi.to_bytes( + mod, + bigi.BigEndian, + bigi.Unsigned, + byte_len, + )) + + let result_bytes = mod_pow_ffi(base_bytes, exp_bytes, mod_bytes) + bigi.from_bytes(result_bytes, bigi.BigEndian, bigi.Unsigned) +} + +fn to_bytes_trimmed(value: BigInt, max_byte_len: Int) -> Result(BitArray, Nil) { + use bytes <- result.try(bigi.to_bytes( + value, + bigi.BigEndian, + bigi.Unsigned, + max_byte_len, + )) + Ok(utils.strip_leading_zeros(bytes)) +} + +fn to_bytes_minimal(value: BigInt) -> Result(BitArray, Nil) { + let zero = bigi.from_int(0) + case value == zero { + True -> Ok(<<0>>) + False -> { + let byte_len = compute_byte_length(value, 1) + use bytes <- result.try(bigi.to_bytes( + value, + bigi.BigEndian, + bigi.Unsigned, + byte_len, + )) + Ok(utils.strip_leading_zeros(bytes)) + } + } +} + +fn compute_byte_length(value: BigInt, len: Int) -> Int { + let assert Ok(bound) = bigi.power(bigi.from_int(256), bigi.from_int(len)) + case bigi.compare(value, bound) == order.Lt { + True -> len + False -> compute_byte_length(value, len + 1) + } +} + +fn factor_rsa_modulus( + n: BigInt, + e: BigInt, + d: BigInt, + byte_len: Int, + attempts_left: Int, +) -> Result(#(BigInt, BigInt), Nil) { + case attempts_left { + 0 -> Error(Nil) + _ -> { + let one = bigi.from_int(1) + let two = bigi.from_int(2) + let three = bigi.from_int(3) + + let k = bigi.subtract(bigi.multiply(e, d), one) + let #(t, r) = factor_out_twos(k, two, 0) + + let g_bytes = crypto.random_bytes(byte_len) + let assert Ok(g_raw) = + bigi.from_bytes(g_bytes, bigi.BigEndian, bigi.Unsigned) + + let n_minus_3 = bigi.subtract(n, three) + let g = bigi.add(bigi.modulo(g_raw, n_minus_3), two) + + use x <- result.try(mod_pow(g, r, n, byte_len)) + + case try_factor(n, t, x, byte_len) { + Ok(#(p, q)) -> Ok(#(p, q)) + Error(Nil) -> factor_rsa_modulus(n, e, d, byte_len, attempts_left - 1) + } + } + } +} + +fn factor_out_twos(k: BigInt, two: BigInt, count: Int) -> #(Int, BigInt) { + case bigi.modulo(k, two) == bigi.from_int(0) { + True -> { + let assert Ok(next) = bigi.floor_divide(dividend: k, divisor: two) + factor_out_twos(next, two, count + 1) + } + False -> #(count, k) + } +} + +fn try_factor( + n: BigInt, + t: Int, + x: BigInt, + byte_len: Int, +) -> Result(#(BigInt, BigInt), Nil) { + let one = bigi.from_int(1) + let n_minus_1 = bigi.subtract(n, one) + + case x == one || x == n_minus_1 { + True -> Error(Nil) + False -> try_factor_loop(n, t, x, 1, byte_len) + } +} + +fn try_factor_loop( + n: BigInt, + t: Int, + x: BigInt, + i: Int, + byte_len: Int, +) -> Result(#(BigInt, BigInt), Nil) { + case i > t { + True -> Error(Nil) + False -> { + let one = bigi.from_int(1) + let two = bigi.from_int(2) + let n_minus_1 = bigi.subtract(n, one) + + use y <- result.try(mod_pow(x, two, n, byte_len)) + + case y == one { + True -> { + let p = gcd(bigi.subtract(x, one), n) + case + bigi.compare(p, one) == order.Gt && bigi.compare(p, n) == order.Lt + { + True -> { + let assert Ok(q) = bigi.floor_divide(dividend: n, divisor: p) + case bigi.compare(p, q) == order.Lt { + True -> Ok(#(p, q)) + False -> Ok(#(q, p)) + } + } + False -> Error(Nil) + } + } + False -> + case y == n_minus_1 { + True -> Error(Nil) + False -> try_factor_loop(n, t, y, i + 1, byte_len) + } + } + } + } +} + +fn gcd(a: BigInt, b: BigInt) -> BigInt { + let zero = bigi.from_int(0) + case b == zero { + True -> bigi.absolute(a) + False -> gcd(b, bigi.modulo(a, b)) + } +} + +fn mod_inverse(a: BigInt, mod: BigInt) -> Result(BigInt, Nil) { + let zero = bigi.from_int(0) + let one = bigi.from_int(1) + + let #(old_r, old_s, _) = extended_gcd_loop(a, mod, one, zero, zero, one) + + case old_r == one { + True -> { + let result = bigi.modulo(old_s, mod) + case bigi.compare(result, zero) == order.Lt { + True -> Ok(bigi.add(result, mod)) + False -> Ok(result) + } + } + False -> Error(Nil) + } +} + +fn extended_gcd_loop( + old_r: BigInt, + r: BigInt, + old_s: BigInt, + s: BigInt, + old_t: BigInt, + t: BigInt, +) -> #(BigInt, BigInt, BigInt) { + let zero = bigi.from_int(0) + case r == zero { + True -> #(old_r, old_s, old_t) + False -> { + let assert Ok(q) = bigi.floor_divide(dividend: old_r, divisor: r) + let new_r = bigi.subtract(old_r, bigi.multiply(q, r)) + let new_s = bigi.subtract(old_s, bigi.multiply(q, s)) + let new_t = bigi.subtract(old_t, bigi.multiply(q, t)) + extended_gcd_loop(r, new_r, s, new_s, t, new_t) + } + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/subtle.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/subtle.gleam new file mode 100644 index 0000000..e639e76 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/subtle.gleam @@ -0,0 +1,8 @@ +//// Internal constant-time utilities. +//// +//// Provides timing-safe operations to prevent side-channel attacks when +//// comparing sensitive data like authentication tags and MACs. + +@external(erlang, "kryptos_ffi", "constant_time_equal") +@external(javascript, "../../kryptos_ffi.mjs", "constantTimeEqual") +pub fn constant_time_equal(a: BitArray, b: BitArray) -> Bool diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/utils.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/utils.gleam new file mode 100644 index 0000000..0f45793 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/utils.gleam @@ -0,0 +1,196 @@ +import gleam/bit_array +import gleam/bool +import gleam/int +import gleam/list +import gleam/result +import gleam/string + +/// Count the number of trailing zero bits in a byte-aligned BitArray. +pub fn count_trailing_zeros(bits: BitArray) -> Int { + let size = bit_array.byte_size(bits) + do_count_trailing_zeros(bits, size - 1, 0) +} + +fn do_count_trailing_zeros(bits: BitArray, byte_pos: Int, count: Int) -> Int { + case byte_pos < 0 { + True -> count + False -> { + let assert Ok(<>) = bit_array.slice(bits, byte_pos, 1) + case byte { + 0 -> do_count_trailing_zeros(bits, byte_pos - 1, count + 8) + _ -> count + trailing_zeros_in_byte(byte, 0) + } + } + } +} + +fn trailing_zeros_in_byte(byte: Int, count: Int) -> Int { + case int.bitwise_and(byte, 1) { + 0 -> trailing_zeros_in_byte(int.bitwise_shift_right(byte, 1), count + 1) + _ -> count + } +} + +/// Strip leading zero bytes from a BitArray, preserving at least one byte. +/// +/// For example: `<<0, 0, 1, 2>>` becomes `<<1, 2>>` +/// But: `<<0, 0>>` becomes `<<0>>` (preserves at least one byte) +pub fn strip_leading_zeros(bytes: BitArray) -> BitArray { + case bytes { + <<0x00, rest:bits>> -> { + case bit_array.byte_size(rest) > 0 { + True -> strip_leading_zeros(rest) + False -> bytes + } + } + _ -> bytes + } +} + +/// Strip trailing zero bytes from a BitArray. +/// +/// For example: `<<1, 2, 0, 0>>` becomes `<<1, 2>>` +/// An all-zeros input returns `<<>>`. +pub fn strip_trailing_zeros(data: BitArray) -> BitArray { + let len = bit_array.byte_size(data) + strip_trailing_zeros_loop(data, len) +} + +fn strip_trailing_zeros_loop(data: BitArray, len: Int) -> BitArray { + case len { + 0 -> <<>> + _ -> { + let assert Ok(<>) = bit_array.slice(data, len - 1, 1) + case last_byte { + 0 -> strip_trailing_zeros_loop(data, len - 1) + _ -> { + let assert Ok(result) = bit_array.slice(data, 0, len) + result + } + } + } + } +} + +/// Left-pad a BitArray with zeros to reach the specified size. +/// +/// If the input is already at least `size` bytes, it is returned unchanged. +pub fn pad_left(value: BitArray, size: Int) -> BitArray { + let current_size = bit_array.byte_size(value) + case current_size >= size { + True -> value + False -> { + let padding_size = size - current_size + let padding = list.repeat(<<0>>, padding_size) |> bit_array.concat + bit_array.concat([padding, value]) + } + } +} + +/// Convert an integer to a zero-padded string of the specified width. +/// +/// For example: `int_to_padded_string(42, 4)` returns `"0042"` +pub fn int_to_padded_string(n: Int, width: Int) -> String { + let s = int.to_string(n) + let padding = string.repeat("0", int.max(0, width - string.length(s))) + padding <> s +} + +/// Check if a string contains only ASCII characters (codepoints 0-127). +pub fn is_ascii(s: String) -> Bool { + s + |> string.to_utf_codepoints + |> list.all(fn(cp) { string.utf_codepoint_to_int(cp) <= 127 }) +} + +/// Split a string into chunks of the specified size. +/// Returns an empty list if size <= 0. +pub fn chunk_string(s: String, size: Int) -> List(String) { + case size > 0 { + False -> [] + True -> + case string.length(s) <= size { + True -> [s] + False -> { + let chunk = string.slice(s, 0, size) + let rest = string.slice(s, size, string.length(s) - size) + [chunk, ..chunk_string(rest, size)] + } + } + } +} + +/// Parses an IP address string into bytes (4 for IPv4, 16 for IPv6). +pub fn parse_ip(ip: String) -> Result(BitArray, Nil) { + case string.contains(ip, ":") { + True -> parse_ipv6(ip) + False -> parse_ipv4(ip) + } +} + +fn parse_ipv4(ip: String) -> Result(BitArray, Nil) { + let parts = string.split(ip, ".") + use <- bool.guard(when: list.length(parts) != 4, return: Error(Nil)) + use bytes <- result.try(list.try_map(parts, parse_ipv4_octet)) + Ok(bit_array.concat(list.map(bytes, fn(b) { <> }))) +} + +fn parse_ipv4_octet(s: String) -> Result(Int, Nil) { + use n <- result.try(int.parse(s)) + use <- bool.guard(when: n < 0 || n > 255, return: Error(Nil)) + Ok(n) +} + +fn parse_ipv6(ip: String) -> Result(BitArray, Nil) { + let ip = case string.starts_with(ip, "::") { + True -> "0" <> ip + False -> ip + } + let ip = case string.ends_with(ip, "::") { + True -> ip <> "0" + False -> ip + } + + case string.contains(ip, "::") { + True -> parse_ipv6_compressed(ip) + False -> parse_ipv6_full(ip) + } +} + +fn parse_ipv6_full(ip: String) -> Result(BitArray, Nil) { + let parts = string.split(ip, ":") + use <- bool.guard(when: list.length(parts) != 8, return: Error(Nil)) + use words <- result.try(list.try_map(parts, parse_ipv6_word)) + Ok(bit_array.concat(list.map(words, fn(w) { <> }))) +} + +fn parse_ipv6_compressed(ip: String) -> Result(BitArray, Nil) { + use #(left, right) <- result.try(case string.split(ip, "::") { + [l, r] -> Ok(#(l, r)) + _ -> Error(Nil) + }) + + let left_parts = case left { + "" -> [] + _ -> string.split(left, ":") + } + let right_parts = case right { + "" -> [] + _ -> string.split(right, ":") + } + + let total = list.length(left_parts) + list.length(right_parts) + use <- bool.guard(when: total > 7, return: Error(Nil)) + + let zeros = list.repeat(0, 8 - total) + use left_words <- result.try(list.try_map(left_parts, parse_ipv6_word)) + use right_words <- result.try(list.try_map(right_parts, parse_ipv6_word)) + let all_words = list.flatten([left_words, zeros, right_words]) + Ok(bit_array.concat(list.map(all_words, fn(w) { <> }))) +} + +fn parse_ipv6_word(s: String) -> Result(Int, Nil) { + use n <- result.try(int.base_parse(s, 16)) + use <- bool.guard(when: n < 0 || n > 0xffff, return: Error(Nil)) + Ok(n) +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/x509.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/x509.gleam new file mode 100644 index 0000000..19a4b1a --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/internal/x509.gleam @@ -0,0 +1,611 @@ +//// Shared X.509 parsing and encoding utilities for CSR and Certificate modules. + +import gleam/bit_array +import gleam/bool +import gleam/list +import gleam/result +import gleam/string +import gleam/string_tree +import kryptos/ec +import kryptos/ecdsa +import kryptos/eddsa +import kryptos/hash +import kryptos/internal/der +import kryptos/internal/utils +import kryptos/rsa +import kryptos/x509 +import kryptos/xdh + +const oid_rsa_with_sha1 = x509.Oid([1, 2, 840, 113_549, 1, 1, 5]) + +const oid_rsa_with_sha256 = x509.Oid([1, 2, 840, 113_549, 1, 1, 11]) + +const oid_rsa_with_sha384 = x509.Oid([1, 2, 840, 113_549, 1, 1, 12]) + +const oid_rsa_with_sha512 = x509.Oid([1, 2, 840, 113_549, 1, 1, 13]) + +const oid_ecdsa_with_sha1 = x509.Oid([1, 2, 840, 10_045, 4, 1]) + +const oid_ecdsa_with_sha256 = x509.Oid([1, 2, 840, 10_045, 4, 3, 2]) + +const oid_ecdsa_with_sha384 = x509.Oid([1, 2, 840, 10_045, 4, 3, 3]) + +const oid_ecdsa_with_sha512 = x509.Oid([1, 2, 840, 10_045, 4, 3, 4]) + +const oid_ed25519 = x509.Oid([1, 3, 101, 112]) + +const oid_ed448 = x509.Oid([1, 3, 101, 113]) + +type PemError { + PemNotFound + PemMalformed +} + +pub type SigAlgInfo { + SigAlgInfo(oid: x509.Oid, include_null_params: Bool) +} + +/// Map a hash algorithm to ECDSA signature algorithm information. +pub fn ecdsa_sig_alg_info(hash: hash.HashAlgorithm) -> Result(SigAlgInfo, Nil) { + case hash { + hash.Sha1 -> Ok(SigAlgInfo(oid_ecdsa_with_sha1, False)) + hash.Sha256 -> Ok(SigAlgInfo(oid_ecdsa_with_sha256, False)) + hash.Sha384 -> Ok(SigAlgInfo(oid_ecdsa_with_sha384, False)) + hash.Sha512 -> Ok(SigAlgInfo(oid_ecdsa_with_sha512, False)) + _ -> Error(Nil) + } +} + +/// Map a hash algorithm to RSA signature algorithm information. +pub fn rsa_sig_alg_info(hash: hash.HashAlgorithm) -> Result(SigAlgInfo, Nil) { + case hash { + hash.Sha1 -> Ok(SigAlgInfo(oid_rsa_with_sha1, True)) + hash.Sha256 -> Ok(SigAlgInfo(oid_rsa_with_sha256, True)) + hash.Sha384 -> Ok(SigAlgInfo(oid_rsa_with_sha384, True)) + hash.Sha512 -> Ok(SigAlgInfo(oid_rsa_with_sha512, True)) + _ -> Error(Nil) + } +} + +/// Map an EdDSA curve to signature algorithm information. +pub fn eddsa_sig_alg_info(curve: eddsa.Curve) -> SigAlgInfo { + case curve { + eddsa.Ed25519 -> SigAlgInfo(oid_ed25519, False) + eddsa.Ed448 -> SigAlgInfo(oid_ed448, False) + } +} + +/// Verify a signature using the appropriate algorithm based on public key and signature algorithm. +pub fn verify_signature( + public_key: x509.PublicKey, + data: BitArray, + signature: BitArray, + signature_algorithm: x509.SignatureAlgorithm, +) -> Bool { + case public_key, signature_algorithm { + x509.EcPublicKey(key), sig_alg -> + case sig_alg_to_hash(sig_alg) { + Ok(hash_alg) -> ecdsa.verify(key, data, signature, hash_alg) + Error(Nil) -> False + } + x509.RsaPublicKey(key), sig_alg -> + case sig_alg_to_hash(sig_alg) { + Ok(hash_alg) -> rsa.verify(key, data, signature, hash_alg, rsa.Pkcs1v15) + Error(Nil) -> False + } + x509.EdPublicKey(key), x509.Ed25519 | x509.EdPublicKey(key), x509.Ed448 -> + eddsa.verify(key, data, signature) + x509.EdPublicKey(_), _ -> False + x509.XdhPublicKey(_), _ -> False + } +} + +fn sig_alg_to_hash( + sig_alg: x509.SignatureAlgorithm, +) -> Result(hash.HashAlgorithm, Nil) { + case sig_alg { + x509.EcdsaSha1 | x509.RsaSha1 -> Ok(hash.Sha1) + x509.EcdsaSha256 | x509.RsaSha256 -> Ok(hash.Sha256) + x509.EcdsaSha384 | x509.RsaSha384 -> Ok(hash.Sha384) + x509.EcdsaSha512 | x509.RsaSha512 -> Ok(hash.Sha512) + x509.Ed25519 | x509.Ed448 -> Error(Nil) + } +} + +/// Parse a DER SEQUENCE including its tag and length header. +/// +/// Returns the complete SEQUENCE (tag + length + content) and remaining bytes. +/// Returns Error(Nil) if the input does not start with a SEQUENCE tag. +pub fn parse_sequence_with_header( + bytes: BitArray, +) -> Result(#(BitArray, BitArray), Nil) { + case bytes { + <<0x30, _:bits>> -> { + use #(inner, remaining) <- result.try(der.parse_sequence(bytes)) + + let inner_len = bit_array.byte_size(inner) + let header_len = + bit_array.byte_size(bytes) - bit_array.byte_size(remaining) - inner_len + let total_len = header_len + inner_len + let assert Ok(full_seq) = bit_array.slice(bytes, 0, total_len) + Ok(#(full_seq, remaining)) + } + _ -> Error(Nil) + } +} + +/// Parse an X.509 signature algorithm from DER-encoded AlgorithmIdentifier. +/// +/// Decodes the OID and returns the corresponding SignatureAlgorithm variant. +/// Returns Error with the unknown OID if the algorithm is not recognized. +pub fn parse_signature_algorithm( + bytes: BitArray, +) -> Result(x509.SignatureAlgorithm, x509.Oid) { + case der.parse_oid(bytes) { + Ok(#(oid_components, _params)) -> + case oid_components { + [1, 2, 840, 113_549, 1, 1, 5] -> Ok(x509.RsaSha1) + [1, 2, 840, 113_549, 1, 1, 11] -> Ok(x509.RsaSha256) + [1, 2, 840, 113_549, 1, 1, 12] -> Ok(x509.RsaSha384) + [1, 2, 840, 113_549, 1, 1, 13] -> Ok(x509.RsaSha512) + [1, 2, 840, 10_045, 4, 1] -> Ok(x509.EcdsaSha1) + [1, 2, 840, 10_045, 4, 3, 2] -> Ok(x509.EcdsaSha256) + [1, 2, 840, 10_045, 4, 3, 3] -> Ok(x509.EcdsaSha384) + [1, 2, 840, 10_045, 4, 3, 4] -> Ok(x509.EcdsaSha512) + [1, 3, 101, 112] -> Ok(x509.Ed25519) + [1, 3, 101, 113] -> Ok(x509.Ed448) + _ -> Error(x509.Oid(oid_components)) + } + Error(_) -> Error(x509.Oid([])) + } +} + +/// Parse an X.509 distinguished name (DN) from DER encoding. +/// +/// Decodes a Name structure containing RDNs (relative distinguished names). +/// Returns Error(Nil) if the encoding is invalid. +pub fn parse_name(bytes: BitArray) -> Result(x509.Name, Nil) { + parse_rdns(bytes, []) + |> result.map(x509.Name) +} + +fn parse_rdns( + bytes: BitArray, + acc: List(x509.Rdn), +) -> Result(List(x509.Rdn), Nil) { + case bytes { + <<>> -> Ok(list.reverse(acc)) + _ -> { + use #(rdn_bytes, rest) <- result.try(der.parse_set(bytes)) + + parse_rdn_attributes(rdn_bytes, []) + |> result.try(fn(attributes) { + parse_rdns(rest, [x509.Rdn(attributes:), ..acc]) + }) + } + } +} + +fn parse_rdn_attributes( + bytes: BitArray, + acc: List(#(x509.Oid, x509.AttributeValue)), +) -> Result(List(#(x509.Oid, x509.AttributeValue)), Nil) { + case bytes { + <<>> -> Ok(list.reverse(acc)) + _ -> { + use #(attr_bytes, rest) <- result.try(der.parse_sequence(bytes)) + use #(oid_components, after_oid) <- result.try(der.parse_oid(attr_bytes)) + use #(value, remaining) <- result.try(parse_attribute_value(after_oid)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + + parse_rdn_attributes(rest, [#(x509.Oid(oid_components), value), ..acc]) + } + } +} + +fn parse_attribute_value( + bytes: BitArray, +) -> Result(#(x509.AttributeValue, BitArray), Nil) { + case bytes { + <<0x0c, _:bits>> -> { + use #(s, rest) <- result.try(der.parse_utf8_string(bytes)) + Ok(#(x509.utf8_string(s), rest)) + } + <<0x13, _:bits>> -> { + use #(s, rest) <- result.try(der.parse_printable_string(bytes)) + Ok(#(x509.printable_string(s), rest)) + } + <<0x14, _:bits>> -> { + // TeletexString - normalize to UTF8String + use #(s, rest) <- result.try(der.parse_teletex_string(bytes)) + Ok(#(x509.utf8_string(s), rest)) + } + <<0x16, _:bits>> -> { + use #(s, rest) <- result.try(der.parse_ia5_string(bytes)) + Ok(#(x509.ia5_string(s), rest)) + } + <<0x1c, _:bits>> -> { + // UniversalString - normalize to UTF8String + use #(s, rest) <- result.try(der.parse_universal_string(bytes)) + Ok(#(x509.utf8_string(s), rest)) + } + <<0x1e, _:bits>> -> { + // BMPString - normalize to UTF8String + use #(s, rest) <- result.try(der.parse_bmp_string(bytes)) + Ok(#(x509.utf8_string(s), rest)) + } + _ -> Error(Nil) + } +} + +/// Encode an X.509 distinguished name to DER format. +/// +/// Produces a DER-encoded Name structure with RDN attributes sorted per RFC 5280. +/// Returns Error(Nil) if encoding fails. +pub fn encode_name(name: x509.Name) -> Result(BitArray, Nil) { + let x509.Name(rdns) = name + + list.try_map(rdns, encode_rdn) + |> result.try(fn(encoded_rdns) { + der.encode_sequence(bit_array.concat(encoded_rdns)) + }) +} + +fn encode_rdn(rdn: x509.Rdn) -> Result(BitArray, Nil) { + let x509.Rdn(attributes) = rdn + use encoded_attrs <- result.try(list.try_map( + attributes, + encode_attribute_type_and_value, + )) + let sorted_attrs = list.sort(encoded_attrs, bit_array.compare) + der.encode_set(bit_array.concat(sorted_attrs)) +} + +fn encode_attribute_type_and_value( + attr: #(x509.Oid, x509.AttributeValue), +) -> Result(BitArray, Nil) { + let #(x509.Oid(oid_components), value) = attr + use encoded_value <- result.try(x509.encode_attribute_value(value)) + use oid_encoded <- result.try(der.encode_oid(oid_components)) + der.encode_sequence(bit_array.concat([oid_encoded, encoded_value])) +} + +/// Parse a public key from DER-encoded SubjectPublicKeyInfo. +/// +/// Decodes the algorithm identifier and dispatches to the appropriate key parser (RSA, EC, Ed25519, etc.). +/// Returns Error with the unknown OID if the algorithm is not supported. +pub fn parse_public_key( + spki_bytes: BitArray, +) -> Result(x509.PublicKey, x509.Oid) { + let result = + { + use #(spki_content, _) <- result.try(der.parse_sequence(spki_bytes)) + use #(alg_id_bytes, after_alg) <- result.try(der.parse_sequence( + spki_content, + )) + use #(alg_oid, _alg_params) <- result.try(der.parse_oid(alg_id_bytes)) + use _ <- result.try(der.parse_bit_string(after_alg)) + + Ok(alg_oid) + } + |> result.replace_error(x509.Oid([])) + + result + |> result.try(dispatch_public_key_parse(_, spki_bytes)) +} + +fn dispatch_public_key_parse( + alg_oid: List(Int), + spki_bytes: BitArray, +) -> Result(x509.PublicKey, x509.Oid) { + case alg_oid { + // id-ecPublicKey + [1, 2, 840, 10_045, 2, 1] -> + ec.public_key_from_der(spki_bytes) + |> result.map(x509.EcPublicKey) + |> result.replace_error(x509.Oid(alg_oid)) + // rsaEncryption + [1, 2, 840, 113_549, 1, 1, 1] -> + rsa.public_key_from_der(spki_bytes, rsa.Spki) + |> result.map(x509.RsaPublicKey) + |> result.replace_error(x509.Oid(alg_oid)) + // id-X25519 / id-X448 (RFC 8410) + [1, 3, 101, 110] | [1, 3, 101, 111] -> + xdh.public_key_from_der(spki_bytes) + |> result.map(x509.XdhPublicKey) + |> result.replace_error(x509.Oid(alg_oid)) + // id-Ed25519 / id-Ed448 (RFC 8410) + [1, 3, 101, 112] | [1, 3, 101, 113] -> + eddsa.public_key_from_der(spki_bytes) + |> result.map(x509.EdPublicKey) + |> result.replace_error(x509.Oid(alg_oid)) + _ -> Error(x509.Oid(alg_oid)) + } +} + +/// Recursively parse a sequence of Subject Alternative Name entries. +/// +/// Accumulates SAN entries from DER-encoded GeneralNames structure. +/// Returns Error(Nil) if parsing fails for any entry. +/// When is_critical is True, unknown GeneralName types cause an error. +pub fn parse_general_names( + bytes: BitArray, + acc: List(x509.SubjectAltName), + is_critical: Bool, +) -> Result(List(x509.SubjectAltName), Nil) { + case bytes { + <<>> -> Ok(list.reverse(acc)) + _ -> { + use #(san, rest) <- result.try(parse_general_name(bytes, is_critical)) + parse_general_names(rest, [san, ..acc], is_critical) + } + } +} + +/// Parse a single Subject Alternative Name entry. +/// +/// Decodes one GeneralName from DER encoding (DNS name, email, IP address, etc.). +/// Returns Error(Nil) if the entry is malformed. +/// When is_critical is True, unknown GeneralName types return Error. +pub fn parse_general_name( + bytes: BitArray, + is_critical: Bool, +) -> Result(#(x509.SubjectAltName, BitArray), Nil) { + use #(tag, value, rest) <- result.try(der.parse_tlv(bytes)) + + case tag { + // [0] otherName - SEQUENCE { type-id OID, [0] value ANY } + 0xa0 -> { + use #(oid_components, after_oid) <- result.try(der.parse_oid(value)) + use #(other_value, _) <- result.try(der.parse_context_tag(after_oid, 0)) + Ok(#(x509.OtherName(x509.Oid(oid_components), other_value), rest)) + } + // [1] rfc822Name (email) + 0x81 -> { + bit_array.to_string(value) + |> result.map(fn(s) { #(x509.Email(s), rest) }) + } + // [2] dNSName + 0x82 -> { + bit_array.to_string(value) + |> result.map(fn(s) { #(x509.DnsName(s), rest) }) + } + // [4] directoryName - explicit Name (SEQUENCE of RDNs) + 0xa4 -> { + use #(name_content, remaining) <- result.try(der.parse_sequence(value)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + use name <- result.try(parse_name(name_content)) + Ok(#(x509.DirectoryName(name), rest)) + } + // [6] uniformResourceIdentifier (URI) + 0x86 -> { + bit_array.to_string(value) + |> result.map(fn(s) { #(x509.Uri(s), rest) }) + } + // [7] iPAddress + 0x87 -> Ok(#(x509.IpAddress(value), rest)) + // [8] registeredID - implicit OID + 0x88 -> { + // The value contains raw OID content bytes (without tag/length) + use oid_components <- result.try(der.decode_oid_components(value)) + Ok(#(x509.RegisteredId(x509.Oid(oid_components)), rest)) + } + _ -> + case is_critical { + True -> Error(Nil) + False -> Ok(#(x509.Unknown(tag, value), rest)) + } + } +} + +/// Encode a single Subject Alternative Name entry to DER format. +/// +/// Produces a context-specific tagged value for supported GeneralName types: +/// DNS names, email addresses, IP addresses, URIs, directory names, +/// registered IDs, and otherName entries. +/// Returns Error(Nil) for Unknown SAN types. +pub fn encode_general_name(san: x509.SubjectAltName) -> Result(BitArray, Nil) { + case san { + x509.DnsName(name) -> + der.encode_context_primitive_tag(2, bit_array.from_string(name)) + x509.Email(email) -> + der.encode_context_primitive_tag(1, bit_array.from_string(email)) + x509.IpAddress(ip) -> der.encode_context_primitive_tag(7, ip) + x509.Uri(uri) -> + der.encode_context_primitive_tag(6, bit_array.from_string(uri)) + x509.DirectoryName(name) -> { + use encoded_name <- result.try(encode_name(name)) + der.encode_context_tag(4, encoded_name) + } + x509.RegisteredId(x509.Oid(components)) -> { + use oid_encoded <- result.try(der.encode_oid(components)) + // Extract just the OID content (skip tag and length) for implicit tagging + use #(_, oid_content, _) <- result.try(der.parse_tlv(oid_encoded)) + der.encode_context_primitive_tag(8, oid_content) + } + x509.OtherName(x509.Oid(oid_components), value) -> { + use oid_encoded <- result.try(der.encode_oid(oid_components)) + use value_tagged <- result.try(der.encode_context_tag(0, value)) + let content = bit_array.concat([oid_encoded, value_tagged]) + der.encode_context_tag(0, content) + } + x509.Unknown(_, _) -> Error(Nil) + } +} + +/// Parse a single X.509 extension from DER encoding. +/// +/// Extracts the extension OID, critical flag, and DER-encoded value bytes. +/// Returns Error(Nil) if the extension structure is invalid. +pub fn parse_single_extension( + bytes: BitArray, +) -> Result(#(x509.Oid, Bool, BitArray), Nil) { + use #(oid_components, after_oid) <- result.try(der.parse_oid(bytes)) + let #(is_critical, after_critical) = case after_oid { + <<0x01, 0x01, critical_byte, rest:bits>> -> #(critical_byte != 0, rest) + other -> #(False, other) + } + use #(value, remaining) <- result.try(der.parse_octet_string(after_critical)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + Ok(#(x509.Oid(oid_components), is_critical, value)) +} + +/// Parse a Subject Alternative Name extension from DER-encoded bytes. +/// +/// Decodes the extension value containing a GeneralNames sequence. +/// Returns Error(Nil) if the extension format is invalid. +/// When is_critical is True, unknown GeneralName types return Error. +pub fn parse_san_extension( + bytes: BitArray, + is_critical: Bool, +) -> Result(List(x509.SubjectAltName), Nil) { + use #(san_content, _) <- result.try(der.parse_sequence(bytes)) + parse_general_names(san_content, [], is_critical) +} + +/// Encode an X.509 AlgorithmIdentifier to DER format. +/// +/// Produces a DER SEQUENCE with OID and optional NULL parameters (for RSA signatures). +/// Returns Error(Nil) if OID encoding fails. +pub fn encode_algorithm_identifier(sig_alg: SigAlgInfo) -> Result(BitArray, Nil) { + let SigAlgInfo(oid, include_null_params) = sig_alg + let x509.Oid(components) = oid + use oid_encoded <- result.try(der.encode_oid(components)) + case include_null_params { + True -> der.encode_sequence(bit_array.concat([oid_encoded, <<0x05, 0x00>>])) + False -> der.encode_sequence(oid_encoded) + } +} + +/// Extract raw public key bytes from a SubjectPublicKeyInfo structure. +/// +/// Skips the algorithm identifier and returns only the BIT STRING key data. +/// Returns Error(Nil) if the SPKI structure is invalid. +pub fn extract_spki_public_key_bytes(spki: BitArray) -> Result(BitArray, Nil) { + use #(spki_content, _) <- result.try(der.parse_sequence(spki)) + use #(_, after_alg) <- result.try(der.parse_sequence(spki_content)) + use #(pub_key_bytes, _) <- result.try(der.parse_bit_string(after_alg)) + Ok(pub_key_bytes) +} + +/// Decode PEM-encoded data to DER bytes. +/// +/// Extracts base64 data between begin and end markers and decodes to binary. +/// Returns Error(Nil) if markers are not found or base64 decoding fails. +pub fn decode_pem( + pem: String, + begin_marker: String, + end_marker: String, +) -> Result(BitArray, Nil) { + decode_pem_all(pem, begin_marker, end_marker) + |> result.try(list.first) +} + +/// Decode all PEM blocks matching the given markers to DER bytes. +/// +/// Returns all matching blocks in order. Returns Ok([]) if no blocks found. +/// Returns Error(Nil) if any block has invalid base64. +pub fn decode_pem_all( + pem: String, + begin_marker: String, + end_marker: String, +) -> Result(List(BitArray), Nil) { + let lines = string.split(pem, "\n") + let lines = list.map(lines, string.trim) + use blocks <- result.try( + extract_all_pem_bodies(lines, begin_marker, end_marker, []), + ) + list.try_map(blocks, fn(body_lines) { + let body = string.join(body_lines, "") + bit_array.base64_decode(body) + }) +} + +fn extract_all_pem_bodies( + lines: List(String), + begin_marker: String, + end_marker: String, + acc: List(List(String)), +) -> Result(List(List(String)), Nil) { + case extract_pem_body(lines, False, [], begin_marker, end_marker) { + Error(PemNotFound) -> Ok(list.reverse(acc)) + Error(PemMalformed) -> Error(Nil) + Ok(#(body, remaining)) -> + extract_all_pem_bodies(remaining, begin_marker, end_marker, [body, ..acc]) + } +} + +fn extract_pem_body( + lines: List(String), + in_body: Bool, + acc: List(String), + begin_marker: String, + end_marker: String, +) -> Result(#(List(String), List(String)), PemError) { + case lines, in_body { + [], False -> Error(PemNotFound) + [], True -> Error(PemMalformed) + [line, ..rest], False -> { + case string.starts_with(line, begin_marker) { + True -> extract_pem_body(rest, True, acc, begin_marker, end_marker) + False -> extract_pem_body(rest, False, acc, begin_marker, end_marker) + } + } + [line, ..rest], True -> { + case string.starts_with(line, end_marker) { + True -> Ok(#(list.reverse(acc), rest)) + False -> + extract_pem_body(rest, True, [line, ..acc], begin_marker, end_marker) + } + } + } +} + +/// Encode DER bytes to PEM format with the specified markers. +/// +/// Produces a base64-encoded string wrapped in the given begin and end markers, +/// with lines wrapped at 64 characters per RFC 7468. +pub fn encode_pem( + der: BitArray, + begin_marker: String, + end_marker: String, +) -> String { + let encoded = bit_array.base64_encode(der, True) + let lines = + utils.chunk_string(encoded, 64) + |> list.map(fn(line) { line <> "\n" }) + + string_tree.new() + |> string_tree.append(begin_marker <> "\n") + |> string_tree.append_tree(string_tree.from_strings(lines)) + |> string_tree.append(end_marker <> "\n\n") + |> string_tree.to_string +} + +/// Encode a Subject Alternative Name extension to DER format. +/// +/// Produces a DER-encoded extension structure with OID, optional critical flag, +/// and the encoded GeneralNames sequence. +/// Returns Error(Nil) if encoding fails (e.g., Unknown SAN types). +pub fn encode_san_extension( + sans: List(x509.SubjectAltName), + critical: Bool, +) -> Result(BitArray, Nil) { + let oid_components = [2, 5, 29, 17] + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + sans + |> list.reverse + |> list.try_map(encode_general_name) + |> result.map(bit_array.concat) + |> result.try(der.encode_sequence) + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { + case critical { + True -> + bit_array.concat([oid_encoded, der.encode_bool(True), value_octet]) + False -> bit_array.concat([oid_encoded, value_octet]) + } + }) + |> result.try(der.encode_sequence) +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/rsa.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/rsa.gleam new file mode 100644 index 0000000..8d8af28 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/rsa.gleam @@ -0,0 +1,386 @@ +//// RSA (Rivest-Shamir-Adleman) cryptography. +//// +//// RSA key generation, signing, and encryption. +//// RSA keys can be used for both digital signatures and encryption. +//// +//// ## Key Generation +//// +//// ```gleam +//// import kryptos/rsa +//// +//// let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048) +//// ``` +//// +//// ## Signing (RSA-PSS) +//// +//// ```gleam +//// import kryptos/rsa +//// import kryptos/hash +//// +//// let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048) +//// let message = <<"hello world":utf8>> +//// let padding = rsa.Pss(rsa.SaltLengthHashLen) +//// let signature = rsa.sign(private_key, message, hash.Sha256, padding) +//// let valid = rsa.verify(public_key, message, signature, hash.Sha256, padding) +//// ``` +//// +//// ## Encryption (RSA-OAEP) +//// +//// ```gleam +//// import kryptos/rsa +//// import kryptos/hash +//// +//// let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048) +//// let plaintext = <<"secret":utf8>> +//// let padding = rsa.Oaep(hash: hash.Sha256, label: <<>>) +//// let assert Ok(ciphertext) = rsa.encrypt(public_key, plaintext, padding) +//// let assert Ok(decrypted) = rsa.decrypt(private_key, ciphertext, padding) +//// ``` + +import gleam/result +import gleam/string +import kryptos/hash.{type HashAlgorithm} +import kryptos/internal/rsa_crt + +/// An RSA private key. +pub type PrivateKey + +/// An RSA public key. +pub type PublicKey + +/// Format for encoding/decoding RSA private keys. +pub type PrivateKeyFormat { + /// PKCS#8 format (PrivateKeyInfo) - works with all key types. + Pkcs8 + /// PKCS#1 format (RSAPrivateKey) - RSA-specific. + Pkcs1 +} + +/// Format for encoding/decoding RSA public keys. +pub type PublicKeyFormat { + /// SPKI format (SubjectPublicKeyInfo) - works with all key types. + Spki + /// PKCS#1 format (RSAPublicKey) - RSA-specific. + RsaPublicKey +} + +/// The minimum allowed RSA key size in bits. +pub const min_key_size = 1024 + +/// Salt length options for RSA-PSS signatures. +pub type PssSaltLength { + /// Salt length equals hash output length (recommended). + SaltLengthHashLen + /// Maximum salt length for the key and hash combination. + SaltLengthMax + /// Explicit salt length in bytes. + SaltLengthExplicit(Int) +} + +/// Padding scheme for RSA signatures. +pub type SignPadding { + /// PKCS#1 v1.5 signature padding. + Pkcs1v15 + /// RSA-PSS (Probabilistic Signature Scheme) padding. + Pss(PssSaltLength) +} + +/// Padding scheme for RSA encryption. +pub type EncryptPadding { + /// PKCS#1 v1.5 encryption padding. + /// + /// **Warning**: Vulnerable to padding oracle attacks. Prefer OAEP for new applications. + /// + /// **JavaScript target**: Decryption may fail on Node.js 20.x due to CVE-2023-46809 + /// which disables PKCS#1 v1.5 decryption to prevent the Marvin timing attack. Use + /// Node.js 22+ or OAEP padding instead. + EncryptPkcs1v15 + /// RSA-OAEP (Optimal Asymmetric Encryption Padding). + /// + /// The hash algorithm is used for both OAEP and MGF1. + /// The label is optional associated data (usually empty). + Oaep(hash: HashAlgorithm, label: BitArray) +} + +/// Generates an RSA key pair with the specified key size. +/// +/// The key size must be >= 1024 bits. +/// +/// ## Example +/// +/// ```gleam +/// let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048) +/// ``` +pub fn generate_key_pair(bits: Int) -> Result(#(PrivateKey, PublicKey), Nil) { + case bits >= min_key_size { + True -> Ok(do_generate_key_pair(bits)) + False -> Error(Nil) + } +} + +@external(erlang, "kryptos_ffi", "rsa_generate_key_pair") +@external(javascript, "../kryptos_ffi.mjs", "rsaGenerateKeyPair") +fn do_generate_key_pair(bits: Int) -> #(PrivateKey, PublicKey) + +/// Signs a message using RSA with the specified hash algorithm and padding. +/// +/// The message is hashed internally using the provided algorithm before signing. +@external(erlang, "kryptos_ffi", "rsa_sign") +@external(javascript, "../kryptos_ffi.mjs", "rsaSign") +pub fn sign( + private_key: PrivateKey, + message: BitArray, + hash: HashAlgorithm, + padding: SignPadding, +) -> BitArray + +/// Verifies an RSA signature against a message. +/// +/// The same hash algorithm and padding used during signing must be used +/// for verification. +@external(erlang, "kryptos_ffi", "rsa_verify") +@external(javascript, "../kryptos_ffi.mjs", "rsaVerify") +pub fn verify( + public_key: PublicKey, + message message: BitArray, + signature signature: BitArray, + hash hash: HashAlgorithm, + padding padding: SignPadding, +) -> Bool + +/// Encrypts data using RSA with the specified padding scheme. +/// +/// **Note**: RSA encryption should only be used for small amounts of data +/// (typically symmetric keys). For bulk encryption, use a symmetric cipher +/// with a randomly generated key, then encrypt that key with RSA. +@external(erlang, "kryptos_ffi", "rsa_encrypt") +@external(javascript, "../kryptos_ffi.mjs", "rsaEncrypt") +pub fn encrypt( + public_key: PublicKey, + plaintext: BitArray, + padding: EncryptPadding, +) -> Result(BitArray, Nil) + +/// Decrypts data using RSA with the specified padding scheme. +@external(erlang, "kryptos_ffi", "rsa_decrypt") +@external(javascript, "../kryptos_ffi.mjs", "rsaDecrypt") +pub fn decrypt( + private_key: PrivateKey, + ciphertext: BitArray, + padding: EncryptPadding, +) -> Result(BitArray, Nil) + +/// Imports an RSA private key from PEM-encoded data. +@external(erlang, "kryptos_ffi", "rsa_import_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "rsaImportPrivateKeyPem") +pub fn from_pem( + pem: String, + format: PrivateKeyFormat, +) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Imports an RSA private key from DER-encoded data. +@external(erlang, "kryptos_ffi", "rsa_import_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "rsaImportPrivateKeyDer") +pub fn from_der( + der: BitArray, + format: PrivateKeyFormat, +) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports an RSA private key to PEM format. +pub fn to_pem(key: PrivateKey, format: PrivateKeyFormat) -> Result(String, Nil) { + do_to_pem(key, format) |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "rsa_export_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "rsaExportPrivateKeyPem") +fn do_to_pem(key: PrivateKey, format: PrivateKeyFormat) -> Result(String, Nil) + +/// Exports an RSA private key to DER format. +@external(erlang, "kryptos_ffi", "rsa_export_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "rsaExportPrivateKeyDer") +pub fn to_der( + key: PrivateKey, + format: PrivateKeyFormat, +) -> Result(BitArray, Nil) + +/// Imports an RSA public key from PEM-encoded data. +@external(erlang, "kryptos_ffi", "rsa_import_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "rsaImportPublicKeyPem") +pub fn public_key_from_pem( + pem: String, + format: PublicKeyFormat, +) -> Result(PublicKey, Nil) + +/// Imports an RSA public key from DER-encoded data. +@external(erlang, "kryptos_ffi", "rsa_import_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "rsaImportPublicKeyDer") +pub fn public_key_from_der( + der: BitArray, + format: PublicKeyFormat, +) -> Result(PublicKey, Nil) + +/// Exports an RSA public key to PEM format. +pub fn public_key_to_pem( + key: PublicKey, + format: PublicKeyFormat, +) -> Result(String, Nil) { + do_public_key_to_pem(key, format) + |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "rsa_export_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "rsaExportPublicKeyPem") +fn do_public_key_to_pem( + key: PublicKey, + format: PublicKeyFormat, +) -> Result(String, Nil) + +/// Exports an RSA public key to DER format. +@external(erlang, "kryptos_ffi", "rsa_export_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "rsaExportPublicKeyDer") +pub fn public_key_to_der( + key: PublicKey, + format: PublicKeyFormat, +) -> Result(BitArray, Nil) + +/// Derives the public key from an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_public_key_from_private") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyFromPrivate") +pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey + +/// Returns the modulus size in bits for an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_private_key_modulus_bits") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyModulusBits") +pub fn modulus_bits(key: PrivateKey) -> Int + +/// Returns the modulus size in bits for an RSA public key. +@external(erlang, "kryptos_ffi", "rsa_public_key_modulus_bits") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyModulusBits") +pub fn public_key_modulus_bits(key: PublicKey) -> Int + +/// Returns the public exponent for an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_private_key_public_exponent") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyPublicExponent") +pub fn public_exponent(key: PrivateKey) -> Int + +/// Returns the public exponent for an RSA public key. +@external(erlang, "kryptos_ffi", "rsa_public_key_public_exponent") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyPublicExponent") +pub fn public_key_exponent(key: PublicKey) -> Int + +/// Returns the modulus (n) as big-endian bytes for an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_private_key_modulus") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyModulus") +pub fn modulus(key: PrivateKey) -> BitArray + +/// Returns the modulus (n) as big-endian bytes for an RSA public key. +@external(erlang, "kryptos_ffi", "rsa_public_key_modulus") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyModulus") +pub fn public_key_modulus(key: PublicKey) -> BitArray + +/// Returns the public exponent (e) as big-endian bytes for an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_private_key_public_exponent_bytes") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyPublicExponentBytes") +pub fn public_exponent_bytes(key: PrivateKey) -> BitArray + +/// Returns the public exponent (e) as big-endian bytes for an RSA public key. +@external(erlang, "kryptos_ffi", "rsa_public_key_exponent_bytes") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyExponentBytes") +pub fn public_key_exponent_bytes(key: PublicKey) -> BitArray + +/// Returns the private exponent (d) as big-endian bytes for an RSA private key. +@external(erlang, "kryptos_ffi", "rsa_private_key_private_exponent_bytes") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyPrivateExponentBytes") +pub fn private_exponent_bytes(key: PrivateKey) -> BitArray + +/// Returns the first prime factor (p) as big-endian bytes. +/// +/// The RSA modulus n = p * q. This is part of the CRT (Chinese Remainder +/// Theorem) parameters used for efficient RSA operations. +@external(erlang, "kryptos_ffi", "rsa_private_key_prime1") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyPrime1") +pub fn prime1(key: PrivateKey) -> BitArray + +/// Returns the second prime factor (q) as big-endian bytes. +/// +/// The RSA modulus n = p * q. This is part of the CRT (Chinese Remainder +/// Theorem) parameters used for efficient RSA operations. +@external(erlang, "kryptos_ffi", "rsa_private_key_prime2") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyPrime2") +pub fn prime2(key: PrivateKey) -> BitArray + +/// Returns the first CRT exponent (dp = d mod (p-1)) as big-endian bytes. +/// +/// This is part of the CRT (Chinese Remainder Theorem) parameters used +/// for efficient RSA operations. +@external(erlang, "kryptos_ffi", "rsa_private_key_exponent1") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyExponent1") +pub fn exponent1(key: PrivateKey) -> BitArray + +/// Returns the second CRT exponent (dq = d mod (q-1)) as big-endian bytes. +/// +/// This is part of the CRT (Chinese Remainder Theorem) parameters used +/// for efficient RSA operations. +@external(erlang, "kryptos_ffi", "rsa_private_key_exponent2") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyExponent2") +pub fn exponent2(key: PrivateKey) -> BitArray + +/// Returns the CRT coefficient (qi = q^-1 mod p) as big-endian bytes. +/// +/// This is part of the CRT (Chinese Remainder Theorem) parameters used +/// for efficient RSA operations. +@external(erlang, "kryptos_ffi", "rsa_private_key_coefficient") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyCoefficient") +pub fn coefficient(key: PrivateKey) -> BitArray + +/// Constructs an RSA public key from its components. +@external(erlang, "kryptos_ffi", "rsa_public_key_from_components") +@external(javascript, "../kryptos_ffi.mjs", "rsaPublicKeyFromComponents") +pub fn public_key_from_components( + modulus n: BitArray, + public_exponent e: BitArray, +) -> Result(PublicKey, Nil) + +/// Constructs an RSA private key from its components. +/// +/// Creates a private key from the minimal set of components (n, e, d). +/// CRT parameters are computed automatically using Miller's algorithm. +/// +/// Note: This function is not constant-time. The CRT parameter derivation +/// involves operations that may leak timing information. This is acceptable +/// for key import since the caller already possesses the secret material, +/// but avoid calling this in timing-sensitive contexts. +/// +/// ## Example +/// +/// ```gleam +/// import kryptos/rsa +/// +/// let assert Ok(#(private_key, _public_key)) = rsa.generate_key_pair(2048) +/// let n = rsa.modulus(private_key) +/// let e = rsa.public_exponent_bytes(private_key) +/// let d = rsa.private_exponent_bytes(private_key) +/// let assert Ok(#(reconstructed, _pub)) = rsa.from_components(n, e, d) +/// ``` +pub fn from_components( + modulus n: BitArray, + public_exponent e: BitArray, + private_exponent d: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) { + use #(p, q, dp, dq, qi) <- result.try(rsa_crt.compute_crt_params(n, e, d)) + from_full_components(n, e, d, p, q, dp, dq, qi) +} + +/// Constructs an RSA private key from all components including CRT parameters. +@external(erlang, "kryptos_ffi", "rsa_private_key_from_full_components") +@external(javascript, "../kryptos_ffi.mjs", "rsaPrivateKeyFromFullComponents") +pub fn from_full_components( + modulus n: BitArray, + public_exponent e: BitArray, + private_exponent d: BitArray, + prime1 p: BitArray, + prime2 q: BitArray, + exponent1 dp: BitArray, + exponent2 dq: BitArray, + coefficient qi: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/x509.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509.gleam new file mode 100644 index 0000000..abc4320 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509.gleam @@ -0,0 +1,346 @@ +//// X.509 certificate and CSR types and utilities. +//// +//// Types and helpers for X.509 public key infrastructure: +//// +//// - Distinguished Names: Type-safe construction of subject/issuer names +//// - Extensions: Subject Alternative Names, Basic Constraints, Key Usage, etc. +//// - Public Keys: Unified representation for RSA, ECDSA, EdDSA, and XDH keys +//// - Signature Algorithms: Common signing algorithms for certificates and CSRs +//// - Certificate Fields: Validity periods, authority key identifiers +//// +//// See also: +//// - `x509/csr` for creating and parsing Certificate Signing Requests +//// - `x509/certificate` for parsing and working with X.509 certificates +//// +//// ## Distinguished Names +//// +//// Distinguished names (DNs) identify subjects and issuers in X.509 structures. +//// They consist of attribute-value pairs like Common Name (CN), Organization (O), +//// and Country (C). Helper functions provide type-safe construction with correct +//// ASN.1 string encodings: +//// +//// ```gleam +//// import kryptos/x509 +//// +//// let subject = x509.name([ +//// x509.cn("example.com"), +//// x509.organization("Acme Inc"), +//// x509.organizational_unit("Engineering"), +//// x509.locality("San Francisco"), +//// x509.state("California"), +//// x509.country("US"), +//// ]) +//// ``` +//// +//// ## Extensions +//// +//// Subject Alternative Names (SANs) specify additional identities: +//// +//// ```gleam +//// import kryptos/x509.{DnsName, Email, IpAddress, Uri} +//// +//// let extensions = x509.Extensions([ +//// DnsName("www.example.com"), +//// DnsName("api.example.com"), +//// Email("admin@example.com"), +//// Uri("https://example.com/cps"), +//// ]) +//// ``` + +import gleam/int +import gleam/list +import gleam/option.{type Option} +import gleam/string +import gleam/time/timestamp.{type Timestamp} +import kryptos/ec +import kryptos/eddsa +import kryptos/internal/der +import kryptos/rsa +import kryptos/xdh + +const oid_common_name = Oid([2, 5, 4, 3]) + +const oid_organization = Oid([2, 5, 4, 10]) + +const oid_organizational_unit = Oid([2, 5, 4, 11]) + +const oid_country = Oid([2, 5, 4, 6]) + +const oid_state = Oid([2, 5, 4, 8]) + +const oid_locality = Oid([2, 5, 4, 7]) + +const oid_email_address = Oid([1, 2, 840, 113_549, 1, 9, 1]) + +/// An ASN.1 Object Identifier represented as a list of integer components. +pub type Oid { + Oid(components: List(Int)) +} + +/// An X.509 distinguished name, consisting of a sequence of RDNs. +pub type Name { + Name(rdns: List(Rdn)) +} + +/// A Relative Distinguished Name (RDN), containing one or more attribute-value pairs. +pub type Rdn { + Rdn(attributes: List(#(Oid, AttributeValue))) +} + +/// An attribute value in a distinguished name. +/// +/// This type is opaque to ensure values are properly validated for their +/// encoding requirements (e.g., PrintableString for country codes, +/// IA5String for email addresses). +pub opaque type AttributeValue { + Utf8String(String) + PrintableString(String) + Ia5String(String) +} + +/// A Subject Alternative Name entry for X.509 extensions. +pub type SubjectAltName { + /// A DNS hostname (e.g., "example.com"). + DnsName(String) + /// An IP address (4 bytes for IPv4, 16 bytes for IPv6). + IpAddress(BitArray) + /// An email address (e.g., "user@example.com"). + Email(String) + /// A Uniform Resource Identifier (e.g., "https://example.com/path"). + Uri(String) + /// A distinguished name, used when a certificate identifies a directory entity. + DirectoryName(Name) + /// A registered OID identifying a name form (e.g., for hardware modules). + RegisteredId(Oid) + /// An application-specific name form with an OID type and raw value. + OtherName(oid: Oid, value: BitArray) + /// An unknown GeneralName type with its raw tag byte and value. + /// Returned when parsing encounters an unrecognized SAN type. + Unknown(tag: Int, value: BitArray) +} + +/// X.509 certificate extensions. +pub type Extensions { + Extensions(subject_alt_names: List(SubjectAltName)) +} + +/// A public key extracted from an X.509 structure (CSR or certificate). +pub type PublicKey { + /// An elliptic-curve public key (e.g., P-256, P-384). + EcPublicKey(ec.PublicKey) + /// An RSA public key. + RsaPublicKey(rsa.PublicKey) + /// An EdDSA public key (Ed25519 or Ed448). + EdPublicKey(eddsa.PublicKey) + /// An XDH public key (e.g., Curve25519 or Curve448). + XdhPublicKey(xdh.PublicKey) +} + +/// Signature algorithm used in X.509 structures. +pub type SignatureAlgorithm { + /// RSA with SHA-1, suitable for legacy systems. + RsaSha1 + /// RSA with SHA-256 + RsaSha256 + /// RSA with SHA-384 + RsaSha384 + /// RSA with SHA-512 + RsaSha512 + /// ECDSA with SHA-1, for legacy elliptic curve systems. + EcdsaSha1 + /// ECDSA with SHA-256 + EcdsaSha256 + /// ECDSA with SHA-384 + EcdsaSha384 + /// ECDSA with SHA-512 + EcdsaSha512 + /// Edwards-Curve Digital Signature Algorithm using Ed25519 + Ed25519 + /// Edwards-Curve Digital Signature Algorithm using Ed448 + Ed448 +} + +/// Basic Constraints extension - indicates if cert is a CA. +pub type BasicConstraints { + BasicConstraints(ca: Bool, path_len_constraint: Option(Int)) +} + +/// Key Usage flags for certificates. +pub type KeyUsage { + /// Verify digital signatures (other than certificates and CRLs). + DigitalSignature + /// Verify signatures for non-repudiation services (also called contentCommitment). + NonRepudiation + /// Encipher private or secret keys (e.g., RSA key transport). + KeyEncipherment + /// Directly encrypt raw user data (without key agreement). + DataEncipherment + /// Key agreement protocols (e.g., Diffie-Hellman). + KeyAgreement + /// Verify signatures on public key certificates (CA certificates). + KeyCertSign + /// Verify signatures on certificate revocation lists. + CrlSign + /// With KeyAgreement, may only encipher data during key agreement. + EncipherOnly + /// With KeyAgreement, may only decipher data during key agreement. + DecipherOnly +} + +/// Extended Key Usage purposes. +pub type ExtendedKeyUsage { + /// TLS server authentication. + ServerAuth + /// TLS client authentication. + ClientAuth + /// Signing downloadable executable code. + CodeSigning + /// Email protection (S/MIME signing and encryption). + EmailProtection + /// Signing OCSP responses. + OcspSigning +} + +/// Certificate validity period from not before to not after (inclusive). +pub type Validity { + Validity(not_before: Timestamp, not_after: Timestamp) +} + +/// Authority Key Identifier extension. Identifies the public key used to +/// verify the certificate's signature. +pub type AuthorityKeyIdentifier { + AuthorityKeyIdentifier( + key_identifier: Option(BitArray), + authority_cert_issuer: Option(List(SubjectAltName)), + authority_cert_serial_number: Option(BitArray), + ) +} + +/// Builds a distinguished name from a list of attribute-value pairs. +/// +/// Creates a Name with each attribute in its own Relative Distinguished Name +/// (RDN). Use helper functions like `cn`, `organization`, `country`, etc. +/// to construct the attribute list. +pub fn name(attributes: List(#(Oid, AttributeValue))) -> Name { + Name(list.map(attributes, fn(attr) { Rdn([attr]) })) +} + +/// Creates a Common Name (CN) attribute. +/// +/// The Common Name typically contains the primary identifier for the subject, +/// such as a domain name for server certificates or a person's name for +/// client certificates. +pub fn cn(value: String) -> #(Oid, AttributeValue) { + #(oid_common_name, Utf8String(value)) +} + +/// Creates an Organization (O) attribute. +pub fn organization(value: String) -> #(Oid, AttributeValue) { + #(oid_organization, Utf8String(value)) +} + +/// Creates an Organizational Unit (OU) attribute. +pub fn organizational_unit(value: String) -> #(Oid, AttributeValue) { + #(oid_organizational_unit, Utf8String(value)) +} + +/// Creates a Country (C) attribute. +/// +/// Uses PrintableString encoding as required by X.520. +/// +/// **Important:** The value must be a two-letter uppercase ISO 3166-1 alpha-2 +/// country code (e.g., "US", "GB", "DE"). Non-ASCII or incorrectly formatted +/// values will produce non-compliant DER that may be rejected by CAs and clients. +pub fn country(value: String) -> #(Oid, AttributeValue) { + #(oid_country, PrintableString(value)) +} + +/// Creates a State or Province (ST) attribute. +pub fn state(value: String) -> #(Oid, AttributeValue) { + #(oid_state, Utf8String(value)) +} + +/// Creates a Locality (L) attribute. +pub fn locality(value: String) -> #(Oid, AttributeValue) { + #(oid_locality, Utf8String(value)) +} + +/// Creates an Email Address attribute. +/// +/// Note: emailAddress in the DN is deprecated; prefer using +/// Subject Alternative Names via `csr.with_email` instead. +/// +/// **Important:** The value must contain only ASCII characters. +/// Non-ASCII values will produce non-compliant DER that may be rejected +/// by CAs and clients. +pub fn email_address(value: String) -> #(Oid, AttributeValue) { + #(oid_email_address, Ia5String(value)) +} + +@internal +pub fn encode_attribute_value(value: AttributeValue) -> Result(BitArray, Nil) { + case value { + Utf8String(s) -> der.encode_utf8_string(s) + PrintableString(s) -> der.encode_printable_string(s) + Ia5String(s) -> der.encode_ia5_string(s) + } +} + +@internal +pub fn utf8_string(value: String) -> AttributeValue { + Utf8String(value) +} + +@internal +pub fn printable_string(value: String) -> AttributeValue { + PrintableString(value) +} + +@internal +pub fn ia5_string(value: String) -> AttributeValue { + Ia5String(value) +} + +/// Extracts the string value from an AttributeValue. +/// +/// Returns the underlying string regardless of encoding type +/// (UTF8String, PrintableString, or IA5String). +pub fn attribute_value_to_string(value: AttributeValue) -> String { + case value { + Utf8String(s) -> s + PrintableString(s) -> s + Ia5String(s) -> s + } +} + +/// Converts a distinguished name to a human-readable string. +/// +/// Formats the name in OpenSSL style: "CN=example.com, O=Acme Inc, C=US" +/// +/// Known OIDs are displayed with their standard abbreviations (CN, O, OU, C, ST, L). +/// Unknown OIDs are displayed in dotted-decimal notation (e.g., "1.2.3.4=value"). +pub fn name_to_string(name: Name) -> String { + let Name(rdns) = name + rdns + |> list.flat_map(fn(rdn) { + let Rdn(attributes) = rdn + list.map(attributes, fn(attr) { + let #(oid, value) = attr + oid_to_abbrev(oid) <> "=" <> attribute_value_to_string(value) + }) + }) + |> string.join(", ") +} + +fn oid_to_abbrev(oid: Oid) -> String { + case oid { + Oid([2, 5, 4, 3]) -> "CN" + Oid([2, 5, 4, 6]) -> "C" + Oid([2, 5, 4, 7]) -> "L" + Oid([2, 5, 4, 8]) -> "ST" + Oid([2, 5, 4, 10]) -> "O" + Oid([2, 5, 4, 11]) -> "OU" + Oid([1, 2, 840, 113_549, 1, 9, 1]) -> "emailAddress" + Oid(components) -> string.join(list.map(components, int.to_string), ".") + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/certificate.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/certificate.gleam new file mode 100644 index 0000000..2311e2e --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/certificate.gleam @@ -0,0 +1,1460 @@ +//// X.509 Certificate generation and parsing. +//// +//// Builder for creating self-signed X.509 certificates. +//// CA-signing is not currently supported. +//// +//// ## Example +//// +//// ```gleam +//// import gleam/option +//// import gleam/time/duration +//// import gleam/time/timestamp +//// import kryptos/ec +//// import kryptos/hash +//// import kryptos/x509 +//// import kryptos/x509/certificate +//// +//// let #(private_key, _) = ec.generate_key_pair(ec.P256) +//// +//// let subject = +//// x509.name([ +//// x509.cn("example.com"), +//// x509.organization("Acme Inc"), +//// ]) +//// +//// let now = timestamp.system_time() +//// // 86,400 seconds per day per CA/Browser Forum definition +//// let one_year_later = timestamp.add(now, duration.seconds(86_400 * 365)) +//// let validity = x509.Validity(not_before: now, not_after: one_year_later) +//// +//// let assert Ok(builder) = +//// certificate.new() +//// |> certificate.with_subject(subject) +//// |> certificate.with_validity(validity) +//// |> certificate.with_basic_constraints(ca: False, path_len_constraint: option.None) +//// |> certificate.with_key_usage(x509.DigitalSignature) +//// |> certificate.with_extended_key_usage(x509.ServerAuth) +//// |> certificate.with_dns_name("example.com") +//// +//// let assert Ok(cert) = +//// certificate.self_signed_with_ecdsa(builder, private_key, hash.Sha256) +//// ``` +//// +//// ## Parsing Certificates +//// +//// ```gleam +//// import kryptos/x509/certificate +//// +//// let pem = "-----BEGIN CERTIFICATE----- +//// MIIBkTCB+wIJAK... +//// -----END CERTIFICATE-----" +//// +//// let assert Ok([cert]) = certificate.from_pem(pem) +//// +//// // Access certificate fields +//// let subject = certificate.subject(cert) +//// let validity = certificate.validity(cert) +//// let public_key = certificate.public_key(cert) +//// +//// // Verify a self-signed certificate +//// let assert Ok(Nil) = certificate.verify_self_signed(cert) +//// ``` + +import gleam/bit_array +import gleam/bool +import gleam/int +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/set.{type Set} +import gleam/time/timestamp.{type Timestamp} +import kryptos/crypto +import kryptos/ec +import kryptos/ecdsa +import kryptos/eddsa +import kryptos/hash.{type HashAlgorithm} +import kryptos/internal/der +import kryptos/internal/utils +import kryptos/internal/x509.{type SigAlgInfo} as x509_internal +import kryptos/rsa +import kryptos/x509 +import kryptos/xdh + +const oid_authority_key_identifier = x509.Oid([2, 5, 29, 35]) + +const oid_basic_constraints = x509.Oid([2, 5, 29, 19]) + +const oid_client_auth = x509.Oid([1, 3, 6, 1, 5, 5, 7, 3, 2]) + +const oid_code_signing = x509.Oid([1, 3, 6, 1, 5, 5, 7, 3, 3]) + +const oid_ed25519 = x509.Oid([1, 3, 101, 112]) + +const oid_ed448 = x509.Oid([1, 3, 101, 113]) + +const oid_email_protection = x509.Oid([1, 3, 6, 1, 5, 5, 7, 3, 4]) + +const oid_extended_key_usage = x509.Oid([2, 5, 29, 37]) + +const oid_key_usage = x509.Oid([2, 5, 29, 15]) + +const oid_ocsp_signing = x509.Oid([1, 3, 6, 1, 5, 5, 7, 3, 9]) + +const oid_server_auth = x509.Oid([1, 3, 6, 1, 5, 5, 7, 3, 1]) + +const oid_subject_key_identifier = x509.Oid([2, 5, 29, 14]) + +const pem_begin = "-----BEGIN CERTIFICATE-----" + +const pem_end = "-----END CERTIFICATE-----" + +/// Phantom type marker for certificates created via the builder. +pub type Built + +/// Phantom type marker for certificates parsed from PEM/DER. +pub type Parsed + +/// Error type for certificate parsing failures. +pub type CertificateError { + /// Failed to parse the certificate data. + ParseError + + /// The certificate uses an algorithm or key type that is not supported. + UnsupportedAlgorithm(x509.Oid) + + /// Cryptographic signature verification failed. + SignatureVerificationFailed + + /// The certificate contains an unrecognized extension marked as critical. + /// + /// Per RFC 5280 §4.2, certificates with unknown critical extensions must + /// be rejected. Non-critical unknown extensions are allowed. + UnrecognizedCriticalExtension(x509.Oid) +} + +type ExtensionsAcc { + ExtensionsAcc( + basic_constraints: Option(x509.BasicConstraints), + key_usage: List(x509.KeyUsage), + extended_key_usage: List(x509.ExtendedKeyUsage), + subject_alt_names: List(x509.SubjectAltName), + subject_key_identifier: Option(BitArray), + authority_key_identifier: Option(x509.AuthorityKeyIdentifier), + raw: List(#(x509.Oid, Bool, BitArray)), + seen_oids: Set(List(Int)), + ) +} + +fn empty_extensions_acc() -> ExtensionsAcc { + ExtensionsAcc( + basic_constraints: option.None, + key_usage: [], + extended_key_usage: [], + subject_alt_names: [], + subject_key_identifier: option.None, + authority_key_identifier: option.None, + raw: [], + seen_oids: set.new(), + ) +} + +/// Configuration for the Subject Key Identifier extension. +pub type SubjectKeyIdentifierConfig { + /// Automatically compute SKI as SHA-1 hash of the public key (RFC 5280 method 1). + SkiAuto + /// Use a custom SKI value. + SkiExplicit(BitArray) +} + +/// Configuration for the Authority Key Identifier extension. +pub type AuthorityKeyIdentifierConfig { + /// Automatically compute AKI as SHA-1 hash of the signing key (default). + AkiAuto + /// Use a custom AKI keyIdentifier value. + AkiExplicit(BitArray) + /// Exclude the AKI extension entirely. + AkiExclude +} + +/// An X.509 Certificate. +/// +/// The phantom type parameter tracks how the certificate was created: +/// - `Certificate(Built)` - created via `self_signed_with_ecdsa` etc. +/// - `Certificate(Parsed)` - created via `from_pem` or `from_der` +/// +/// Export functions (`to_pem`, `to_der`) work on any `Certificate(a)`. +/// Accessor functions (`version`, `subject`, etc.) require `Certificate(Parsed)`. +pub opaque type Certificate(status) { + BuiltCertificate(der: BitArray) + ParsedCertificate( + der: BitArray, + tbs_bytes: BitArray, + signature: BitArray, + version: Int, + serial_number: BitArray, + signature_algorithm: x509.SignatureAlgorithm, + issuer: x509.Name, + validity: x509.Validity, + subject: x509.Name, + public_key: x509.PublicKey, + basic_constraints: Option(x509.BasicConstraints), + key_usage: List(x509.KeyUsage), + extended_key_usage: List(x509.ExtendedKeyUsage), + subject_alt_names: List(x509.SubjectAltName), + subject_key_identifier: Option(BitArray), + authority_key_identifier: Option(x509.AuthorityKeyIdentifier), + extensions: List(#(x509.Oid, Bool, BitArray)), + ) +} + +/// A builder for constructing X.509 certificates. +/// +/// Create a builder with `new()`, configure it with `with_*` functions, then +/// sign with one of the signing functions: +/// +/// - `self_signed_with_ecdsa()` for ECDSA keys +/// - `self_signed_with_rsa()` for RSA keys +/// - `self_signed_with_eddsa()` for Ed25519/Ed448 keys +pub opaque type Builder { + Builder( + subject: x509.Name, + validity: Option(x509.Validity), + basic_constraints: Option(#(Bool, Option(Int))), + key_usage: List(x509.KeyUsage), + extended_key_usage: List(x509.ExtendedKeyUsage), + subject_alt_names: List(x509.SubjectAltName), + serial_number: Option(BitArray), + subject_key_identifier: Option(SubjectKeyIdentifierConfig), + authority_key_identifier: AuthorityKeyIdentifierConfig, + ) +} + +/// Creates a new certificate builder with default values. +/// +/// Use the `with_*` functions to configure the builder, then call +/// a signing function to generate the certificate. +pub fn new() -> Builder { + Builder( + subject: x509.name([]), + validity: option.None, + basic_constraints: option.None, + key_usage: [], + extended_key_usage: [], + subject_alt_names: [], + serial_number: option.None, + subject_key_identifier: option.None, + authority_key_identifier: AkiAuto, + ) +} + +/// Sets the distinguished name subject for the certificate. +pub fn with_subject(builder: Builder, subject: x509.Name) -> Builder { + Builder(..builder, subject:) +} + +/// Sets the validity period for the certificate. +pub fn with_validity(builder: Builder, validity: x509.Validity) -> Builder { + Builder(..builder, validity: option.Some(validity)) +} + +/// Sets the Basic Constraints extension. +/// +/// This extension indicates whether the certificate is a CA certificate +/// and optionally limits the path length of the certification chain. +/// Per RFC 5280, path_len_constraint is only meaningful when ca is True. +pub fn with_basic_constraints( + builder: Builder, + ca ca: Bool, + path_len_constraint path_len_constraint: Option(Int), +) -> Builder { + let effective_path_len = case ca { + True -> path_len_constraint + False -> option.None + } + Builder(..builder, basic_constraints: option.Some(#(ca, effective_path_len))) +} + +/// Adds a Key Usage flag to the certificate. +/// +/// Multiple usages can be added by chaining calls. +pub fn with_key_usage(builder: Builder, usage: x509.KeyUsage) -> Builder { + Builder(..builder, key_usage: [usage, ..builder.key_usage]) +} + +/// Adds an Extended Key Usage purpose to the certificate. +/// +/// EKU narrows allowed purposes beyond Key Usage (e.g., ServerAuth, +/// CodeSigning). Multiple usages can be added by chaining calls. +pub fn with_extended_key_usage( + builder: Builder, + usage: x509.ExtendedKeyUsage, +) -> Builder { + Builder(..builder, extended_key_usage: [usage, ..builder.extended_key_usage]) +} + +/// Adds a DNS name to the Subject Alternative Names extension. +/// +/// The name must contain only ASCII characters. +pub fn with_dns_name(builder: Builder, name: String) -> Result(Builder, Nil) { + case utils.is_ascii(name) { + True -> + Builder(..builder, subject_alt_names: [ + x509.DnsName(name), + ..builder.subject_alt_names + ]) + |> Ok + False -> Error(Nil) + } +} + +/// Adds an email address to the Subject Alternative Names extension. +/// +/// The email must contain only ASCII characters. +pub fn with_email(builder: Builder, email: String) -> Result(Builder, Nil) { + case utils.is_ascii(email) { + True -> + Builder(..builder, subject_alt_names: [ + x509.Email(email), + ..builder.subject_alt_names + ]) + |> Ok + False -> Error(Nil) + } +} + +/// Adds an IP address to the Subject Alternative Names extension. +/// +/// Accepts IPv4 (e.g., "192.168.1.1") or IPv6 (e.g., "2001:db8::1") addresses. +pub fn with_ip(builder: Builder, ip: String) -> Result(Builder, Nil) { + utils.parse_ip(ip) + |> result.map(fn(parsed) { + Builder(..builder, subject_alt_names: [ + x509.IpAddress(parsed), + ..builder.subject_alt_names + ]) + }) +} + +/// Sets the serial number for the certificate. +/// +/// If not set, a random serial number will be generated during signing. +pub fn with_serial_number(builder: Builder, serial: BitArray) -> Builder { + Builder(..builder, serial_number: option.Some(serial)) +} + +/// Enables the Subject Key Identifier extension in the certificate. +/// +/// If not called, the SKI extension will not be included. Use `SkiAuto` to +/// compute from the public key (SHA-1 hash per RFC 5280 method 1) or +/// `SkiExplicit(bytes)` for a custom value. +pub fn with_subject_key_identifier( + builder: Builder, + ski: SubjectKeyIdentifierConfig, +) -> Builder { + Builder(..builder, subject_key_identifier: option.Some(ski)) +} + +/// Configures the Authority Key Identifier extension for the certificate. +/// +/// By default, self-signed certificates include an AKI with keyIdentifier +/// computed as the SHA-1 hash of the signing public key. Use `AkiExplicit` +/// for a custom value or `AkiExclude` to omit the extension. +pub fn with_authority_key_identifier( + builder: Builder, + aki: AuthorityKeyIdentifierConfig, +) -> Builder { + Builder(..builder, authority_key_identifier: aki) +} + +/// Generates a random 20-byte serial number with the high bit cleared per RFC 5280. +pub fn generate_serial_number() -> BitArray { + let bytes = crypto.random_bytes(20) + let assert <> = bytes + <> +} + +/// Signs a self-signed certificate with an ECDSA private key. +/// +/// The public key is derived from the private key and used as both +/// the issuer and subject public key. +pub fn self_signed_with_ecdsa( + builder: Builder, + key: ec.PrivateKey, + hash: HashAlgorithm, +) -> Result(Certificate(Built), Nil) { + use validity <- result.try(option.to_result(builder.validity, Nil)) + use sig_alg <- result.try(x509_internal.ecdsa_sig_alg_info(hash)) + let public_key = ec.public_key_from_private_key(key) + use spki <- result.try(ec.public_key_to_der(public_key)) + + let serial = case builder.serial_number { + option.Some(s) -> s + option.None -> generate_serial_number() + } + + use tbs <- result.try(encode_tbs_certificate( + builder, + serial, + sig_alg, + spki, + validity, + )) + let signature = ecdsa.sign(key, tbs, hash) + + use cert_der <- result.try(encode_certificate(tbs, sig_alg, signature)) + Ok(BuiltCertificate(cert_der)) +} + +/// Signs a self-signed certificate with an RSA private key using PKCS#1 v1.5 padding. +/// +/// The public key is derived from the private key and used as both +/// the issuer and subject public key. +pub fn self_signed_with_rsa( + builder: Builder, + key: rsa.PrivateKey, + hash: HashAlgorithm, +) -> Result(Certificate(Built), Nil) { + use validity <- result.try(option.to_result(builder.validity, Nil)) + use sig_alg <- result.try(x509_internal.rsa_sig_alg_info(hash)) + let public_key = rsa.public_key_from_private_key(key) + use spki <- result.try(rsa.public_key_to_der(public_key, rsa.Spki)) + + let serial = case builder.serial_number { + option.Some(s) -> s + option.None -> generate_serial_number() + } + + use tbs <- result.try(encode_tbs_certificate( + builder, + serial, + sig_alg, + spki, + validity, + )) + let signature = rsa.sign(key, tbs, hash, rsa.Pkcs1v15) + use cert_der <- result.try(encode_certificate(tbs, sig_alg, signature)) + Ok(BuiltCertificate(cert_der)) +} + +/// Signs a self-signed certificate with an EdDSA private key. +/// +/// The public key is derived from the private key and used as both +/// the issuer and subject public key. EdDSA has built-in hashing, so no +/// hash algorithm parameter is needed. +pub fn self_signed_with_eddsa( + builder: Builder, + key: eddsa.PrivateKey, +) -> Result(Certificate(Built), Nil) { + use validity <- result.try(option.to_result(builder.validity, Nil)) + let sig_alg = case eddsa.curve(key) { + eddsa.Ed25519 -> x509_internal.SigAlgInfo(oid_ed25519, False) + eddsa.Ed448 -> x509_internal.SigAlgInfo(oid_ed448, False) + } + let public_key = eddsa.public_key_from_private_key(key) + use spki <- result.try(eddsa.public_key_to_der(public_key)) + + let serial = case builder.serial_number { + option.Some(s) -> s + option.None -> generate_serial_number() + } + + use tbs <- result.try(encode_tbs_certificate( + builder, + serial, + sig_alg, + spki, + validity, + )) + let signature = eddsa.sign(key, tbs) + use cert_der <- result.try(encode_certificate(tbs, sig_alg, signature)) + Ok(BuiltCertificate(cert_der)) +} + +/// Exports the certificate as DER-encoded bytes. +pub fn to_der(cert: Certificate(a)) -> BitArray { + case cert { + BuiltCertificate(der) -> der + ParsedCertificate(der, ..) -> der + } +} + +/// Exports the certificate as a PEM-encoded string. +pub fn to_pem(cert: Certificate(a)) -> String { + x509_internal.encode_pem(to_der(cert), pem_begin, pem_end) +} + +/// Parse all PEM-encoded certificates from a string. +/// +/// Extracts and parses all `-----BEGIN CERTIFICATE-----` blocks from the input. +/// Certificates are returned in the order they appear. +/// +/// **Note:** This function does NOT verify the certificates' cryptographic +/// signatures. To verify a certificate was signed by an issuer, use `verify()`. +/// For self-signed certificates, use `verify_self_signed()`. +pub fn from_pem( + pem: String, +) -> Result(List(Certificate(Parsed)), CertificateError) { + x509_internal.decode_pem_all(pem, pem_begin, pem_end) + |> result.replace_error(ParseError) + |> result.try(list.try_map(_, from_der)) +} + +/// Parse a DER-encoded X.509 certificate. +/// +/// Validates the ASN.1 structure and extracts all standard fields and +/// extensions. Unknown non-critical extensions are preserved but not parsed. +/// +/// **Note:** This function does NOT verify the certificate's cryptographic +/// signature. To verify a certificate was signed by an issuer, use `verify()`. +/// For self-signed certificates, use `verify_self_signed()`. +pub fn from_der(der: BitArray) -> Result(Certificate(Parsed), CertificateError) { + use #(cert_content, remaining) <- result.try( + der.parse_sequence(der) |> result.replace_error(ParseError), + ) + use <- bool.guard( + when: bit_array.byte_size(remaining) != 0, + return: Error(ParseError), + ) + + // Parse TBSCertificate + use #(tbs_bytes, after_tbs) <- result.try( + x509_internal.parse_sequence_with_header(cert_content) + |> result.replace_error(ParseError), + ) + + // Parse the inner content of TBSCertificate + use #(tbs_content, _) <- result.try( + der.parse_sequence(tbs_bytes) + |> result.replace_error(ParseError), + ) + + // Parse version [0] EXPLICIT (optional, defaults to v1 = 0) + use #(version, after_version) <- result.try(parse_certificate_version( + tbs_content, + )) + + // Parse serial number (INTEGER) + use #(serial_number, after_serial) <- result.try( + der.parse_integer(after_version) + |> result.replace_error(ParseError), + ) + + // Parse signature algorithm (SEQUENCE) + use #(sig_alg_bytes, after_sig_alg) <- result.try( + der.parse_sequence(after_serial) + |> result.replace_error(ParseError), + ) + use signature_algorithm <- result.try( + x509_internal.parse_signature_algorithm(sig_alg_bytes) + |> result.map_error(UnsupportedAlgorithm), + ) + + // Parse issuer (Name) + use #(issuer_bytes, after_issuer) <- result.try( + der.parse_sequence(after_sig_alg) + |> result.replace_error(ParseError), + ) + use issuer <- result.try( + x509_internal.parse_name(issuer_bytes) + |> result.replace_error(ParseError), + ) + + // Parse validity (SEQUENCE) + use #(validity_bytes, after_validity) <- result.try( + der.parse_sequence(after_issuer) + |> result.replace_error(ParseError), + ) + use validity <- result.try(parse_validity(validity_bytes)) + + // Parse subject (Name) + use #(subject_bytes, after_subject) <- result.try( + der.parse_sequence(after_validity) + |> result.replace_error(ParseError), + ) + use subject <- result.try( + x509_internal.parse_name(subject_bytes) + |> result.replace_error(ParseError), + ) + + // Parse SubjectPublicKeyInfo (SEQUENCE) + use #(spki_bytes, after_spki) <- result.try( + x509_internal.parse_sequence_with_header(after_subject) + |> result.replace_error(ParseError), + ) + use public_key <- result.try( + x509_internal.parse_public_key(spki_bytes) + |> result.map_error(fn(oid) { + case oid { + x509.Oid([]) -> ParseError + _ -> UnsupportedAlgorithm(oid) + } + }), + ) + + // Parse extensions [3] (optional) + use exts <- result.try(parse_certificate_extensions(after_spki, version)) + + // Parse outer signature algorithm (must match TBS per RFC 5280 §4.1.1.2) + use #(outer_sig_alg_bytes, after_outer_sig_alg) <- result.try( + der.parse_sequence(after_tbs) + |> result.replace_error(ParseError), + ) + use outer_signature_algorithm <- result.try( + x509_internal.parse_signature_algorithm(outer_sig_alg_bytes) + |> result.replace_error(ParseError), + ) + use <- bool.guard( + when: signature_algorithm != outer_signature_algorithm, + return: Error(ParseError), + ) + + // Parse signature (BIT STRING) + use #(signature, _) <- result.try( + der.parse_bit_string(after_outer_sig_alg) + |> result.replace_error(ParseError), + ) + + Ok(ParsedCertificate( + der:, + tbs_bytes:, + signature:, + version:, + serial_number:, + signature_algorithm:, + issuer:, + validity:, + subject:, + public_key:, + basic_constraints: exts.basic_constraints, + key_usage: exts.key_usage, + extended_key_usage: exts.extended_key_usage, + subject_alt_names: exts.subject_alt_names, + subject_key_identifier: exts.subject_key_identifier, + authority_key_identifier: exts.authority_key_identifier, + extensions: exts.raw, + )) +} + +/// Returns the version of a parsed certificate (0 = v1, 1 = v2, 2 = v3). +pub fn version(cert: Certificate(Parsed)) -> Int { + let assert ParsedCertificate(version:, ..) = cert + version +} + +/// Returns the serial number of a parsed certificate. +pub fn serial_number(cert: Certificate(Parsed)) -> BitArray { + let assert ParsedCertificate(serial_number:, ..) = cert + serial_number +} + +/// Returns the signature algorithm used to sign the certificate. +pub fn signature_algorithm(cert: Certificate(Parsed)) -> x509.SignatureAlgorithm { + let assert ParsedCertificate(signature_algorithm:, ..) = cert + signature_algorithm +} + +/// Returns the issuer distinguished name. +/// +/// For self-signed certificates, issuer equals subject. +pub fn issuer(cert: Certificate(Parsed)) -> x509.Name { + let assert ParsedCertificate(issuer:, ..) = cert + issuer +} + +/// Returns the validity period of the certificate. +pub fn validity(cert: Certificate(Parsed)) -> x509.Validity { + let assert ParsedCertificate(validity:, ..) = cert + validity +} + +/// Returns the subject distinguished name. +pub fn subject(cert: Certificate(Parsed)) -> x509.Name { + let assert ParsedCertificate(subject:, ..) = cert + subject +} + +/// Returns the public key embedded in the certificate. +pub fn public_key(cert: Certificate(Parsed)) -> x509.PublicKey { + let assert ParsedCertificate(public_key:, ..) = cert + public_key +} + +/// Returns the Basic Constraints extension from a parsed certificate. +pub fn basic_constraints( + cert: Certificate(Parsed), +) -> Result(x509.BasicConstraints, Nil) { + let assert ParsedCertificate(basic_constraints:, ..) = cert + option.to_result(basic_constraints, Nil) +} + +/// Returns the Key Usage flags from a parsed certificate. +pub fn key_usage(cert: Certificate(Parsed)) -> List(x509.KeyUsage) { + let assert ParsedCertificate(key_usage:, ..) = cert + key_usage +} + +/// Returns the Extended Key Usage purposes from a parsed certificate. +pub fn extended_key_usage( + cert: Certificate(Parsed), +) -> List(x509.ExtendedKeyUsage) { + let assert ParsedCertificate(extended_key_usage:, ..) = cert + extended_key_usage +} + +/// Returns the Subject Alternative Names (SANs) from a parsed certificate. +pub fn subject_alt_names(cert: Certificate(Parsed)) -> List(x509.SubjectAltName) { + let assert ParsedCertificate(subject_alt_names:, ..) = cert + subject_alt_names +} + +/// Returns the Subject Key Identifier (SKI) from a parsed certificate. +pub fn subject_key_identifier( + cert: Certificate(Parsed), +) -> Result(BitArray, Nil) { + let assert ParsedCertificate(subject_key_identifier:, ..) = cert + option.to_result(subject_key_identifier, Nil) +} + +/// Returns the Authority Key Identifier (AKI) from a parsed certificate. +pub fn authority_key_identifier( + cert: Certificate(Parsed), +) -> Result(x509.AuthorityKeyIdentifier, Nil) { + let assert ParsedCertificate(authority_key_identifier:, ..) = cert + option.to_result(authority_key_identifier, Nil) +} + +/// Returns all extensions as raw (OID, critical, value) tuples. +/// +/// Includes all extensions, even those with typed representations. +/// The Bool indicates whether the extension was marked as critical per RFC 5280. +pub fn extensions( + cert: Certificate(Parsed), +) -> List(#(x509.Oid, Bool, BitArray)) { + let assert ParsedCertificate(extensions:, ..) = cert + extensions +} + +/// Verify a certificate's signature against an issuer's public key. +/// +/// The public key must be RSA, ECDSA, or EdDSA (XDH keys cannot sign). +pub fn verify( + cert: Certificate(Parsed), + issuer_public_key: x509.PublicKey, +) -> Result(Nil, CertificateError) { + let assert ParsedCertificate(tbs_bytes:, signature:, signature_algorithm:, ..) = + cert + + use <- bool.lazy_guard(when: is_xdh_key(issuer_public_key), return: fn() { + let oid = + xdh_key_oid(issuer_public_key) + |> result.unwrap(x509.Oid([])) + Error(UnsupportedAlgorithm(oid)) + }) + + let verified = + x509_internal.verify_signature( + issuer_public_key, + tbs_bytes, + signature, + signature_algorithm, + ) + + case verified { + True -> Ok(Nil) + False -> Error(SignatureVerificationFailed) + } +} + +/// Verify a self-signed certificate against its own public key. +pub fn verify_self_signed( + cert: Certificate(Parsed), +) -> Result(Nil, CertificateError) { + public_key(cert) + |> verify(cert, _) +} + +fn parse_certificate_version( + bytes: BitArray, +) -> Result(#(Int, BitArray), CertificateError) { + case der.parse_context_tag(bytes, 0) { + Ok(#(version_content, rest)) -> { + use #(version_bytes, _) <- result.try( + der.parse_integer(version_content) + |> result.replace_error(ParseError), + ) + + case version_bytes { + <<0>> -> Ok(#(0, rest)) + <<1>> -> Ok(#(1, rest)) + <<2>> -> Ok(#(2, rest)) + <<_:8>> -> Error(ParseError) + _ -> Error(ParseError) + } + } + Error(_) -> Ok(#(0, bytes)) + } +} + +fn parse_validity(bytes: BitArray) -> Result(x509.Validity, CertificateError) { + use #(not_before, after_not_before) <- result.try( + parse_time(bytes) |> result.replace_error(ParseError), + ) + use #(not_after, _) <- result.try( + parse_time(after_not_before) |> result.replace_error(ParseError), + ) + Ok(x509.Validity(not_before:, not_after:)) +} + +fn parse_time(bytes: BitArray) -> Result(#(Timestamp, BitArray), Nil) { + case bytes { + <<0x17, _:bits>> -> der.parse_utc_time(bytes) + <<0x18, _:bits>> -> der.parse_generalized_time(bytes) + _ -> Error(Nil) + } +} + +/// Parse optional unique IDs with RFC 5280 version validation. +fn parse_optional_unique_ids( + bytes: BitArray, + version: Int, +) -> Result(BitArray, CertificateError) { + case bytes { + <<0x81, _:bits>> | <<0x82, _:bits>> -> { + use <- bool.guard(when: version < 1, return: Error(ParseError)) + case der.parse_tlv(bytes) { + Ok(#(_, _, remaining)) -> parse_optional_unique_ids(remaining, version) + Error(_) -> Error(ParseError) + } + } + _ -> Ok(bytes) + } +} + +/// Parse extensions with RFC 5280 version validation. +fn parse_certificate_extensions( + bytes: BitArray, + version: Int, +) -> Result(ExtensionsAcc, CertificateError) { + use bytes <- result.try(parse_optional_unique_ids(bytes, version)) + case der.parse_context_tag(bytes, 3) { + Ok(#(exts_content, _)) -> { + use <- bool.guard(when: version < 2, return: Error(ParseError)) + use #(exts_seq, _) <- result.try( + der.parse_sequence(exts_content) + |> result.replace_error(ParseError), + ) + parse_extensions_content(exts_seq) + } + Error(_) -> Ok(empty_extensions_acc()) + } +} + +fn parse_raw_extensions( + bytes: BitArray, + acc: List(#(x509.Oid, Bool, BitArray)), +) -> Result(List(#(x509.Oid, Bool, BitArray)), Nil) { + case bytes { + <<>> -> Ok(list.reverse(acc)) + _ -> { + use #(ext_bytes, rest) <- result.try(der.parse_sequence(bytes)) + x509_internal.parse_single_extension(ext_bytes) + |> result.try(fn(ext) { parse_raw_extensions(rest, [ext, ..acc]) }) + } + } +} + +fn process_extension( + acc: ExtensionsAcc, + ext: #(x509.Oid, Bool, BitArray), +) -> Result(ExtensionsAcc, CertificateError) { + let #(oid, is_critical, value) = ext + case oid { + x509.Oid([2, 5, 29, 19]) -> { + parse_basic_constraints_ext(value) + |> result.replace_error(ParseError) + |> result.map(fn(bc) { + ExtensionsAcc(..acc, basic_constraints: option.Some(bc)) + }) + } + x509.Oid([2, 5, 29, 15]) -> { + parse_key_usage_ext(value) + |> result.replace_error(ParseError) + |> result.map(fn(key_usage) { ExtensionsAcc(..acc, key_usage:) }) + } + x509.Oid([2, 5, 29, 37]) -> { + parse_extended_key_usage_ext(value, is_critical) + |> result.map(fn(extended_key_usage) { + ExtensionsAcc(..acc, extended_key_usage:) + }) + } + x509.Oid([2, 5, 29, 17]) -> { + x509_internal.parse_san_extension(value, is_critical) + |> result.replace_error(ParseError) + |> result.map(fn(subject_alt_names) { + ExtensionsAcc(..acc, subject_alt_names:) + }) + } + x509.Oid([2, 5, 29, 14]) -> { + parse_subject_key_identifier_ext(value) + |> result.replace_error(ParseError) + |> result.map(fn(ski) { + ExtensionsAcc(..acc, subject_key_identifier: option.Some(ski)) + }) + } + x509.Oid([2, 5, 29, 35]) -> { + parse_authority_key_identifier_ext(value) + |> result.replace_error(ParseError) + |> result.map(fn(aki) { + ExtensionsAcc(..acc, authority_key_identifier: option.Some(aki)) + }) + } + _ -> { + case is_critical { + True -> Error(UnrecognizedCriticalExtension(oid)) + False -> Ok(acc) + } + } + } +} + +fn parse_extensions_content( + bytes: BitArray, +) -> Result(ExtensionsAcc, CertificateError) { + use raw <- result.try( + parse_raw_extensions(bytes, []) + |> result.replace_error(ParseError), + ) + + list.try_fold(raw, empty_extensions_acc(), fn(acc, ext) { + let #(x509.Oid(components), _, _) = ext + case set.contains(acc.seen_oids, components) { + True -> Error(ParseError) + False -> { + ExtensionsAcc(..acc, seen_oids: set.insert(acc.seen_oids, components)) + |> process_extension(ext) + } + } + }) + |> result.map(fn(acc) { ExtensionsAcc(..acc, raw:) }) +} + +fn parse_subject_key_identifier_ext(bytes: BitArray) -> Result(BitArray, Nil) { + use #(value, remaining) <- result.try(der.parse_octet_string(bytes)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + Ok(value) +} + +fn parse_authority_key_identifier_ext( + bytes: BitArray, +) -> Result(x509.AuthorityKeyIdentifier, Nil) { + use #(inner, remaining) <- result.try(der.parse_sequence(bytes)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + parse_aki_fields( + inner, + key_id: option.None, + issuer: option.None, + serial: option.None, + ) +} + +fn parse_aki_fields( + bytes: BitArray, + key_id key_id: Option(BitArray), + issuer issuer: Option(List(x509.SubjectAltName)), + serial serial: Option(BitArray), +) -> Result(x509.AuthorityKeyIdentifier, Nil) { + case bytes { + <<>> -> + Ok(x509.AuthorityKeyIdentifier( + key_identifier: key_id, + authority_cert_issuer: issuer, + authority_cert_serial_number: serial, + )) + <<0x80, len:8, rest:bits>> -> { + use <- bool.guard( + when: bit_array.byte_size(rest) < len, + return: Error(Nil), + ) + + use key_bytes <- result.try(bit_array.slice(rest, 0, len)) + use remaining <- result.try(bit_array.slice( + rest, + len, + bit_array.byte_size(rest) - len, + )) + + parse_aki_fields( + remaining, + key_id: option.Some(key_bytes), + issuer:, + serial:, + ) + } + <<0xa1, _:bits>> -> { + use #(issuer_content, remaining) <- result.try(der.parse_context_tag( + bytes, + 1, + )) + use parsed_issuers <- result.try(x509_internal.parse_general_names( + issuer_content, + [], + False, + )) + + parse_aki_fields( + remaining, + key_id:, + issuer: option.Some(parsed_issuers), + serial:, + ) + } + <<0x82, len:8, rest:bits>> -> { + use <- bool.guard( + when: bit_array.byte_size(rest) < len, + return: Error(Nil), + ) + + use serial_bytes <- result.try(bit_array.slice(rest, 0, len)) + use remaining <- result.try(bit_array.slice( + rest, + len, + bit_array.byte_size(rest) - len, + )) + + parse_aki_fields( + remaining, + key_id:, + issuer:, + serial: option.Some(serial_bytes), + ) + } + _ -> Error(Nil) + } +} + +fn parse_basic_constraints_ext( + bytes: BitArray, +) -> Result(x509.BasicConstraints, Nil) { + use #(seq_content, remaining) <- result.try(der.parse_sequence(bytes)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + use <- bool.guard( + when: bit_array.byte_size(seq_content) == 0, + return: Ok(x509.BasicConstraints( + ca: False, + path_len_constraint: option.None, + )), + ) + + use #(ca, after_ca) <- result.try(der.parse_bool(seq_content)) + use <- bool.guard( + when: bit_array.byte_size(after_ca) == 0, + return: Ok(x509.BasicConstraints(ca: ca, path_len_constraint: option.None)), + ) + + use <- bool.guard( + when: !ca, + return: Ok(x509.BasicConstraints( + ca: False, + path_len_constraint: option.None, + )), + ) + + use #(path_len_bytes, remaining) <- result.try(der.parse_integer(after_ca)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + use path_len <- result.try(bytes_to_int(path_len_bytes)) + Ok(x509.BasicConstraints(ca: ca, path_len_constraint: option.Some(path_len))) +} + +fn bytes_to_int(bytes: BitArray) -> Result(Int, Nil) { + case bytes { + <> -> Ok(n) + <> -> Ok(n) + <> -> Ok(n) + <> -> Ok(n) + _ -> Error(Nil) + } +} + +fn parse_key_usage_ext(bytes: BitArray) -> Result(List(x509.KeyUsage), Nil) { + case bytes { + <<0x03, len:8, unused_bits:8, rest:bits>> if unused_bits <= 7 && len >= 2 -> { + bit_array.slice(rest, 0, len - 1) + |> result.map(decode_key_usage_bits) + } + _ -> Error(Nil) + } +} + +fn decode_key_usage_bits(bytes: BitArray) -> List(x509.KeyUsage) { + case bytes { + << + digital_signature:1, + non_repudiation:1, + key_encipherment:1, + data_encipherment:1, + key_agreement:1, + key_cert_sign:1, + crl_sign:1, + encipher_only:1, + rest:bits, + >> -> { + let usages = + [ + #(digital_signature, x509.DigitalSignature), + #(non_repudiation, x509.NonRepudiation), + #(key_encipherment, x509.KeyEncipherment), + #(data_encipherment, x509.DataEncipherment), + #(key_agreement, x509.KeyAgreement), + #(key_cert_sign, x509.KeyCertSign), + #(crl_sign, x509.CrlSign), + #(encipher_only, x509.EncipherOnly), + ] + |> list.filter_map(fn(pair) { + let #(bit, usage) = pair + case bit == 1 { + True -> Ok(usage) + False -> Error(Nil) + } + }) + + case rest { + <<1:1, _:bits>> -> [x509.DecipherOnly, ..usages] + _ -> usages + } + } + _ -> [] + } +} + +fn parse_extended_key_usage_ext( + bytes: BitArray, + is_critical: Bool, +) -> Result(List(x509.ExtendedKeyUsage), CertificateError) { + use #(seq_content, _) <- result.try( + der.parse_sequence(bytes) |> result.replace_error(ParseError), + ) + parse_eku_oids(seq_content, [], is_critical) +} + +fn parse_eku_oids( + bytes: BitArray, + acc: List(x509.ExtendedKeyUsage), + is_critical: Bool, +) -> Result(List(x509.ExtendedKeyUsage), CertificateError) { + case bytes { + <<>> -> Ok(list.reverse(acc)) + _ -> { + use #(oid_components, rest) <- result.try( + der.parse_oid(bytes) |> result.replace_error(ParseError), + ) + let eku = case oid_components { + [1, 3, 6, 1, 5, 5, 7, 3, 1] -> option.Some(x509.ServerAuth) + [1, 3, 6, 1, 5, 5, 7, 3, 2] -> option.Some(x509.ClientAuth) + [1, 3, 6, 1, 5, 5, 7, 3, 3] -> option.Some(x509.CodeSigning) + [1, 3, 6, 1, 5, 5, 7, 3, 4] -> option.Some(x509.EmailProtection) + [1, 3, 6, 1, 5, 5, 7, 3, 9] -> option.Some(x509.OcspSigning) + _ -> option.None + } + case eku, is_critical { + option.Some(e), _ -> parse_eku_oids(rest, [e, ..acc], is_critical) + option.None, False -> parse_eku_oids(rest, acc, is_critical) + option.None, True -> + Error(UnrecognizedCriticalExtension(x509.Oid(oid_components))) + } + } + } +} + +fn encode_tbs_certificate( + builder: Builder, + serial: BitArray, + sig_alg: SigAlgInfo, + spki: BitArray, + validity: x509.Validity, +) -> Result(BitArray, Nil) { + use version <- result.try(encode_version()) + use serial_int <- result.try(der.encode_integer(serial)) + use sig_alg_der <- result.try(x509_internal.encode_algorithm_identifier( + sig_alg, + )) + use issuer <- result.try(x509_internal.encode_name(builder.subject)) + use validity_der <- result.try(encode_validity(validity)) + use subject <- result.try(x509_internal.encode_name(builder.subject)) + use extensions <- result.try(encode_extensions(builder, spki)) + + der.encode_sequence( + bit_array.concat([ + version, + serial_int, + sig_alg_der, + issuer, + validity_der, + subject, + spki, + extensions, + ]), + ) +} + +fn encode_version() -> Result(BitArray, Nil) { + der.encode_integer(<<2>>) + |> result.try(der.encode_context_tag(0, _)) +} + +fn encode_validity(validity: x509.Validity) -> Result(BitArray, Nil) { + let x509.Validity(not_before, not_after) = validity + use not_before_der <- result.try(der.encode_timestamp(not_before)) + use not_after_der <- result.try(der.encode_timestamp(not_after)) + der.encode_sequence(bit_array.concat([not_before_der, not_after_der])) +} + +fn encode_extensions(builder: Builder, spki: BitArray) -> Result(BitArray, Nil) { + let x509.Name(rdns) = builder.subject + let subject_is_empty = list.is_empty(rdns) + let sans_is_empty = list.is_empty(builder.subject_alt_names) + use <- bool.guard(when: subject_is_empty && sans_is_empty, return: Error(Nil)) + + let extension_results = [ + encode_basic_constraints_opt(builder.basic_constraints), + encode_key_usage_opt(builder.key_usage), + encode_extended_key_usage_opt(builder.extended_key_usage), + encode_san_opt(builder.subject_alt_names, subject_is_empty), + encode_ski_opt(builder.subject_key_identifier, spki), + encode_aki_opt(builder.authority_key_identifier, spki), + ] + + use results <- result.try(result.all(extension_results)) + let encoded = result.values(results) + + case encoded { + [] -> Ok(<<>>) + _ -> { + encoded + |> bit_array.concat + |> der.encode_sequence + |> result.try(der.encode_context_tag(3, _)) + } + } +} + +fn encode_basic_constraints_opt( + config: Option(#(Bool, Option(Int))), +) -> Result(Result(BitArray, Nil), Nil) { + case config { + option.None -> Ok(Error(Nil)) + option.Some(#(ca, path_len)) -> + result.map(encode_basic_constraints_extension(ca, path_len), Ok) + } +} + +fn encode_key_usage_opt( + usages: List(x509.KeyUsage), +) -> Result(Result(BitArray, Nil), Nil) { + use <- bool.guard(when: list.is_empty(usages), return: Ok(Error(Nil))) + result.map(encode_key_usage_extension(usages), Ok) +} + +fn encode_extended_key_usage_opt( + usages: List(x509.ExtendedKeyUsage), +) -> Result(Result(BitArray, Nil), Nil) { + use <- bool.guard(when: list.is_empty(usages), return: Ok(Error(Nil))) + result.map(encode_extended_key_usage_extension(usages), Ok) +} + +fn encode_san_opt( + sans: List(x509.SubjectAltName), + critical: Bool, +) -> Result(Result(BitArray, Nil), Nil) { + use <- bool.guard(when: list.is_empty(sans), return: Ok(Error(Nil))) + result.map(x509_internal.encode_san_extension(sans, critical), Ok) +} + +fn encode_ski_opt( + config: Option(SubjectKeyIdentifierConfig), + spki: BitArray, +) -> Result(Result(BitArray, Nil), Nil) { + case config { + option.None -> Ok(Error(Nil)) + option.Some(SkiAuto) -> + compute_ski(spki) + |> result.try(encode_subject_key_identifier_extension) + |> result.map(Ok) + option.Some(SkiExplicit(ski)) -> + encode_subject_key_identifier_extension(ski) + |> result.map(Ok) + } +} + +fn encode_aki_opt( + config: AuthorityKeyIdentifierConfig, + spki: BitArray, +) -> Result(Result(BitArray, Nil), Nil) { + case config { + AkiExclude -> Ok(Error(Nil)) + AkiAuto -> + compute_ski(spki) + |> result.try(encode_authority_key_identifier_extension) + |> result.map(Ok) + AkiExplicit(key_id) -> + encode_authority_key_identifier_extension(key_id) + |> result.map(Ok) + } +} + +fn encode_basic_constraints_extension( + ca: Bool, + path_len: Option(Int), +) -> Result(BitArray, Nil) { + let x509.Oid(oid_components) = oid_basic_constraints + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + let ca_bool = case ca { + True -> der.encode_bool(True) + False -> <<>> + } + + case path_len { + option.Some(n) -> der.encode_small_int(n) + option.None -> Ok(<<>>) + } + |> result.map(fn(path_len_int) { bit_array.concat([ca_bool, path_len_int]) }) + |> result.try(der.encode_sequence) + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { + bit_array.concat([oid_encoded, der.encode_bool(True), value_octet]) + }) + |> result.try(der.encode_sequence) +} + +const key_usages = [ + x509.DigitalSignature, + x509.NonRepudiation, + x509.KeyEncipherment, + x509.DataEncipherment, + x509.KeyAgreement, + x509.KeyCertSign, + x509.CrlSign, + x509.EncipherOnly, + x509.DecipherOnly, +] + +fn encode_key_usage_extension( + usages: List(x509.KeyUsage), +) -> Result(BitArray, Nil) { + let x509.Oid(oid_components) = oid_key_usage + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + let last_set_index = + list.index_fold(key_usages, 0, fn(last_index, usage, index) { + case list.contains(usages, usage) { + True -> index + 1 + False -> last_index + } + }) + + let key_usage_bits = + key_usages + |> list.take(last_set_index) + |> list.fold(<<>>, fn(acc, usage) { + let bit = case list.contains(usages, usage) { + True -> 1 + False -> 0 + } + <> + }) + + key_usage_bits + |> der.encode_bit_string + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { + bit_array.concat([oid_encoded, der.encode_bool(True), value_octet]) + }) + |> result.try(der.encode_sequence) +} + +fn encode_extended_key_usage_extension( + usages: List(x509.ExtendedKeyUsage), +) -> Result(BitArray, Nil) { + let x509.Oid(oid_components) = oid_extended_key_usage + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + usages + |> list.try_map(fn(usage) { + let x509.Oid(components) = case usage { + x509.ServerAuth -> oid_server_auth + x509.ClientAuth -> oid_client_auth + x509.CodeSigning -> oid_code_signing + x509.EmailProtection -> oid_email_protection + x509.OcspSigning -> oid_ocsp_signing + } + der.encode_oid(components) + }) + |> result.map(bit_array.concat) + |> result.try(der.encode_sequence) + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { bit_array.concat([oid_encoded, value_octet]) }) + |> result.try(der.encode_sequence) +} + +fn compute_ski(spki: BitArray) -> Result(BitArray, Nil) { + spki + |> x509_internal.extract_spki_public_key_bytes + |> result.try(crypto.hash(hash.Sha1, _)) +} + +fn encode_subject_key_identifier_extension( + ski: BitArray, +) -> Result(BitArray, Nil) { + let x509.Oid(oid_components) = oid_subject_key_identifier + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + ski + |> der.encode_octet_string + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { bit_array.concat([oid_encoded, value_octet]) }) + |> result.try(der.encode_sequence) +} + +fn encode_authority_key_identifier_extension( + key_identifier: BitArray, +) -> Result(BitArray, Nil) { + let x509.Oid(oid_components) = oid_authority_key_identifier + use oid_encoded <- result.try(der.encode_oid(oid_components)) + + key_identifier + |> der.encode_context_primitive_tag(0, _) + |> result.try(der.encode_sequence) + |> result.try(der.encode_octet_string) + |> result.map(fn(value_octet) { bit_array.concat([oid_encoded, value_octet]) }) + |> result.try(der.encode_sequence) +} + +fn encode_certificate( + tbs: BitArray, + sig_alg: SigAlgInfo, + signature: BitArray, +) -> Result(BitArray, Nil) { + use sig_alg_der <- result.try(x509_internal.encode_algorithm_identifier( + sig_alg, + )) + use sig_bits <- result.try(der.encode_bit_string(signature)) + der.encode_sequence(bit_array.concat([tbs, sig_alg_der, sig_bits])) +} + +fn is_xdh_key(key: x509.PublicKey) -> Bool { + case key { + x509.XdhPublicKey(_) -> True + _ -> False + } +} + +fn xdh_key_oid(key: x509.PublicKey) -> Result(x509.Oid, Nil) { + case key { + x509.XdhPublicKey(xdh_key) -> + case xdh.public_key_curve(xdh_key) { + xdh.X25519 -> Ok(x509.Oid([1, 3, 101, 110])) + xdh.X448 -> Ok(x509.Oid([1, 3, 101, 111])) + } + _ -> Error(Nil) + } +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/csr.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/csr.gleam new file mode 100644 index 0000000..517eae5 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/x509/csr.gleam @@ -0,0 +1,626 @@ +//// X.509 Certificate Signing Request (CSR) generation. +//// +//// Builder for creating PKCS#10 Certificate Signing Requests (CSRs). +//// CSRs are used to request certificates from a Certificate Authority. +//// +//// ## Example +//// +//// ```gleam +//// import gleam/result +//// import kryptos/ec +//// import kryptos/hash +//// import kryptos/x509 +//// import kryptos/x509/csr +//// +//// let #(private_key, _) = ec.generate_key_pair(ec.P256) +//// +//// let subject = x509.name([ +//// x509.cn("example.com"), +//// x509.organization("Acme Inc"), +//// x509.country("US"), +//// ]) +//// +//// let assert Ok(builder) = +//// csr.new() +//// |> csr.with_subject(subject) +//// |> csr.with_dns_name("example.com") +//// |> result.try(csr.with_dns_name(_, "www.example.com")) +//// +//// let assert Ok(my_csr) = csr.sign_with_ecdsa(builder, private_key, hash.Sha256) +//// +//// let pem = csr.to_pem(my_csr) +//// ``` + +import gleam/bit_array +import gleam/bool +import gleam/list +import gleam/result +import kryptos/ec +import kryptos/ecdsa +import kryptos/eddsa +import kryptos/hash.{type HashAlgorithm} +import kryptos/internal/der +import kryptos/internal/utils +import kryptos/internal/x509.{type SigAlgInfo} as x509_internal +import kryptos/rsa +import kryptos/x509 + +const oid_extension_request = x509.Oid([1, 2, 840, 113_549, 1, 9, 14]) + +const pem_begin = "-----BEGIN CERTIFICATE REQUEST-----" + +const pem_end = "-----END CERTIFICATE REQUEST-----" + +const pem_new_begin = "-----BEGIN NEW CERTIFICATE REQUEST-----" + +const pem_new_end = "-----END NEW CERTIFICATE REQUEST-----" + +/// Phantom type marker for CSRs created via the builder. +pub type Built + +/// Phantom type marker for CSRs parsed from PEM/DER. +pub type Parsed + +/// A Certificate Signing Request. +/// +/// The phantom type parameter tracks how the CSR was created: +/// - `Csr(Built)` - created via `sign_with_ecdsa` or `sign_with_rsa` +/// - `Csr(Parsed)` - created via `from_pem` or `from_der` +/// +/// Export functions (`to_pem`, `to_der`) work on any `Csr(a)`. +/// Accessor functions (`subject`, `public_key`, etc.) require `Csr(Parsed)`. +pub opaque type Csr(status) { + BuiltCsr(der: BitArray) + ParsedCsr( + der: BitArray, + version: Int, + subject: x509.Name, + public_key: x509.PublicKey, + signature_algorithm: x509.SignatureAlgorithm, + subject_alt_names: List(x509.SubjectAltName), + extensions: List(#(x509.Oid, Bool, BitArray)), + attributes: List(#(x509.Oid, BitArray)), + ) +} + +/// Error type for CSR parsing failures. +pub type CsrError { + InvalidPem + InvalidStructure + UnsupportedSignatureAlgorithm(x509.Oid) + UnsupportedKeyType(x509.Oid) + SignatureVerificationFailed + UnsupportedVersion(Int) +} + +/// A builder for constructing CSRs. +pub opaque type Builder { + Builder(subject: x509.Name, extensions: x509.Extensions) +} + +/// Creates a new CSR builder with an empty subject and no extensions. +/// +/// Use the `with_*` functions to configure the builder, then call +/// `sign_with_ecdsa` or `sign_with_rsa` to generate the signed CSR. +pub fn new() -> Builder { + Builder( + subject: x509.name([]), + extensions: x509.Extensions(subject_alt_names: []), + ) +} + +/// Sets the distinguished name subject for the CSR. +pub fn with_subject(builder: Builder, subject: x509.Name) -> Builder { + Builder(..builder, subject:) +} + +/// Adds a DNS name to the Subject Alternative Names extension. +/// +/// SANs allow a certificate to be valid for multiple hostnames. Modern +/// browsers require the domain to appear in the SAN extension, not just +/// the Common Name. The name must contain only ASCII characters. +pub fn with_dns_name(builder: Builder, name: String) -> Result(Builder, Nil) { + use <- bool.guard(when: !utils.is_ascii(name), return: Error(Nil)) + let x509.Extensions(sans) = builder.extensions + Ok( + Builder( + ..builder, + extensions: x509.Extensions(subject_alt_names: [ + x509.DnsName(name), + ..sans + ]), + ), + ) +} + +/// Adds an email address to the Subject Alternative Names extension. +/// +/// Used for S/MIME certificates. The email must contain only ASCII characters. +pub fn with_email(builder: Builder, email: String) -> Result(Builder, Nil) { + use <- bool.guard(when: !utils.is_ascii(email), return: Error(Nil)) + let x509.Extensions(sans) = builder.extensions + Ok( + Builder( + ..builder, + extensions: x509.Extensions(subject_alt_names: [x509.Email(email), ..sans]), + ), + ) +} + +/// Adds an IP address to the Subject Alternative Names extension. +/// +/// Accepts IPv4 (e.g., "192.168.1.1") or IPv6 (e.g., "2001:db8::1") addresses. +pub fn with_ip(builder: Builder, ip: String) -> Result(Builder, Nil) { + use parsed <- result.try(utils.parse_ip(ip)) + let x509.Extensions(sans) = builder.extensions + Ok( + Builder( + ..builder, + extensions: x509.Extensions(subject_alt_names: [ + x509.IpAddress(parsed), + ..sans + ]), + ), + ) +} + +/// Signs the CSR with an ECDSA private key. +/// +/// The public key is derived from the private key and included in the CSR. +/// Recommended hash: `Sha256` for P-256, `Sha384` for P-384, `Sha512` for +/// P-521. `Sha1` is supported for legacy compatibility but is +/// cryptographically weak. +pub fn sign_with_ecdsa( + builder: Builder, + key: ec.PrivateKey, + hash: HashAlgorithm, +) -> Result(Csr(Built), Nil) { + use sig_alg <- result.try(x509_internal.ecdsa_sig_alg_info(hash)) + let public_key = ec.public_key_from_private_key(key) + use spki <- result.try(ec.public_key_to_der(public_key)) + use cert_request_info <- result.try(encode_certification_request_info( + builder, + spki, + )) + let signature = ecdsa.sign(key, cert_request_info, hash) + use csr_der <- result.try(encode_csr(cert_request_info, sig_alg, signature)) + Ok(BuiltCsr(csr_der)) +} + +/// Signs the CSR with an RSA private key using PKCS#1 v1.5 padding. +/// +/// The public key is derived from the private key and included in the CSR. +/// Recommended hash: `Sha256` for 2048-bit keys, `Sha384` or `Sha512` for +/// 3072-bit or larger keys. `Sha1` is supported for legacy compatibility +/// but is cryptographically weak. +pub fn sign_with_rsa( + builder: Builder, + key: rsa.PrivateKey, + hash: HashAlgorithm, +) -> Result(Csr(Built), Nil) { + use sig_alg <- result.try(x509_internal.rsa_sig_alg_info(hash)) + let public_key = rsa.public_key_from_private_key(key) + use spki <- result.try(rsa.public_key_to_der(public_key, rsa.Spki)) + use cert_request_info <- result.try(encode_certification_request_info( + builder, + spki, + )) + let signature = rsa.sign(key, cert_request_info, hash, rsa.Pkcs1v15) + use csr_der <- result.try(encode_csr(cert_request_info, sig_alg, signature)) + Ok(BuiltCsr(csr_der)) +} + +/// Signs the CSR with an EdDSA private key (Ed25519 or Ed448). +/// +/// **Note**: Support for EdDSA is limited with browsers and certificate +/// authorities. +pub fn sign_with_eddsa( + builder: Builder, + key: eddsa.PrivateKey, +) -> Result(Csr(Built), Nil) { + let sig_alg = x509_internal.eddsa_sig_alg_info(eddsa.curve(key)) + let public_key = eddsa.public_key_from_private_key(key) + use spki <- result.try(eddsa.public_key_to_der(public_key)) + use cert_request_info <- result.try(encode_certification_request_info( + builder, + spki, + )) + let signature = eddsa.sign(key, cert_request_info) + use csr_der <- result.try(encode_csr(cert_request_info, sig_alg, signature)) + Ok(BuiltCsr(csr_der)) +} + +/// Exports the CSR as DER-encoded bytes. +pub fn to_der(csr: Csr(a)) -> BitArray { + case csr { + BuiltCsr(der) -> der + ParsedCsr(der, ..) -> der + } +} + +/// Exports the CSR as a PEM-encoded string. +/// +/// This is the format typically required when submitting a CSR to a +/// Certificate Authority. +pub fn to_pem(csr: Csr(a)) -> String { + x509_internal.encode_pem(to_der(csr), pem_begin, pem_end) +} + +/// Parse a PEM-encoded CSR and verify its signature. +/// +/// Returns an error if the PEM is invalid, the structure is malformed, +/// or the signature doesn't verify against the embedded public key. +pub fn from_pem(pem: String) -> Result(Csr(Parsed), CsrError) { + use der <- result.try(decode_csr_pem(pem) |> result.replace_error(InvalidPem)) + from_der(der) +} + +/// Parse a DER-encoded CSR and verify its signature. +pub fn from_der(der: BitArray) -> Result(Csr(Parsed), CsrError) { + use parsed <- result.try(from_der_unverified(der)) + use _ <- result.try(verify_signature(parsed)) + Ok(parsed) +} + +/// Parse a PEM-encoded CSR without verifying the signature. +/// +/// Useful for debugging malformed or partially valid CSRs. +/// The parsed fields may not be trustworthy. +pub fn from_pem_unverified(pem: String) -> Result(Csr(Parsed), CsrError) { + use der <- result.try(decode_csr_pem(pem) |> result.replace_error(InvalidPem)) + from_der_unverified(der) +} + +/// Parse a DER-encoded CSR without verifying the signature. +/// +/// Useful for debugging malformed or partially valid CSRs. +/// The parsed fields may not be trustworthy since the signature +/// was not verified. +pub fn from_der_unverified(der: BitArray) -> Result(Csr(Parsed), CsrError) { + use #(csr_content, remaining) <- result.try( + der.parse_sequence(der) |> result.replace_error(InvalidStructure), + ) + use <- bool.guard( + when: bit_array.byte_size(remaining) != 0, + return: Error(InvalidStructure), + ) + + // Parse CertificationRequestInfo - we need to preserve the original bytes + use #(cert_req_info_bytes, after_info) <- result.try( + x509_internal.parse_sequence_with_header(csr_content) + |> result.replace_error(InvalidStructure), + ) + + // Parse the inner content of CertificationRequestInfo + use #(cert_req_info_content, _) <- result.try( + der.parse_sequence(cert_req_info_bytes) + |> result.replace_error(InvalidStructure), + ) + + // Parse version (INTEGER) + use #(version_bytes, after_version) <- result.try( + der.parse_integer(cert_req_info_content) + |> result.replace_error(InvalidStructure), + ) + use version <- result.try(parse_version(version_bytes)) + + // Parse subject (Name) + use #(subject_bytes, after_subject) <- result.try( + der.parse_sequence(after_version) |> result.replace_error(InvalidStructure), + ) + use subject <- result.try( + x509_internal.parse_name(subject_bytes) + |> result.replace_error(InvalidStructure), + ) + + // Parse SubjectPublicKeyInfo + use #(spki_bytes, after_spki) <- result.try( + x509_internal.parse_sequence_with_header(after_subject) + |> result.replace_error(InvalidStructure), + ) + use public_key <- result.try( + x509_internal.parse_public_key(spki_bytes) + |> result.map_error(fn(oid) { + case oid { + x509.Oid([]) -> InvalidStructure + _ -> UnsupportedKeyType(oid) + } + }), + ) + + // Parse attributes [0] (optional) + use #(subject_alt_names, extensions, attributes) <- result.try( + parse_attributes(after_spki) |> result.replace_error(InvalidStructure), + ) + + // Parse signature algorithm + use #(sig_alg_bytes, after_sig_alg) <- result.try( + der.parse_sequence(after_info) |> result.replace_error(InvalidStructure), + ) + use signature_algorithm <- result.try( + x509_internal.parse_signature_algorithm(sig_alg_bytes) + |> result.map_error(UnsupportedSignatureAlgorithm), + ) + + // Parse signature (BIT STRING) - store for verification + use #(_signature, _) <- result.try( + der.parse_bit_string(after_sig_alg) + |> result.replace_error(InvalidStructure), + ) + + Ok(ParsedCsr( + der:, + version:, + subject:, + public_key:, + signature_algorithm:, + subject_alt_names:, + extensions:, + attributes:, + )) +} + +/// Returns the version of a parsed CSR. +/// +/// PKCS#10 v1 CSRs always have version 0. +pub fn version(csr: Csr(Parsed)) -> Int { + let assert ParsedCsr(version:, ..) = csr + version +} + +/// Returns the subject (distinguished name) of a parsed CSR. +pub fn subject(csr: Csr(Parsed)) -> x509.Name { + let assert ParsedCsr(subject:, ..) = csr + subject +} + +/// Returns the public key embedded in a parsed CSR. +pub fn public_key(csr: Csr(Parsed)) -> x509.PublicKey { + let assert ParsedCsr(public_key:, ..) = csr + public_key +} + +/// Returns the signature algorithm used to sign the CSR. +pub fn signature_algorithm(csr: Csr(Parsed)) -> x509.SignatureAlgorithm { + let assert ParsedCsr(signature_algorithm:, ..) = csr + signature_algorithm +} + +/// Returns the Subject Alternative Names from the CSR. +pub fn subject_alt_names(csr: Csr(Parsed)) -> List(x509.SubjectAltName) { + let assert ParsedCsr(subject_alt_names:, ..) = csr + subject_alt_names +} + +/// Returns any extensions beyond SANs as raw (OID, critical, value) tuples. +/// +/// This allows access to extensions that kryptos doesn't have typed +/// representations for. The Bool indicates whether the extension was +/// marked as critical per RFC 5280. +pub fn extensions(csr: Csr(Parsed)) -> List(#(x509.Oid, Bool, BitArray)) { + let assert ParsedCsr(extensions:, ..) = csr + extensions +} + +/// Returns any non-extension attributes as raw (OID, value) pairs. +/// +/// Most CSRs only have the extensionRequest attribute, so this is +/// typically empty. +pub fn attributes(csr: Csr(Parsed)) -> List(#(x509.Oid, BitArray)) { + let assert ParsedCsr(attributes:, ..) = csr + attributes +} + +fn decode_csr_pem(pem: String) -> Result(BitArray, Nil) { + case x509_internal.decode_pem(pem, pem_begin, pem_end) { + Ok(der) -> Ok(der) + Error(_) -> x509_internal.decode_pem(pem, pem_new_begin, pem_new_end) + } +} + +fn parse_version(bytes: BitArray) -> Result(Int, CsrError) { + case bytes { + <<0>> -> Ok(0) + <> -> Error(UnsupportedVersion(v)) + _ -> Error(InvalidStructure) + } +} + +fn parse_attributes( + bytes: BitArray, +) -> Result( + #( + List(x509.SubjectAltName), + List(#(x509.Oid, Bool, BitArray)), + List(#(x509.Oid, BitArray)), + ), + Nil, +) { + case bytes { + <<0xa0, _:bits>> -> + case der.parse_context_tag(bytes, 0) { + Ok(#(attrs_content, _)) -> + parse_attributes_content(attrs_content, [], [], []) + Error(_) -> Error(Nil) + } + _ -> Ok(#([], [], [])) + } +} + +fn parse_attributes_content( + bytes: BitArray, + sans: List(x509.SubjectAltName), + exts: List(#(x509.Oid, Bool, BitArray)), + attrs: List(#(x509.Oid, BitArray)), +) -> Result( + #( + List(x509.SubjectAltName), + List(#(x509.Oid, Bool, BitArray)), + List(#(x509.Oid, BitArray)), + ), + Nil, +) { + case bytes { + <<>> -> Ok(#(sans, list.reverse(exts), list.reverse(attrs))) + _ -> { + use #(attr_bytes, rest) <- result.try(der.parse_sequence(bytes)) + use #(oid, value) <- result.try(parse_single_attribute(attr_bytes)) + case oid { + x509.Oid([1, 2, 840, 113_549, 1, 9, 14]) -> { + use #(new_sans, new_exts) <- result.try(parse_extension_request(value)) + parse_attributes_content( + rest, + list.append(sans, new_sans), + list.append(exts, new_exts), + attrs, + ) + } + _ -> + parse_attributes_content(rest, sans, exts, [#(oid, value), ..attrs]) + } + } + } +} + +fn parse_single_attribute(bytes: BitArray) -> Result(#(x509.Oid, BitArray), Nil) { + use #(oid_components, after_oid) <- result.try(der.parse_oid(bytes)) + use #(value, remaining) <- result.try(der.parse_set(after_oid)) + use <- bool.guard(when: remaining != <<>>, return: Error(Nil)) + Ok(#(x509.Oid(oid_components), value)) +} + +fn parse_extension_request( + bytes: BitArray, +) -> Result( + #(List(x509.SubjectAltName), List(#(x509.Oid, Bool, BitArray))), + Nil, +) { + use <- bool.guard( + when: bit_array.byte_size(bytes) == 0, + return: Ok(#([], [])), + ) + use #(exts_content, _) <- result.try(der.parse_sequence(bytes)) + parse_extensions(exts_content, [], []) +} + +fn parse_extensions( + bytes: BitArray, + sans: List(x509.SubjectAltName), + exts: List(#(x509.Oid, Bool, BitArray)), +) -> Result( + #(List(x509.SubjectAltName), List(#(x509.Oid, Bool, BitArray))), + Nil, +) { + case bytes { + <<>> -> Ok(#(sans, list.reverse(exts))) + _ -> { + use #(ext_bytes, rest) <- result.try(der.parse_sequence(bytes)) + use #(oid, is_critical, value) <- result.try( + x509_internal.parse_single_extension(ext_bytes), + ) + case oid { + x509.Oid([2, 5, 29, 17]) -> { + use new_sans <- result.try(x509_internal.parse_san_extension( + value, + False, + )) + parse_extensions(rest, list.append(sans, new_sans), exts) + } + _ -> parse_extensions(rest, sans, [#(oid, is_critical, value), ..exts]) + } + } + } +} + +fn verify_signature(csr: Csr(Parsed)) -> Result(Nil, CsrError) { + let assert ParsedCsr(der:, public_key:, signature_algorithm:, ..) = csr + + // Extract the CertificationRequestInfo and signature from the DER + use #(csr_content, _) <- result.try( + der.parse_sequence(der) |> result.replace_error(InvalidStructure), + ) + + use #(cert_req_info_bytes, after_info) <- result.try( + x509_internal.parse_sequence_with_header(csr_content) + |> result.replace_error(InvalidStructure), + ) + + // Skip signature algorithm + use #(_, after_sig_alg) <- result.try( + der.parse_sequence(after_info) |> result.replace_error(InvalidStructure), + ) + + // Get signature + use #(signature, _) <- result.try( + der.parse_bit_string(after_sig_alg) + |> result.replace_error(InvalidStructure), + ) + + // Verify based on algorithm and key type + let verified = + x509_internal.verify_signature( + public_key, + cert_req_info_bytes, + signature, + signature_algorithm, + ) + + case verified { + True -> Ok(Nil) + False -> Error(SignatureVerificationFailed) + } +} + +fn encode_csr( + cert_request_info: BitArray, + sig_alg: SigAlgInfo, + signature: BitArray, +) -> Result(BitArray, Nil) { + use sig_alg_der <- result.try(x509_internal.encode_algorithm_identifier( + sig_alg, + )) + use sig_bits <- result.try(der.encode_bit_string(signature)) + der.encode_sequence( + bit_array.concat([cert_request_info, sig_alg_der, sig_bits]), + ) +} + +fn encode_certification_request_info( + builder: Builder, + spki: BitArray, +) -> Result(BitArray, Nil) { + use version <- result.try(der.encode_integer(<<0>>)) + use subject <- result.try(x509_internal.encode_name(builder.subject)) + use attributes <- result.try(encode_attributes(builder.extensions)) + der.encode_sequence(bit_array.concat([version, subject, spki, attributes])) +} + +fn encode_attributes(extensions: x509.Extensions) -> Result(BitArray, Nil) { + case list.is_empty(extensions.subject_alt_names) { + True -> der.encode_context_tag(0, <<>>) + False -> + extensions + |> encode_extension_request + |> result.try(der.encode_context_tag(0, _)) + } +} + +fn encode_extension_request( + extensions: x509.Extensions, +) -> Result(BitArray, Nil) { + let x509.Oid(ext_req_components) = oid_extension_request + use oid_encoded <- result.try(der.encode_oid(ext_req_components)) + + extensions + |> encode_extensions + |> result.try(der.encode_set) + |> result.map(fn(set_encoded) { bit_array.concat([oid_encoded, set_encoded]) }) + |> result.try(der.encode_sequence) +} + +fn encode_extensions(extensions: x509.Extensions) -> Result(BitArray, Nil) { + let x509.Extensions(sans) = extensions + + x509_internal.encode_san_extension(sans, False) + |> result.try(der.encode_sequence) +} diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos/xdh.gleam b/examples/http_interactions/build/packages/kryptos/src/kryptos/xdh.gleam new file mode 100644 index 0000000..6669757 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos/xdh.gleam @@ -0,0 +1,218 @@ +//// X25519 and X448 (XDH) key agreement. +//// +//// XDH provides Diffie-Hellman key agreement using Montgomery curves X25519 +//// and X448. These curves are designed specifically for key agreement and +//// offer excellent performance with strong security properties. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/xdh +//// +//// // Alice generates a key pair +//// let #(alice_private, alice_public) = xdh.generate_key_pair(xdh.X25519) +//// +//// // Bob generates a key pair +//// let #(bob_private, bob_public) = xdh.generate_key_pair(xdh.X25519) +//// +//// // Both compute the same shared secret +//// let assert Ok(alice_shared) = xdh.compute_shared_secret(alice_private, bob_public) +//// let assert Ok(bob_shared) = xdh.compute_shared_secret(bob_private, alice_public) +//// // alice_shared == bob_shared +//// ``` + +import gleam/result +import gleam/string + +/// An XDH private key. +pub type PrivateKey + +/// An XDH public key. +pub type PublicKey + +/// Supported curves for XDH key agreement. +pub type Curve { + /// X25519 curve (Curve25519). 32-byte keys and shared secret. + X25519 + /// X448 curve (Curve448). 56-byte keys and shared secret. + X448 +} + +/// Returns the key size in bytes for the given curve. +pub fn key_size(curve: Curve) -> Int { + case curve { + X25519 -> 32 + X448 -> 56 + } +} + +/// Generates a new XDH key pair. +@external(erlang, "kryptos_ffi", "xdh_generate_key_pair") +@external(javascript, "../kryptos_ffi.mjs", "xdhGenerateKeyPair") +pub fn generate_key_pair(curve: Curve) -> #(PrivateKey, PublicKey) + +/// Computes a shared secret using XDH key agreement. +/// +/// Both parties compute the same shared secret by combining their private key +/// with the other party's public key. +/// +/// Returns `Error(Nil)` if the keys use different curves, the result is an +/// all-zero shared secret (low-order point attack), or another error occurs. +/// +/// The raw shared secret should be passed through a KDF (like HKDF) before +/// use as a symmetric key. +pub fn compute_shared_secret( + private_key: PrivateKey, + peer_public_key: PublicKey, +) -> Result(BitArray, Nil) { + use shared <- result.try(do_compute_shared_secret( + private_key, + peer_public_key, + )) + case is_all_zeros(shared) { + True -> Error(Nil) + False -> Ok(shared) + } +} + +fn is_all_zeros(bytes: BitArray) -> Bool { + case bytes { + <<>> -> True + <<0, rest:bytes>> -> is_all_zeros(rest) + _ -> False + } +} + +@external(erlang, "kryptos_ffi", "xdh_compute_shared_secret") +@external(javascript, "../kryptos_ffi.mjs", "xdhComputeSharedSecret") +fn do_compute_shared_secret( + private_key: PrivateKey, + peer_public_key: PublicKey, +) -> Result(BitArray, Nil) + +/// Imports a private key from raw bytes. +/// +/// The bytes should be the raw private key scalar: +/// - X25519: 32 bytes +/// - X448: 56 bytes +/// +/// Returns the private key and its corresponding public key, or `Error(Nil)` +/// if the bytes are invalid. +@external(erlang, "kryptos_ffi", "xdh_private_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "xdhPrivateKeyFromBytes") +pub fn from_bytes( + curve: Curve, + private_bytes: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports a private key to raw bytes. +/// +/// Returns the raw private key scalar: +/// - X25519: 32 bytes +/// - X448: 56 bytes +@external(erlang, "kryptos_ffi", "xdh_private_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "xdhPrivateKeyToBytes") +pub fn to_bytes(key: PrivateKey) -> BitArray + +/// Imports a public key from raw bytes. +/// +/// The bytes should be the raw public key point: +/// - X25519: 32 bytes +/// - X448: 56 bytes +/// +/// Returns the public key or `Error(Nil)` if the bytes are invalid. +@external(erlang, "kryptos_ffi", "xdh_public_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "xdhPublicKeyFromBytes") +pub fn public_key_from_bytes( + curve: Curve, + public_bytes: BitArray, +) -> Result(PublicKey, Nil) + +/// Exports a public key to raw bytes. +/// +/// Returns the raw public key point: +/// - X25519: 32 bytes +/// - X448: 56 bytes +@external(erlang, "kryptos_ffi", "xdh_public_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "xdhPublicKeyToBytes") +pub fn public_key_to_bytes(key: PublicKey) -> BitArray + +/// Imports an XDH private key from PEM-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "xdh_import_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "xdhImportPrivateKeyPem") +pub fn from_pem(pem: String) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Imports an XDH private key from DER-encoded data. +/// +/// The key must be in PKCS#8 format. +@external(erlang, "kryptos_ffi", "xdh_import_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "xdhImportPrivateKeyDer") +pub fn from_der(der: BitArray) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports an XDH private key to PEM format. +/// +/// The key is exported in PKCS#8 format. +pub fn to_pem(key: PrivateKey) -> Result(String, Nil) { + do_to_pem(key) |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "xdh_export_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "xdhExportPrivateKeyPem") +fn do_to_pem(key: PrivateKey) -> Result(String, Nil) + +/// Exports an XDH private key to DER format. +/// +/// The key is exported in PKCS#8 format. +@external(erlang, "kryptos_ffi", "xdh_export_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "xdhExportPrivateKeyDer") +pub fn to_der(key: PrivateKey) -> Result(BitArray, Nil) + +/// Imports an XDH public key from PEM-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "xdh_import_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "xdhImportPublicKeyPem") +pub fn public_key_from_pem(pem: String) -> Result(PublicKey, Nil) + +/// Imports an XDH public key from DER-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "xdh_import_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "xdhImportPublicKeyDer") +pub fn public_key_from_der(der: BitArray) -> Result(PublicKey, Nil) + +/// Exports an XDH public key to PEM format. +/// +/// The key is exported in SPKI format. +pub fn public_key_to_pem(key: PublicKey) -> Result(String, Nil) { + do_public_key_to_pem(key) + |> result.map(fn(pem) { string.trim_end(pem) <> "\n" }) +} + +@external(erlang, "kryptos_ffi", "xdh_export_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "xdhExportPublicKeyPem") +fn do_public_key_to_pem(key: PublicKey) -> Result(String, Nil) + +/// Exports an XDH public key to DER format. +/// +/// The key is exported in SPKI format. +@external(erlang, "kryptos_ffi", "xdh_export_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "xdhExportPublicKeyDer") +pub fn public_key_to_der(key: PublicKey) -> Result(BitArray, Nil) + +/// Derives the public key from an XDH private key. +@external(erlang, "kryptos_ffi", "xdh_public_key_from_private") +@external(javascript, "../kryptos_ffi.mjs", "xdhPublicKeyFromPrivate") +pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey + +/// Returns the curve for an XDH private key. +@external(erlang, "kryptos_ffi", "xdh_private_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "xdhPrivateKeyCurve") +pub fn curve(key: PrivateKey) -> Curve + +/// Returns the curve for an XDH public key. +@external(erlang, "kryptos_ffi", "xdh_public_key_curve") +@external(javascript, "../kryptos_ffi.mjs", "xdhPublicKeyCurve") +pub fn public_key_curve(key: PublicKey) -> Curve diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@aead.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@aead.erl new file mode 100644 index 0000000..ee8631e --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@aead.erl @@ -0,0 +1,417 @@ +-module(kryptos@aead). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/aead.gleam"). +-export([gcm/1, gcm_with_nonce_size/2, ccm/1, ccm_with_sizes/3, chacha20_poly1305/1, xchacha20_poly1305/1, nonce_size/1, tag_size/1, seal_with_aad/4, seal/3, open_with_aad/5, open/4, cipher_name/1, cipher_key/1, is_ccm/1, is_gcm/1, is_chacha20_poly1305/1]). +-export_type([aead_context/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Authenticated Encryption with Associated Data (AEAD).\n" + "\n" + " AEAD provides both confidentiality and integrity for data, with optional\n" + " authenticated additional data (AAD) that is integrity-protected but not\n" + " encrypted.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/aead\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" + " let ctx = aead.gcm(cipher)\n" + " let nonce = crypto.random_bytes(aead.nonce_size(ctx))\n" + " let assert Ok(#(ciphertext, tag)) = aead.seal(ctx, nonce:, plaintext: <<\"secret\":utf8>>)\n" + " ```\n" +). + +-opaque aead_context() :: {gcm, kryptos@block:block_cipher(), integer()} | + {ccm, kryptos@block:block_cipher(), integer(), integer()} | + {cha_cha20_poly1305, bitstring()} | + {x_cha_cha20_poly1305, bitstring()}. + +-file("src/kryptos/aead.gleam", 54). +?DOC( + " Creates an AES-GCM context with the given block cipher.\n" + "\n" + " Uses standard parameters: 16-byte (128-bit) authentication tag and\n" + " 12-byte (96-bit) nonce.\n" + "\n" + " **Note:** This library only supports the full 16-byte authentication tag.\n" + " Truncated tags (as permitted by NIST SP 800-38D) are not supported due to\n" + " their reduced security guarantees.\n" +). +-spec gcm(kryptos@block:block_cipher()) -> aead_context(). +gcm(Cipher) -> + {gcm, Cipher, 12}. + +-file("src/kryptos/aead.gleam", 62). +?DOC( + " Creates an AES-GCM context with a custom nonce size.\n" + "\n" + " GCM supports variable nonce sizes, though 12 bytes is strongly recommended.\n" + " This function is primarily useful for compatibility testing with test vectors.\n" +). +-spec gcm_with_nonce_size(kryptos@block:block_cipher(), integer()) -> {ok, + aead_context()} | + {error, nil}. +gcm_with_nonce_size(Cipher, Nonce_size) -> + case (Nonce_size >= 1) andalso (Nonce_size =< 64) of + true -> + {ok, {gcm, Cipher, Nonce_size}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 76). +?DOC( + " Creates an AES-CCM context with the given block cipher.\n" + "\n" + " Uses standard parameters: 16-byte (128-bit) authentication tag and\n" + " 13-byte (104-bit) nonce, which allows messages up to 64KB.\n" +). +-spec ccm(kryptos@block:block_cipher()) -> aead_context(). +ccm(Cipher) -> + {ccm, Cipher, 13, 16}. + +-file("src/kryptos/aead.gleam", 87). +?DOC( + " Creates an AES-CCM context with custom nonce and tag sizes.\n" + "\n" + " CCM allows flexible nonce and tag sizes per RFC 3610:\n" + " - Nonce size affects maximum message length (larger nonce = smaller max message)\n" + " - Tag size affects authentication strength (larger tag = stronger)\n" + "\n" + " Nonce must be 7-13 bytes. Tag must be 4, 6, 8, 10, 12, 14, or 16 bytes.\n" +). +-spec ccm_with_sizes(kryptos@block:block_cipher(), integer(), integer()) -> {ok, + aead_context()} | + {error, nil}. +ccm_with_sizes(Cipher, Nonce_size, Tag_size) -> + Valid_nonce = (Nonce_size >= 7) andalso (Nonce_size =< 13), + Valid_tag = gleam@list:contains([4, 6, 8, 10, 12, 14, 16], Tag_size), + case Valid_nonce andalso Valid_tag of + true -> + {ok, {ccm, Cipher, Nonce_size, Tag_size}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 104). +?DOC( + " Creates a ChaCha20-Poly1305 AEAD context with the given key.\n" + "\n" + " Uses standard parameters per RFC 8439: 12-byte (96-bit) nonce and\n" + " 16-byte (128-bit) authentication tag. The key must be exactly 32 bytes.\n" +). +-spec chacha20_poly1305(bitstring()) -> {ok, aead_context()} | {error, nil}. +chacha20_poly1305(Key) -> + case erlang:byte_size(Key) =:= 32 of + true -> + {ok, {cha_cha20_poly1305, Key}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 116). +?DOC( + " Creates an XChaCha20-Poly1305 AEAD context with the given key.\n" + "\n" + " Uses extended parameters: 24-byte (192-bit) nonce and 16-byte (128-bit)\n" + " authentication tag. The extended nonce provides better collision resistance\n" + " when generating random nonces. The key must be exactly 32 bytes.\n" +). +-spec xchacha20_poly1305(bitstring()) -> {ok, aead_context()} | {error, nil}. +xchacha20_poly1305(Key) -> + case erlang:byte_size(Key) =:= 32 of + true -> + {ok, {x_cha_cha20_poly1305, Key}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 124). +?DOC(" Returns the required nonce size in bytes for an AEAD context.\n"). +-spec nonce_size(aead_context()) -> integer(). +nonce_size(Ctx) -> + case Ctx of + {gcm, _, Nonce_size} -> + Nonce_size; + + {ccm, _, Nonce_size@1, _} -> + Nonce_size@1; + + {cha_cha20_poly1305, _} -> + 12; + + {x_cha_cha20_poly1305, _} -> + 24 + end. + +-file("src/kryptos/aead.gleam", 134). +?DOC(" Returns the authentication tag size in bytes for an AEAD context.\n"). +-spec tag_size(aead_context()) -> integer(). +tag_size(Ctx) -> + case Ctx of + {gcm, _, _} -> + 16; + + {ccm, _, _, Tag_size} -> + Tag_size; + + {cha_cha20_poly1305, _} -> + 16; + + {x_cha_cha20_poly1305, _} -> + 16 + end. + +-file("src/kryptos/aead.gleam", 269). +?DOC(" Derives the subkey and ChaCha20 nonce for XChaCha20-Poly1305.\n"). +-spec xchacha20_derive(bitstring(), bitstring()) -> {ok, + {bitstring(), bitstring()}} | + {error, nil}. +xchacha20_derive(Key, Nonce) -> + case Nonce of + <> -> + Subkey = kryptos@internal@hchacha20:subkey(Key, Hchacha_input), + Chacha_nonce = <<0:32, Nonce_suffix/bitstring>>, + {ok, {Subkey, Chacha_nonce}}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 159). +?DOC( + " Encrypts and authenticates plaintext with additional authenticated data.\n" + "\n" + " The AAD is authenticated but not encrypted. It can be used for headers,\n" + " metadata, or context that should be tamper-proof but remain readable.\n" +). +-spec seal_with_aad(aead_context(), bitstring(), bitstring(), bitstring()) -> {ok, + {bitstring(), bitstring()}} | + {error, nil}. +seal_with_aad(Ctx, Nonce, Plaintext, Aad) -> + Nonce_len = erlang:byte_size(Nonce), + case (Nonce_len > 0) andalso (Nonce_len =:= nonce_size(Ctx)) of + true -> + case Ctx of + {x_cha_cha20_poly1305, Key} -> + gleam@result:'try'( + xchacha20_derive(Key, Nonce), + fun(_use0) -> + {Subkey, Chacha_nonce} = _use0, + kryptos_ffi:aead_seal( + {cha_cha20_poly1305, Subkey}, + Chacha_nonce, + Plaintext, + Aad + ) + end + ); + + {gcm, _, _} -> + kryptos_ffi:aead_seal(Ctx, Nonce, Plaintext, Aad); + + {ccm, _, _, _} -> + kryptos_ffi:aead_seal(Ctx, Nonce, Plaintext, Aad); + + {cha_cha20_poly1305, _} -> + kryptos_ffi:aead_seal(Ctx, Nonce, Plaintext, Aad) + end; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 147). +?DOC( + " Encrypts and authenticates plaintext using AEAD.\n" + "\n" + " The nonce must be exactly `nonce_size` bytes. Never reuse a nonce with\n" + " the same key.\n" +). +-spec seal(aead_context(), bitstring(), bitstring()) -> {ok, + {bitstring(), bitstring()}} | + {error, nil}. +seal(Ctx, Nonce, Plaintext) -> + seal_with_aad(Ctx, Nonce, Plaintext, <<>>). + +-file("src/kryptos/aead.gleam", 233). +?DOC( + " Decrypts and verifies AEAD-encrypted data with additional authenticated data.\n" + "\n" + " The AAD must match exactly what was provided during encryption.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/aead\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" + " let ctx = aead.gcm(cipher)\n" + " let nonce = crypto.random_bytes(aead.nonce_size(ctx))\n" + " let aad = <<\"header\":utf8>>\n" + " let assert Ok(#(ciphertext, tag)) =\n" + " aead.seal_with_aad(ctx, nonce:, plaintext: <<\"secret\":utf8>>, additional_data: aad)\n" + " let assert Ok(plaintext) =\n" + " aead.open_with_aad(ctx, nonce:, tag:, ciphertext:, additional_data: aad)\n" + " ```\n" +). +-spec open_with_aad( + aead_context(), + bitstring(), + bitstring(), + bitstring(), + bitstring() +) -> {ok, bitstring()} | {error, nil}. +open_with_aad(Ctx, Nonce, Tag, Ciphertext, Aad) -> + Nonce_len = erlang:byte_size(Nonce), + Tag_len = erlang:byte_size(Tag), + case ((Nonce_len > 0) andalso (Nonce_len =:= nonce_size(Ctx))) andalso (Tag_len + =:= tag_size(Ctx)) of + true -> + case Ctx of + {x_cha_cha20_poly1305, Key} -> + gleam@result:'try'( + xchacha20_derive(Key, Nonce), + fun(_use0) -> + {Subkey, Chacha_nonce} = _use0, + kryptos_ffi:aead_open( + {cha_cha20_poly1305, Subkey}, + Chacha_nonce, + Tag, + Ciphertext, + Aad + ) + end + ); + + {gcm, _, _} -> + kryptos_ffi:aead_open(Ctx, Nonce, Tag, Ciphertext, Aad); + + {ccm, _, _, _} -> + kryptos_ffi:aead_open(Ctx, Nonce, Tag, Ciphertext, Aad); + + {cha_cha20_poly1305, _} -> + kryptos_ffi:aead_open(Ctx, Nonce, Tag, Ciphertext, Aad) + end; + + false -> + {error, nil} + end. + +-file("src/kryptos/aead.gleam", 204). +?DOC( + " Decrypts and verifies AEAD-encrypted data.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/aead\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" + " let ctx = aead.gcm(cipher)\n" + " let nonce = crypto.random_bytes(aead.nonce_size(ctx))\n" + " let assert Ok(#(ciphertext, tag)) = aead.seal(ctx, nonce:, plaintext: <<\"secret\":utf8>>)\n" + " let assert Ok(plaintext) = aead.open(ctx, nonce:, tag:, ciphertext:)\n" + " ```\n" +). +-spec open(aead_context(), bitstring(), bitstring(), bitstring()) -> {ok, + bitstring()} | + {error, nil}. +open(Ctx, Nonce, Tag, Ciphertext) -> + open_with_aad(Ctx, Nonce, Tag, Ciphertext, <<>>). + +-file("src/kryptos/aead.gleam", 287). +?DOC(false). +-spec cipher_name(aead_context()) -> binary(). +cipher_name(Ctx) -> + case Ctx of + {gcm, Cipher, _} -> + <<<<"aes-"/utf8, + (erlang:integer_to_binary(kryptos@block:key_size(Cipher)))/binary>>/binary, + "-gcm"/utf8>>; + + {ccm, Cipher@1, _, _} -> + <<<<"aes-"/utf8, + (erlang:integer_to_binary(kryptos@block:key_size(Cipher@1)))/binary>>/binary, + "-ccm"/utf8>>; + + {cha_cha20_poly1305, _} -> + <<"chacha20-poly1305"/utf8>>; + + {x_cha_cha20_poly1305, _} -> + <<"chacha20-poly1305"/utf8>> + end. + +-file("src/kryptos/aead.gleam", 299). +?DOC(false). +-spec cipher_key(aead_context()) -> bitstring(). +cipher_key(Ctx) -> + case Ctx of + {gcm, Cipher, _} -> + kryptos@block:aes_key(Cipher); + + {ccm, Cipher, _, _} -> + kryptos@block:aes_key(Cipher); + + {cha_cha20_poly1305, Key} -> + Key; + + {x_cha_cha20_poly1305, Key} -> + Key + end. + +-file("src/kryptos/aead.gleam", 307). +?DOC(false). +-spec is_ccm(aead_context()) -> boolean(). +is_ccm(Ctx) -> + case Ctx of + {ccm, _, _, _} -> + true; + + _ -> + false + end. + +-file("src/kryptos/aead.gleam", 315). +?DOC(false). +-spec is_gcm(aead_context()) -> boolean(). +is_gcm(Ctx) -> + case Ctx of + {gcm, _, _} -> + true; + + _ -> + false + end. + +-file("src/kryptos/aead.gleam", 323). +?DOC(false). +-spec is_chacha20_poly1305(aead_context()) -> boolean(). +is_chacha20_poly1305(Ctx) -> + case Ctx of + {cha_cha20_poly1305, _} -> + true; + + _ -> + false + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@block.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@block.erl new file mode 100644 index 0000000..022a66c --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@block.erl @@ -0,0 +1,568 @@ +-module(kryptos@block). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/block.gleam"). +-export([key_size/1, block_size/1, aes_128/1, aes_192/1, aes_256/1, ecb/1, cbc/2, ctr/2, encrypt/2, decrypt/2, cipher_name/1, cipher_key/1, cipher_iv/1, aes_key/1, is_ctr/1, wrap/2, unwrap/2]). +-export_type([block_cipher/0, cipher_context/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Block cipher implementations and modes of operation.\n" + "\n" + " AES block ciphers and modes of operation (ECB, CBC, CTR).\n" + "\n" + " **IMPORTANT SECURITY WARNING:**\n" + " ECB, CBC, and CTR modes do NOT provide authentication. An attacker can modify\n" + " ciphertext without detection. For most applications, you should use\n" + " authenticated encryption modes like AES-GCM or ChaCha20-Poly1305\n" + " from the `kryptos/aead` module instead.\n" + "\n" + " Use these modes only when:\n" + " - Interoperating with legacy systems that require them\n" + " - Implementing higher-level protocols that provide their own authentication\n" + " - You fully understand the security implications\n" + "\n" + " ## Modes Overview\n" + "\n" + " - ECB (Electronic Codebook): Encrypts each block independently.\n" + " INSECURE for most uses - reveals patterns in data. Only use for\n" + " single-block encryption or specific legacy requirements.\n" + " - CBC (Cipher Block Chaining): Each block XORed with previous ciphertext.\n" + " Requires random IV per encryption. Uses PKCS7 padding automatically.\n" + " - CTR (Counter): Converts block cipher to stream cipher.\n" + " Nonce reuse is catastrophic - NEVER reuse a nonce with the same key.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " // CBC encryption with random IV\n" + " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" + " let assert Ok(ctx) = block.cbc(cipher, iv: crypto.random_bytes(16))\n" + " let assert Ok(ciphertext) = block.encrypt(ctx, <<\"secret\":utf8>>)\n" + " let assert Ok(decrypted) = block.decrypt(ctx, ciphertext)\n" + " // decrypted == <<\"secret\":utf8>>\n" + " ```\n" +). + +-opaque block_cipher() :: {aes, integer(), bitstring()}. + +-opaque cipher_context() :: {ecb, block_cipher()} | + {cbc, block_cipher(), bitstring()} | + {ctr, block_cipher(), bitstring()}. + +-file("src/kryptos/block.gleam", 74). +?DOC(" Returns the key size in bits for a block cipher.\n"). +-spec key_size(block_cipher()) -> integer(). +key_size(Cipher) -> + case Cipher of + {aes, Key_size, _} -> + Key_size + end. + +-file("src/kryptos/block.gleam", 81). +?DOC(" Returns the block size in bytes for a block cipher.\n"). +-spec block_size(block_cipher()) -> integer(). +block_size(Cipher) -> + case Cipher of + {aes, _, _} -> + 16 + end. + +-file("src/kryptos/block.gleam", 90). +?DOC( + " Creates a new AES-128 block cipher with the given key.\n" + "\n" + " The key must be exactly 16 bytes.\n" +). +-spec aes_128(bitstring()) -> {ok, block_cipher()} | {error, nil}. +aes_128(Key) -> + case erlang:byte_size(Key) =:= 16 of + true -> + {ok, {aes, 128, Key}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 100). +?DOC( + " Creates a new AES-192 block cipher with the given key.\n" + "\n" + " The key must be exactly 24 bytes.\n" +). +-spec aes_192(bitstring()) -> {ok, block_cipher()} | {error, nil}. +aes_192(Key) -> + case erlang:byte_size(Key) =:= 24 of + true -> + {ok, {aes, 192, Key}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 110). +?DOC( + " Creates a new AES-256 block cipher with the given key.\n" + "\n" + " The key must be exactly 32 bytes.\n" +). +-spec aes_256(bitstring()) -> {ok, block_cipher()} | {error, nil}. +aes_256(Key) -> + case erlang:byte_size(Key) =:= 32 of + true -> + {ok, {aes, 256, Key}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 122). +?DOC( + " Creates an ECB mode context for the given cipher.\n" + "\n" + " **SECURITY WARNING:** ECB mode is insecure for most use cases.\n" + " Identical plaintext blocks produce identical ciphertext blocks,\n" + " revealing patterns in the data.\n" +). +-spec ecb(block_cipher()) -> cipher_context(). +ecb(Cipher) -> + {ecb, Cipher}. + +-file("src/kryptos/block.gleam", 129). +?DOC( + " Creates a CBC mode context with the given cipher and IV.\n" + "\n" + " The IV must be exactly 16 bytes, random, and unique per encryption.\n" +). +-spec cbc(block_cipher(), bitstring()) -> {ok, cipher_context()} | {error, nil}. +cbc(Cipher, Iv) -> + case erlang:byte_size(Iv) =:= 16 of + true -> + {ok, {cbc, Cipher, Iv}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 154). +?DOC( + " Creates a CTR mode context with the given cipher and nonce.\n" + "\n" + " **SECURITY WARNING:** Nonce reuse is catastrophic in CTR mode.\n" + " NEVER reuse a nonce with the same key.\n" + "\n" + " The nonce must be exactly 16 bytes.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" + " let assert Ok(ctx) = block.ctr(cipher, nonce: crypto.random_bytes(16))\n" + " let assert Ok(ciphertext) = block.encrypt(ctx, <<\"secret\":utf8>>)\n" + " let assert Ok(plaintext) = block.decrypt(ctx, ciphertext)\n" + " ```\n" +). +-spec ctr(block_cipher(), bitstring()) -> {ok, cipher_context()} | {error, nil}. +ctr(Cipher, Nonce) -> + case erlang:byte_size(Nonce) =:= 16 of + true -> + {ok, {ctr, Cipher, Nonce}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 177). +-spec validate_iv(cipher_context()) -> boolean(). +validate_iv(Ctx) -> + case Ctx of + {ecb, _} -> + true; + + {cbc, _, Iv} -> + erlang:byte_size(Iv) =:= 16; + + {ctr, _, Nonce} -> + erlang:byte_size(Nonce) =:= 16 + end. + +-file("src/kryptos/block.gleam", 170). +?DOC( + " Encrypts plaintext using the cipher mode.\n" + "\n" + " ## Notes\n" + " - ECB: No IV required\n" + " - CBC: Automatically applies PKCS7 padding; ciphertext may be larger than plaintext\n" + " - CTR: No padding needed; ciphertext is same size as plaintext\n" +). +-spec encrypt(cipher_context(), bitstring()) -> {ok, bitstring()} | {error, nil}. +encrypt(Ctx, Plaintext) -> + case validate_iv(Ctx) of + true -> + kryptos_ffi:block_cipher_encrypt(Ctx, Plaintext); + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 195). +?DOC( + " Decrypts ciphertext using the cipher mode.\n" + "\n" + " ## Notes\n" + " - ECB: No IV required\n" + " - CBC: Automatically removes PKCS7 padding; returns error if padding is invalid\n" + " - CTR: No padding; ciphertext size equals plaintext size\n" +). +-spec decrypt(cipher_context(), bitstring()) -> {ok, bitstring()} | {error, nil}. +decrypt(Ctx, Ciphertext) -> + case validate_iv(Ctx) of + true -> + kryptos_ffi:block_cipher_decrypt(Ctx, Ciphertext); + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 210). +?DOC(false). +-spec cipher_name(cipher_context()) -> binary(). +cipher_name(Ctx) -> + case Ctx of + {ecb, {aes, Key_size, _}} -> + <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size))/binary>>/binary, + "-ecb"/utf8>>; + + {cbc, {aes, Key_size@1, _}, _} -> + <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size@1))/binary>>/binary, + "-cbc"/utf8>>; + + {ctr, {aes, Key_size@2, _}, _} -> + <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size@2))/binary>>/binary, + "-ctr"/utf8>> + end. + +-file("src/kryptos/block.gleam", 222). +?DOC(false). +-spec cipher_key(cipher_context()) -> bitstring(). +cipher_key(Ctx) -> + case Ctx of + {ecb, {aes, _, Key}} -> + Key; + + {cbc, {aes, _, Key}, _} -> + Key; + + {ctr, {aes, _, Key}, _} -> + Key + end. + +-file("src/kryptos/block.gleam", 231). +?DOC(false). +-spec cipher_iv(cipher_context()) -> bitstring(). +cipher_iv(Ctx) -> + case Ctx of + {ecb, _} -> + <<>>; + + {cbc, _, Iv} -> + Iv; + + {ctr, _, Nonce} -> + Nonce + end. + +-file("src/kryptos/block.gleam", 400). +-spec split_into_blocks(bitstring(), list(bitstring())) -> list(bitstring()). +split_into_blocks(Data, Acc) -> + case Data of + <> -> + split_into_blocks(Rest, [Block | Acc]); + + _ -> + lists:reverse(Acc) + end. + +-file("src/kryptos/block.gleam", 417). +-spec xor_with_counter(bitstring(), integer()) -> bitstring(). +xor_with_counter(A, T) -> + A_int@1 = case A of + <> -> A_int; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/block"/utf8>>, + function => <<"xor_with_counter"/utf8>>, + line => 418, + value => _assert_fail, + start => 11957, + 'end' => 11999, + pattern_start => 11968, + pattern_end => 11995}) + end, + Result = erlang:'bxor'(A_int@1, T), + <>. + +-file("src/kryptos/block.gleam", 298). +-spec wrap_inner( + block_cipher(), + bitstring(), + list(bitstring()), + integer(), + integer(), + integer(), + list(bitstring()) +) -> {bitstring(), list(bitstring())}. +wrap_inner(Cipher, A, R, N, J, I, Acc) -> + case R of + [] -> + {A, lists:reverse(Acc)}; + + [Ri | Rest] -> + B = kryptos_ffi:aes_encrypt_block( + Cipher, + <> + ), + {A_new@1, Ri_new@1} = case B of + <> -> {A_new, Ri_new}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/block"/utf8>>, + function => <<"wrap_inner"/utf8>>, + line => 311, + value => _assert_fail, + start => 9124, + 'end' => 9184, + pattern_start => 9135, + pattern_end => 9180}) + end, + T = (N * J) + I, + A_xored = xor_with_counter(A_new@1, T), + wrap_inner(Cipher, A_xored, Rest, N, J, I + 1, [Ri_new@1 | Acc]) + end. + +-file("src/kryptos/block.gleam", 281). +-spec wrap_rounds( + block_cipher(), + bitstring(), + list(bitstring()), + integer(), + integer() +) -> {bitstring(), list(bitstring())}. +wrap_rounds(Cipher, A, R, N, J) -> + case J < 6 of + false -> + {A, R}; + + true -> + {A_new, R_new} = wrap_inner(Cipher, A, R, N, J, 1, []), + wrap_rounds(Cipher, A_new, R_new, N, J + 1) + end. + +-file("src/kryptos/block.gleam", 378). +-spec unwrap_inner( + block_cipher(), + bitstring(), + list(bitstring()), + integer(), + integer(), + integer(), + list(bitstring()) +) -> {bitstring(), list(bitstring())}. +unwrap_inner(Cipher, A, R, N, J, I, Acc) -> + case R of + [] -> + {A, Acc}; + + [Ri | Rest] -> + T = (N * J) + I, + A_xored = xor_with_counter(A, T), + B = kryptos_ffi:aes_decrypt_block( + Cipher, + <> + ), + {A_new@1, Ri_new@1} = case B of + <> -> {A_new, Ri_new}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/block"/utf8>>, + function => <<"unwrap_inner"/utf8>>, + line => 393, + value => _assert_fail, + start => 11225, + 'end' => 11285, + pattern_start => 11236, + pattern_end => 11281}) + end, + unwrap_inner(Cipher, A_new@1, Rest, N, J, I - 1, [Ri_new@1 | Acc]) + end. + +-file("src/kryptos/block.gleam", 360). +-spec unwrap_rounds( + block_cipher(), + bitstring(), + list(bitstring()), + integer(), + integer() +) -> {bitstring(), list(bitstring())}. +unwrap_rounds(Cipher, A, R, N, J) -> + case J >= 0 of + false -> + {A, R}; + + true -> + {A_new, R_new} = unwrap_inner( + Cipher, + A, + lists:reverse(R), + N, + J, + N, + [] + ), + unwrap_rounds(Cipher, A_new, R_new, N, J - 1) + end. + +-file("src/kryptos/block.gleam", 424). +?DOC(false). +-spec aes_key(block_cipher()) -> bitstring(). +aes_key(Cipher) -> + case Cipher of + {aes, _, Key} -> + Key + end. + +-file("src/kryptos/block.gleam", 431). +?DOC(false). +-spec is_ctr(cipher_context()) -> boolean(). +is_ctr(Ctx) -> + case Ctx of + {ctr, _, _} -> + true; + + _ -> + false + end. + +-file("src/kryptos/block.gleam", 272). +-spec do_wrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. +do_wrap(Cipher, Plaintext) -> + N = erlang:byte_size(Plaintext) div 8, + R = split_into_blocks(Plaintext, []), + {A, R_final} = wrap_rounds( + Cipher, + <<16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6>>, + R, + N, + 0 + ), + {ok, gleam_stdlib:bit_array_concat([A | R_final])}. + +-file("src/kryptos/block.gleam", 259). +?DOC( + " Wraps key material using AES Key Wrap (RFC 3394).\n" + "\n" + " Key wrapping is used to protect cryptographic keys when they need to be\n" + " transported or stored. Unlike general encryption, key wrapping:\n" + " - Does not require an IV (uses a default IV internally)\n" + " - Provides integrity protection\n" + " - Output is always 8 bytes larger than input\n" + "\n" + " The plaintext must be a multiple of 8 bytes, minimum 16 bytes.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/block\n" + " import kryptos/crypto\n" + "\n" + " let assert Ok(kek) = block.aes_256(crypto.random_bytes(32))\n" + " let key_to_wrap = crypto.random_bytes(32)\n" + " let assert Ok(wrapped) = block.wrap(kek, key_to_wrap)\n" + " ```\n" +). +-spec wrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. +wrap(Cipher, Plaintext) -> + Size = erlang:byte_size(Plaintext), + case (Size >= 16) andalso ((Size rem 8) =:= 0) of + true -> + do_wrap(Cipher, Plaintext); + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 347). +-spec do_unwrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. +do_unwrap(Cipher, Ciphertext) -> + {A@1, Rest@1} = case Ciphertext of + <> -> {A, Rest}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/block"/utf8>>, + function => <<"do_unwrap"/utf8>>, + line => 348, + value => _assert_fail, + start => 10166, + 'end' => 10221, + pattern_start => 10177, + pattern_end => 10208}) + end, + N = erlang:byte_size(Rest@1) div 8, + R = split_into_blocks(Rest@1, []), + {A_final, R_final} = unwrap_rounds(Cipher, A@1, R, N, 5), + case fun kryptos_ffi:constant_time_equal/2( + A_final, + <<16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6>> + ) of + true -> + {ok, gleam_stdlib:bit_array_concat(R_final)}; + + false -> + {error, nil} + end. + +-file("src/kryptos/block.gleam", 331). +?DOC( + " Unwraps key material using AES Key Wrap (RFC 3394).\n" + "\n" + " The ciphertext must be a multiple of 8 bytes, minimum 24 bytes.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/block\n" + "\n" + " let assert Ok(kek) = block.aes_256(kek_bytes)\n" + " let assert Ok(unwrapped) = block.unwrap(kek, wrapped_key)\n" + " ```\n" +). +-spec unwrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. +unwrap(Cipher, Ciphertext) -> + Size = erlang:byte_size(Ciphertext), + case (Size >= 24) andalso ((Size rem 8) =:= 0) of + true -> + do_unwrap(Cipher, Ciphertext); + + false -> + {error, nil} + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@crypto.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@crypto.erl new file mode 100644 index 0000000..83760a0 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@crypto.erl @@ -0,0 +1,336 @@ +-module(kryptos@crypto). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/crypto.gleam"). +-export([hash/2, hmac/3, hkdf/5, concat_kdf/4, pbkdf2/5, random_bytes/1, random_uuid/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Convenience wrappers for hashing, key derivation, random bytes, and constant-time comparison.\n" + "\n" + " - One-shot hashing via `hash()` and HMAC via `hmac()`\n" + " - Key derivation: HKDF (RFC 5869), PBKDF2 (RFC 8018), Concat KDF (NIST SP 800-56A)\n" + " - Random bytes via `random_bytes()` and UUID v4 via `random_uuid()`\n" + " - Constant-time comparison via `constant_time_equal()`\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/crypto\n" + "\n" + " // Generate 32 random bytes (suitable for a 256-bit key)\n" + " let key = crypto.random_bytes(32)\n" + " ```\n" +). + +-file("src/kryptos/crypto.gleam", 41). +?DOC( + " Computes the hash digest of input data in one call.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/crypto\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(digest) = crypto.hash(hash.Sha256, <<\"hello\":utf8>>)\n" + " ```\n" +). +-spec hash(kryptos@hash:hash_algorithm(), bitstring()) -> {ok, bitstring()} | + {error, nil}. +hash(Algorithm, Data) -> + gleam@result:'try'( + kryptos@hash:new(Algorithm), + fun(Hasher) -> _pipe = Hasher, + _pipe@1 = kryptos_ffi:hash_update(_pipe, Data), + _pipe@2 = kryptos_ffi:hash_final(_pipe@1), + {ok, _pipe@2} end + ). + +-file("src/kryptos/crypto.gleam", 59). +?DOC( + " Computes the HMAC of input data in one call.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/crypto\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(mac) = crypto.hmac(hash.Sha256, key: <<\"secret\":utf8>>, data: <<\"hello\":utf8>>)\n" + " ```\n" +). +-spec hmac(kryptos@hash:hash_algorithm(), bitstring(), bitstring()) -> {ok, + bitstring()} | + {error, nil}. +hmac(Algorithm, Key, Data) -> + gleam@result:'try'( + kryptos@hmac:new(Algorithm, Key), + fun(Hmac) -> _pipe = Hmac, + _pipe@1 = crypto:mac_update(_pipe, Data), + _pipe@2 = crypto:mac_final(_pipe@1), + {ok, _pipe@2} end + ). + +-file("src/kryptos/crypto.gleam", 91). +?DOC( + " Derives key material using HKDF (RFC 5869).\n" + "\n" + " HKDF combines an extract-then-expand approach to derive cryptographically\n" + " strong key material from input key material. The algorithm must be\n" + " HMAC-compatible. Maximum output length is 255 * hash_length bytes.\n" + " A `None` salt uses hash-length zeros per RFC 5869.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import gleam/option\n" + " import kryptos/crypto\n" + " import kryptos/hash\n" + "\n" + " let ikm = crypto.random_bytes(32)\n" + " let salt = option.Some(crypto.random_bytes(16))\n" + " let assert Ok(derived) =\n" + " crypto.hkdf(hash.Sha256, input: ikm, salt:, info: <<\"app\":utf8>>, length: 32)\n" + " ```\n" +). +-spec hkdf( + kryptos@hash:hash_algorithm(), + bitstring(), + gleam@option:option(bitstring()), + bitstring(), + integer() +) -> {ok, bitstring()} | {error, nil}. +hkdf(Algorithm, Ikm, Salt, Info, Length) -> + Hash_len = kryptos@hash:byte_size(Algorithm), + Max_length = 255 * Hash_len, + Salt_bytes = gleam@option:lazy_unwrap( + Salt, + fun() -> _pipe = gleam@list:repeat(<<0>>, Hash_len), + gleam_stdlib:bit_array_concat(_pipe) end + ), + case {kryptos@hmac:supported_hash(Algorithm), + Length > 0, + Length =< Max_length} of + {true, true, true} -> + kryptos@internal@hkdf:do_derive( + Algorithm, + Ikm, + Salt_bytes, + Info, + Length + ); + + {_, _, _} -> + {error, nil} + end. + +-file("src/kryptos/crypto.gleam", 150). +-spec concat_kdf_supported_hash(kryptos@hash:hash_algorithm()) -> boolean(). +concat_kdf_supported_hash(Algorithm) -> + case Algorithm of + sha1 -> + true; + + sha256 -> + true; + + sha384 -> + true; + + sha512 -> + true; + + sha512x224 -> + true; + + sha512x256 -> + true; + + sha3x224 -> + true; + + sha3x256 -> + true; + + sha3x384 -> + true; + + sha3x512 -> + true; + + blake2b -> + false; + + blake2s -> + false; + + md5 -> + false; + + {shake128, _} -> + false; + + {shake256, _} -> + false + end. + +-file("src/kryptos/crypto.gleam", 128). +?DOC( + " Derives key material using Concat KDF (NIST SP 800-56A). Also called the\n" + " single-step or one-step key derivation function.\n" + "\n" + " Concat KDF uses a hash function to derive key material from a shared secret\n" + " and context-specific information. Supports SHA-1, SHA-2, and SHA-3 family\n" + " algorithms. Maximum output length is 255 * hash_length bytes.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/crypto\n" + " import kryptos/hash\n" + "\n" + " let secret = crypto.random_bytes(32)\n" + " let assert Ok(derived) =\n" + " crypto.concat_kdf(hash.Sha256, secret:, info: <<\"context\":utf8>>, length: 32)\n" + " ```\n" +). +-spec concat_kdf( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + integer() +) -> {ok, bitstring()} | {error, nil}. +concat_kdf(Algorithm, Secret, Info, Length) -> + Max_length = 255 * kryptos@hash:byte_size(Algorithm), + case {concat_kdf_supported_hash(Algorithm), + Length > 0, + Length =< Max_length} of + {true, true, true} -> + kryptos@internal@concat_kdf:derive_loop( + Algorithm, + Secret, + Info, + Length, + 1, + gleam@bytes_tree:new() + ); + + {_, _, _} -> + {error, nil} + end. + +-file("src/kryptos/crypto.gleam", 199). +?DOC( + " Derives key material from a password using PBKDF2 (RFC 8018).\n" + "\n" + " PBKDF2 applies a pseudorandom function (HMAC) to derive keys from passwords.\n" + " It is designed to be computationally expensive to resist brute-force attacks.\n" + "\n" + " **Note:** For password hashing in production applications, consider using\n" + " [Argus](https://github.com/Pevensie/argus) which provides Argon2 an\n" + " algorithm specifically designed for password storage. PBKDF2 is primarily\n" + " useful for interoperability with systems that require it.\n" + "\n" + " The algorithm must be HMAC-compatible. SHA-256 or stronger is recommended;\n" + " MD5 and SHA-1 are weak for password hashing.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/crypto\n" + " import kryptos/hash\n" + "\n" + " let salt = crypto.random_bytes(16)\n" + " let assert Ok(derived) =\n" + " crypto.pbkdf2(\n" + " hash.Sha256,\n" + " password: <<\"hunter2\":utf8>>,\n" + " salt:,\n" + " iterations: 100_000,\n" + " length: 32,\n" + " )\n" + " ```\n" +). +-spec pbkdf2( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + integer(), + integer() +) -> {ok, bitstring()} | {error, nil}. +pbkdf2(Algorithm, Password, Salt, Iterations, Length) -> + case {kryptos@hmac:supported_hash(Algorithm), Iterations > 0, Length > 0} of + {true, true, true} -> + kryptos_ffi:pbkdf2_derive( + Algorithm, + Password, + Salt, + Iterations, + Length + ); + + {_, _, _} -> + {error, nil} + end. + +-file("src/kryptos/crypto.gleam", 219). +?DOC( + " Generates cryptographically secure random bytes using the platform's\n" + " cryptographically secure random number generator.\n" + "\n" + " A negative length returns an empty `BitArray`.\n" +). +-spec random_bytes(integer()) -> bitstring(). +random_bytes(Length) -> + kryptos_ffi:random_bytes(Length). + +-file("src/kryptos/crypto.gleam", 223). +?DOC(" Generates a cryptographically secure random UUID v4.\n"). +-spec random_uuid() -> binary(). +random_uuid() -> + {A@1, B@1, C_raw@1, D_raw@1, E@1} = case kryptos_ffi:random_bytes(16) of + <> -> {A, B, C_raw, D_raw, E}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/crypto"/utf8>>, + function => <<"random_uuid"/utf8>>, + line => 224, + value => _assert_fail, + start => 6402, + 'end' => 6472, + pattern_start => 6413, + pattern_end => 6453}) + end, + C = erlang:'bor'(erlang:'band'(C_raw@1, 16#0FFF), 16#4000), + D = erlang:'bor'(erlang:'band'(D_raw@1, 16#3FFF), 16#8000), + Uuid = <<<<<<<<<<<<<<<<(gleam@string:pad_start( + gleam@int:to_base16(A@1), + 8, + <<"0"/utf8>> + ))/binary, + "-"/utf8>>/binary, + (gleam@string:pad_start( + gleam@int:to_base16(B@1), + 4, + <<"0"/utf8>> + ))/binary>>/binary, + "-"/utf8>>/binary, + (gleam@string:pad_start( + gleam@int:to_base16(C), + 4, + <<"0"/utf8>> + ))/binary>>/binary, + "-"/utf8>>/binary, + (gleam@string:pad_start(gleam@int:to_base16(D), 4, <<"0"/utf8>>))/binary>>/binary, + "-"/utf8>>/binary, + (gleam@string:pad_start(gleam@int:to_base16(E@1), 12, <<"0"/utf8>>))/binary>>, + string:lowercase(Uuid). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@ec.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@ec.erl new file mode 100644 index 0000000..a82f292 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@ec.erl @@ -0,0 +1,262 @@ +-module(kryptos@ec). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/ec.gleam"). +-export([coordinate_size/1, generate_key_pair/1, from_pem/1, from_der/1, to_pem/1, to_der/1, public_key_from_pem/1, public_key_from_der/1, public_key_from_raw_point/2, public_key_to_raw_point/1, public_key_to_pem/1, public_key_to_der/1, public_key_from_private_key/1, curve/1, public_key_curve/1, to_bytes/1, from_bytes/2]). +-export_type([private_key/0, public_key/0, curve/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Elliptic Curve Cryptography key generation and management.\n" + "\n" + " Key pair generation and management for elliptic curve cryptography,\n" + " supporting standard NIST curves and secp256k1. EC keys can be used for\n" + " both ECDSA signatures and ECDH key agreement.\n" + "\n" + " ## Key Generation\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + "\n" + " let #(private_key, public_key) = ec.generate_key_pair(ec.P256)\n" + " ```\n" + "\n" + " ## Import/Export\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let assert Ok(pem) = ec.to_pem(private_key)\n" + " let assert Ok(#(imported_private, _)) = ec.from_pem(pem)\n" + " ```\n" +). + +-type private_key() :: any(). + +-type public_key() :: any(). + +-type curve() :: p256 | p384 | p521 | secp256k1. + +-file("src/kryptos/ec.gleam", 49). +?DOC( + " Returns the coordinate size in bytes for the given curve.\n" + "\n" + " This is the size of each coordinate (x or y) in an EC point.\n" +). +-spec coordinate_size(curve()) -> integer(). +coordinate_size(Curve) -> + case Curve of + p256 -> + 32; + + secp256k1 -> + 32; + + p384 -> + 48; + + p521 -> + 66 + end. + +-file("src/kryptos/ec.gleam", 60). +?DOC(" Generates a new elliptic curve key pair.\n"). +-spec generate_key_pair(curve()) -> {private_key(), public_key()}. +generate_key_pair(Curve) -> + kryptos_ffi:ec_generate_key_pair(Curve). + +-file("src/kryptos/ec.gleam", 67). +?DOC( + " Imports an EC private key from PEM-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_pem(binary()) -> {ok, {private_key(), public_key()}} | {error, nil}. +from_pem(Pem) -> + kryptos_ffi:ec_import_private_key_pem(Pem). + +-file("src/kryptos/ec.gleam", 74). +?DOC( + " Imports an EC private key from DER-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_der(bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_der(Der) -> + kryptos_ffi:ec_import_private_key_der(Der). + +-file("src/kryptos/ec.gleam", 79). +?DOC( + " Exports an EC private key to PEM format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_pem(private_key()) -> {ok, binary()} | {error, nil}. +to_pem(Key) -> + _pipe = kryptos_ffi:ec_export_private_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/ec.gleam", 92). +?DOC( + " Exports an EC private key to DER format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_der(private_key()) -> {ok, bitstring()} | {error, nil}. +to_der(Key) -> + kryptos_ffi:ec_export_private_key_der(Key). + +-file("src/kryptos/ec.gleam", 99). +?DOC( + " Imports an EC public key from PEM-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_pem(binary()) -> {ok, public_key()} | {error, nil}. +public_key_from_pem(Pem) -> + kryptos_ffi:ec_import_public_key_pem(Pem). + +-file("src/kryptos/ec.gleam", 106). +?DOC( + " Imports an EC public key from DER-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_der(bitstring()) -> {ok, public_key()} | {error, nil}. +public_key_from_der(Der) -> + kryptos_ffi:ec_import_public_key_der(Der). + +-file("src/kryptos/ec.gleam", 124). +?DOC( + " Imports an EC public key from an uncompressed SEC1 point.\n" + "\n" + " The point must be in uncompressed format: `0x04 || x || y`\n" + " where x and y are the coordinates padded to the curve's coordinate size.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + "\n" + " let #(_private_key, public_key) = ec.generate_key_pair(ec.P256)\n" + " let point = ec.public_key_to_raw_point(public_key)\n" + " let assert Ok(imported) = ec.public_key_from_raw_point(ec.P256, point)\n" + " ```\n" +). +-spec public_key_from_raw_point(curve(), bitstring()) -> {ok, public_key()} | + {error, nil}. +public_key_from_raw_point(Curve, Point) -> + kryptos_ffi:ec_public_key_from_raw_point(Curve, Point). + +-file("src/kryptos/ec.gleam", 141). +?DOC( + " Exports a public key to uncompressed SEC1 point format.\n" + "\n" + " Returns a BitArray in the format: `0x04 || X || Y` where X and Y are\n" + " the coordinates of the public key point, each padded to the curve's\n" + " coordinate size.\n" + "\n" + " If the key was imported with a compressed point format, it will be\n" + " automatically decompressed.\n" + "\n" + " This is the inverse of `public_key_from_raw_point`.\n" +). +-spec public_key_to_raw_point(public_key()) -> bitstring(). +public_key_to_raw_point(Key) -> + kryptos_ffi:ec_public_key_to_raw_point(Key). + +-file("src/kryptos/ec.gleam", 146). +?DOC( + " Exports an EC public key to PEM format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_pem(public_key()) -> {ok, binary()} | {error, nil}. +public_key_to_pem(Key) -> + _pipe = kryptos_ffi:ec_export_public_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/ec.gleam", 160). +?DOC( + " Exports an EC public key to DER format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_der(public_key()) -> {ok, bitstring()} | {error, nil}. +public_key_to_der(Key) -> + kryptos_ffi:ec_export_public_key_der(Key). + +-file("src/kryptos/ec.gleam", 165). +?DOC(" Derives the public key from an EC private key.\n"). +-spec public_key_from_private_key(private_key()) -> public_key(). +public_key_from_private_key(Key) -> + kryptos_ffi:ec_public_key_from_private(Key). + +-file("src/kryptos/ec.gleam", 170). +?DOC(" Returns the curve for an EC private key.\n"). +-spec curve(private_key()) -> curve(). +curve(Key) -> + kryptos_ffi:ec_private_key_curve(Key). + +-file("src/kryptos/ec.gleam", 175). +?DOC(" Returns the curve for an EC public key.\n"). +-spec public_key_curve(public_key()) -> curve(). +public_key_curve(Key) -> + kryptos_ffi:ec_public_key_curve(Key). + +-file("src/kryptos/ec.gleam", 192). +?DOC( + " Exports an EC private key to raw scalar bytes.\n" + "\n" + " Returns the private scalar (the \"d\" value in JWK terminology) as\n" + " big-endian bytes. The size matches the curve's coordinate size.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let scalar = ec.to_bytes(private_key)\n" + " ```\n" +). +-spec to_bytes(private_key()) -> bitstring(). +to_bytes(Key) -> + kryptos_ffi:ec_private_key_to_bytes(Key). + +-file("src/kryptos/ec.gleam", 211). +?DOC( + " Imports an EC private key from raw scalar bytes.\n" + "\n" + " The scalar should be in big-endian format with size matching the\n" + " curve's coordinate size (32 bytes for P256/Secp256k1, 48 for P384,\n" + " 66 for P521).\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let scalar = ec.to_bytes(private_key)\n" + " let assert Ok(#(imported, _pub)) = ec.from_bytes(ec.P256, scalar)\n" + " ```\n" +). +-spec from_bytes(curve(), bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_bytes(Curve, Private_bytes) -> + kryptos_ffi:ec_private_key_from_bytes(Curve, Private_bytes). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdh.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdh.erl new file mode 100644 index 0000000..75358ed --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdh.erl @@ -0,0 +1,55 @@ +-module(kryptos@ecdh). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/ecdh.gleam"). +-export([compute_shared_secret/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Elliptic Curve Diffie-Hellman (ECDH) key agreement.\n" + "\n" + " ECDH allows two parties to establish a shared secret over an insecure\n" + " channel using elliptic curve key pairs. The shared secret can then be\n" + " used with a key derivation function (KDF) to derive symmetric keys.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdh\n" + "\n" + " // Alice generates a key pair\n" + " let #(alice_private, alice_public) = ec.generate_key_pair(ec.P256)\n" + "\n" + " // Bob generates a key pair\n" + " let #(bob_private, bob_public) = ec.generate_key_pair(ec.P256)\n" + "\n" + " // Both compute the same shared secret\n" + " let assert Ok(alice_shared) = ecdh.compute_shared_secret(alice_private, bob_public)\n" + " let assert Ok(bob_shared) = ecdh.compute_shared_secret(bob_private, alice_public)\n" + " // alice_shared == bob_shared\n" + " ```\n" +). + +-file("src/kryptos/ecdh.gleam", 37). +?DOC( + " Computes a shared secret using ECDH key agreement.\n" + "\n" + " Both parties compute the same shared secret by combining their private key\n" + " with the other party's public key. The result is the x-coordinate of the\n" + " resulting elliptic curve point, returned as raw bytes.\n" + "\n" + " The raw shared secret should be passed through a KDF (like HKDF) before\n" + " use as a symmetric key. Both keys must use the same curve.\n" +). +-spec compute_shared_secret(kryptos@ec:private_key(), kryptos@ec:public_key()) -> {ok, + bitstring()} | + {error, nil}. +compute_shared_secret(Private_key, Peer_public_key) -> + kryptos_ffi:ecdh_compute_shared_secret(Private_key, Peer_public_key). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdsa.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdsa.erl new file mode 100644 index 0000000..f4f42ee --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@ecdsa.erl @@ -0,0 +1,265 @@ +-module(kryptos@ecdsa). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/ecdsa.gleam"). +-export([sign/3, verify/4, der_to_rs/2, sign_rs/3, rs_to_der/2, verify_rs/4]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Elliptic Curve Digital Signature Algorithm (ECDSA).\n" + "\n" + " ECDSA provides digital signatures using elliptic curve cryptography,\n" + " offering strong security with smaller key sizes compared to RSA.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdsa\n" + " import kryptos/hash\n" + "\n" + " let #(private_key, public_key) = ec.generate_key_pair(ec.P256)\n" + " let message = <<\"hello world\":utf8>>\n" + " let signature = ecdsa.sign(private_key, message, hash.Sha256)\n" + " let valid = ecdsa.verify(public_key, message, signature, hash.Sha256)\n" + " // valid == True\n" + " ```\n" +). + +-file("src/kryptos/ecdsa.gleam", 36). +?DOC( + " Signs a message using ECDSA with the specified hash algorithm.\n" + "\n" + " The message is hashed internally before signing. Returns a DER-encoded\n" + " signature. Signatures may be non-deterministic depending on platform\n" + " (Erlang uses random nonces, some platforms may use deterministic\n" + " RFC 6979 nonces).\n" +). +-spec sign(kryptos@ec:private_key(), bitstring(), kryptos@hash:hash_algorithm()) -> bitstring(). +sign(Private_key, Message, Hash) -> + kryptos_ffi:ecdsa_sign(Private_key, Message, Hash). + +-file("src/kryptos/ecdsa.gleam", 48). +?DOC( + " Verifies an ECDSA signature against a message.\n" + "\n" + " The message is hashed internally before verification. The same hash\n" + " algorithm used during signing must be used for verification.\n" +). +-spec verify( + kryptos@ec:public_key(), + bitstring(), + bitstring(), + kryptos@hash:hash_algorithm() +) -> boolean(). +verify(Public_key, Message, Signature, Hash) -> + kryptos_ffi:ecdsa_verify(Public_key, Message, Signature, Hash). + +-file("src/kryptos/ecdsa.gleam", 128). +?DOC( + " Converts a DER-encoded ECDSA signature to R||S format.\n" + "\n" + " R||S format concatenates the r and s integer values, each padded\n" + " to the curve's coordinate size with leading zeros.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdsa\n" + " import kryptos/hash\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let der_sig = ecdsa.sign(private_key, <<\"hello\":utf8>>, hash.Sha256)\n" + " let assert Ok(rs_sig) = ecdsa.der_to_rs(der_sig, ec.P256)\n" + " ```\n" +). +-spec der_to_rs(bitstring(), kryptos@ec:curve()) -> {ok, bitstring()} | + {error, nil}. +der_to_rs(Der_sig, Curve) -> + Coord_size = kryptos@ec:coordinate_size(Curve), + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Der_sig), + fun(_use0) -> + {Content, Remaining} = _use0, + gleam@bool:guard( + erlang:byte_size(Remaining) /= 0, + {error, nil}, + fun() -> + gleam@result:'try'( + kryptos@internal@der:parse_integer(Content), + fun(_use0@1) -> + {R_bytes, Remaining@1} = _use0@1, + gleam@result:'try'( + kryptos@internal@der:parse_integer(Remaining@1), + fun(_use0@2) -> + {S_bytes, Remaining@2} = _use0@2, + gleam@bool:guard( + erlang:byte_size(Remaining@2) /= 0, + {error, nil}, + fun() -> + R = kryptos@internal@utils:strip_leading_zeros( + R_bytes + ), + S = kryptos@internal@utils:strip_leading_zeros( + S_bytes + ), + R_ok = erlang:byte_size(R) =< Coord_size, + S_ok = erlang:byte_size(S) =< Coord_size, + gleam@bool:guard( + not R_ok orelse not S_ok, + {error, nil}, + fun() -> + {ok, + gleam_stdlib:bit_array_concat( + [kryptos@internal@utils:pad_left( + R, + Coord_size + ), + kryptos@internal@utils:pad_left( + S, + Coord_size + )] + )} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/ecdsa.gleam", 70). +?DOC( + " Signs a message and returns the signature in R||S format (IEEE P1363).\n" + "\n" + " In R||S format, the signature is the concatenation of r and s values,\n" + " each padded to the curve's coordinate size.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdsa\n" + " import kryptos/hash\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let signature = ecdsa.sign_rs(private_key, <<\"hello\":utf8>>, hash.Sha256)\n" + " ```\n" +). +-spec sign_rs( + kryptos@ec:private_key(), + bitstring(), + kryptos@hash:hash_algorithm() +) -> bitstring(). +sign_rs(Private_key, Message, Hash) -> + Der_sig = kryptos_ffi:ecdsa_sign(Private_key, Message, Hash), + Curve = kryptos_ffi:ec_private_key_curve(Private_key), + Rs_sig@1 = case der_to_rs(Der_sig, Curve) of + {ok, Rs_sig} -> Rs_sig; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/ecdsa"/utf8>>, + function => <<"sign_rs"/utf8>>, + line => 77, + value => _assert_fail, + start => 2399, + 'end' => 2448, + pattern_start => 2410, + pattern_end => 2420}) + end, + Rs_sig@1. + +-file("src/kryptos/ecdsa.gleam", 173). +?DOC( + " Converts an R||S format signature to DER encoding.\n" + "\n" + " The R||S input must be exactly 2 * coordinate_size bytes.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdsa\n" + " import kryptos/hash\n" + "\n" + " let #(private_key, _public_key) = ec.generate_key_pair(ec.P256)\n" + " let rs_sig = ecdsa.sign_rs(private_key, <<\"hello\":utf8>>, hash.Sha256)\n" + " let assert Ok(der_sig) = ecdsa.rs_to_der(rs_sig, ec.P256)\n" + " ```\n" +). +-spec rs_to_der(bitstring(), kryptos@ec:curve()) -> {ok, bitstring()} | + {error, nil}. +rs_to_der(Rs, Curve) -> + Coord_size = kryptos@ec:coordinate_size(Curve), + case Rs of + <> -> + gleam@result:'try'( + kryptos@internal@der:encode_integer(R), + fun(R_encoded) -> + gleam@result:'try'( + kryptos@internal@der:encode_integer(S), + fun(S_encoded) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [R_encoded, S_encoded] + ) + ) + end + ) + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/ecdsa.gleam", 99). +?DOC( + " Verifies an R||S format signature against a message.\n" + "\n" + " The R||S format is the concatenation of r and s values, each padded\n" + " to the curve's coordinate size.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/ec\n" + " import kryptos/ecdsa\n" + " import kryptos/hash\n" + "\n" + " let #(private_key, public_key) = ec.generate_key_pair(ec.P256)\n" + " let message = <<\"hello\":utf8>>\n" + " let signature = ecdsa.sign_rs(private_key, message, hash.Sha256)\n" + " let valid = ecdsa.verify_rs(public_key, message, signature, hash.Sha256)\n" + " // valid == True\n" + " ```\n" +). +-spec verify_rs( + kryptos@ec:public_key(), + bitstring(), + bitstring(), + kryptos@hash:hash_algorithm() +) -> boolean(). +verify_rs(Public_key, Message, Signature, Hash) -> + Curve = kryptos_ffi:ec_public_key_curve(Public_key), + case rs_to_der(Signature, Curve) of + {ok, Der_sig} -> + kryptos_ffi:ecdsa_verify(Public_key, Message, Der_sig, Hash); + + {error, nil} -> + false + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@eddsa.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@eddsa.erl new file mode 100644 index 0000000..3ff8625 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@eddsa.erl @@ -0,0 +1,243 @@ +-module(kryptos@eddsa). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/eddsa.gleam"). +-export([key_size/1, generate_key_pair/1, sign/2, verify/3, from_bytes/2, to_bytes/1, public_key_from_bytes/2, public_key_to_bytes/1, from_pem/1, from_der/1, to_pem/1, to_der/1, public_key_from_pem/1, public_key_from_der/1, public_key_to_pem/1, public_key_to_der/1, public_key_from_private_key/1, curve/1, public_key_curve/1]). +-export_type([private_key/0, public_key/0, curve/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Edwards-curve Digital Signature Algorithm (EdDSA).\n" + "\n" + " EdDSA provides digital signatures using Edwards curves Ed25519 and Ed448.\n" + " Unlike ECDSA, EdDSA has built-in hashing (SHA-512 for Ed25519, SHAKE256\n" + " for Ed448) and produces deterministic signatures.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/eddsa\n" + "\n" + " let #(private_key, public_key) = eddsa.generate_key_pair(eddsa.Ed25519)\n" + " let message = <<\"hello world\":utf8>>\n" + " let signature = eddsa.sign(private_key, message)\n" + " let valid = eddsa.verify(public_key, message, signature)\n" + " // valid == True\n" + " ```\n" +). + +-type private_key() :: any(). + +-type public_key() :: any(). + +-type curve() :: ed25519 | ed448. + +-file("src/kryptos/eddsa.gleam", 37). +?DOC(" Returns the key size in bytes for the given curve.\n"). +-spec key_size(curve()) -> integer(). +key_size(Curve) -> + case Curve of + ed25519 -> + 32; + + ed448 -> + 57 + end. + +-file("src/kryptos/eddsa.gleam", 47). +?DOC(" Generates a new EdDSA key pair.\n"). +-spec generate_key_pair(curve()) -> {private_key(), public_key()}. +generate_key_pair(Curve) -> + kryptos_ffi:eddsa_generate_key_pair(Curve). + +-file("src/kryptos/eddsa.gleam", 56). +?DOC( + " Signs a message using EdDSA.\n" + "\n" + " The message is hashed internally using the curve's built-in hash function\n" + " (SHA-512 for Ed25519, SHAKE256 for Ed448). Signatures are deterministic:\n" + " signing the same message with the same key always produces the same signature.\n" +). +-spec sign(private_key(), bitstring()) -> bitstring(). +sign(Private_key, Message) -> + kryptos_ffi:eddsa_sign(Private_key, Message). + +-file("src/kryptos/eddsa.gleam", 61). +?DOC(" Verifies an EdDSA signature against a message.\n"). +-spec verify(public_key(), bitstring(), bitstring()) -> boolean(). +verify(Public_key, Message, Signature) -> + kryptos_ffi:eddsa_verify(Public_key, Message, Signature). + +-file("src/kryptos/eddsa.gleam", 77). +?DOC( + " Imports a private key from raw bytes.\n" + "\n" + " The bytes should be the raw private key seed:\n" + " - Ed25519: 32 bytes\n" + " - Ed448: 57 bytes\n" + "\n" + " Returns the private key and its corresponding public key, or `Error(Nil)`\n" + " if the bytes are invalid.\n" +). +-spec from_bytes(curve(), bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_bytes(Curve, Private_bytes) -> + kryptos_ffi:eddsa_private_key_from_bytes(Curve, Private_bytes). + +-file("src/kryptos/eddsa.gleam", 89). +?DOC( + " Exports a private key to raw bytes.\n" + "\n" + " Returns the raw private key seed:\n" + " - Ed25519: 32 bytes\n" + " - Ed448: 57 bytes\n" +). +-spec to_bytes(private_key()) -> bitstring(). +to_bytes(Key) -> + kryptos_ffi:eddsa_private_key_to_bytes(Key). + +-file("src/kryptos/eddsa.gleam", 106). +?DOC( + " Imports a public key from raw bytes.\n" + "\n" + " The bytes should be the raw public key point:\n" + " - Ed25519: 32 bytes\n" + " - Ed448: 57 bytes\n" + "\n" + " Returns the public key or `Error(Nil)` if the bytes have an invalid length.\n" + "\n" + " **Security note:** This function only validates the byte length, not that the\n" + " bytes encode a valid curve point or that the key is in the prime-order\n" + " subgroup. Callers that accept public keys from untrusted sources should\n" + " perform their own validation to reject small-order or invalid points, as\n" + " such keys can allow trivial signature forgery.\n" +). +-spec public_key_from_bytes(curve(), bitstring()) -> {ok, public_key()} | + {error, nil}. +public_key_from_bytes(Curve, Public_bytes) -> + kryptos_ffi:eddsa_public_key_from_bytes(Curve, Public_bytes). + +-file("src/kryptos/eddsa.gleam", 118). +?DOC( + " Exports a public key to raw bytes.\n" + "\n" + " Returns the raw public key point:\n" + " - Ed25519: 32 bytes\n" + " - Ed448: 57 bytes\n" +). +-spec public_key_to_bytes(public_key()) -> bitstring(). +public_key_to_bytes(Key) -> + kryptos_ffi:eddsa_public_key_to_bytes(Key). + +-file("src/kryptos/eddsa.gleam", 125). +?DOC( + " Imports an EdDSA private key from PEM-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_pem(binary()) -> {ok, {private_key(), public_key()}} | {error, nil}. +from_pem(Pem) -> + kryptos_ffi:eddsa_import_private_key_pem(Pem). + +-file("src/kryptos/eddsa.gleam", 132). +?DOC( + " Imports an EdDSA private key from DER-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_der(bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_der(Der) -> + kryptos_ffi:eddsa_import_private_key_der(Der). + +-file("src/kryptos/eddsa.gleam", 137). +?DOC( + " Exports an EdDSA private key to PEM format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_pem(private_key()) -> {ok, binary()} | {error, nil}. +to_pem(Key) -> + _pipe = kryptos_ffi:eddsa_export_private_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/eddsa.gleam", 150). +?DOC( + " Exports an EdDSA private key to DER format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_der(private_key()) -> {ok, bitstring()} | {error, nil}. +to_der(Key) -> + kryptos_ffi:eddsa_export_private_key_der(Key). + +-file("src/kryptos/eddsa.gleam", 157). +?DOC( + " Imports an EdDSA public key from PEM-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_pem(binary()) -> {ok, public_key()} | {error, nil}. +public_key_from_pem(Pem) -> + kryptos_ffi:eddsa_import_public_key_pem(Pem). + +-file("src/kryptos/eddsa.gleam", 164). +?DOC( + " Imports an EdDSA public key from DER-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_der(bitstring()) -> {ok, public_key()} | {error, nil}. +public_key_from_der(Der) -> + kryptos_ffi:eddsa_import_public_key_der(Der). + +-file("src/kryptos/eddsa.gleam", 169). +?DOC( + " Exports an EdDSA public key to PEM format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_pem(public_key()) -> {ok, binary()} | {error, nil}. +public_key_to_pem(Key) -> + _pipe = kryptos_ffi:eddsa_export_public_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/eddsa.gleam", 183). +?DOC( + " Exports an EdDSA public key to DER format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_der(public_key()) -> {ok, bitstring()} | {error, nil}. +public_key_to_der(Key) -> + kryptos_ffi:eddsa_export_public_key_der(Key). + +-file("src/kryptos/eddsa.gleam", 188). +?DOC(" Derives the public key from an EdDSA private key.\n"). +-spec public_key_from_private_key(private_key()) -> public_key(). +public_key_from_private_key(Key) -> + kryptos_ffi:eddsa_public_key_from_private(Key). + +-file("src/kryptos/eddsa.gleam", 193). +?DOC(" Returns the curve for an EdDSA private key.\n"). +-spec curve(private_key()) -> curve(). +curve(Key) -> + kryptos_ffi:eddsa_private_key_curve(Key). + +-file("src/kryptos/eddsa.gleam", 198). +?DOC(" Returns the curve for an EdDSA public key.\n"). +-spec public_key_curve(public_key()) -> curve(). +public_key_curve(Key) -> + kryptos_ffi:eddsa_public_key_curve(Key). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@hash.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@hash.erl new file mode 100644 index 0000000..f5fc565 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@hash.erl @@ -0,0 +1,239 @@ +-module(kryptos@hash). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/hash.gleam"). +-export([shake_128/1, shake_256/1, algorithm_name/1, byte_size/1, new/1, update/2, final/1, is_supported/1]). +-export_type([hash_algorithm/0, hasher/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Cryptographic hash functions.\n" + "\n" + " Hash functions take arbitrary input data and produce a fixed-size digest.\n" + " Use these for data integrity verification, fingerprinting, and as building\n" + " blocks for other cryptographic constructs like HMAC.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(h) = hash.new(hash.Sha256)\n" + " let digest = h |> hash.update(<<\"hello\":utf8>>) |> hash.final()\n" + " ```\n" +). + +-type hash_algorithm() :: blake2b | + blake2s | + md5 | + sha1 | + sha256 | + sha384 | + sha512 | + sha512x224 | + sha512x256 | + sha3x224 | + sha3x256 | + sha3x384 | + sha3x512 | + {shake128, integer()} | + {shake256, integer()}. + +-type hasher() :: any(). + +-file("src/kryptos/hash.gleam", 57). +?DOC( + " Creates a SHAKE128 hash algorithm with the given output length in bytes.\n" + "\n" + " The output length must be greater than zero.\n" +). +-spec shake_128(integer()) -> {ok, hash_algorithm()} | {error, nil}. +shake_128(Length) -> + case Length > 0 of + true -> + {ok, {shake128, Length}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/hash.gleam", 67). +?DOC( + " Creates a SHAKE256 hash algorithm with the given output length in bytes.\n" + "\n" + " The output length must be greater than zero.\n" +). +-spec shake_256(integer()) -> {ok, hash_algorithm()} | {error, nil}. +shake_256(Length) -> + case Length > 0 of + true -> + {ok, {shake256, Length}}; + + false -> + {error, nil} + end. + +-file("src/kryptos/hash.gleam", 75). +?DOC(false). +-spec algorithm_name(hash_algorithm()) -> binary(). +algorithm_name(Algorithm) -> + case Algorithm of + blake2b -> + <<"blake2b512"/utf8>>; + + blake2s -> + <<"blake2s256"/utf8>>; + + md5 -> + <<"md5"/utf8>>; + + sha1 -> + <<"sha1"/utf8>>; + + sha256 -> + <<"sha256"/utf8>>; + + sha384 -> + <<"sha384"/utf8>>; + + sha512 -> + <<"sha512"/utf8>>; + + sha512x224 -> + <<"sha512-224"/utf8>>; + + sha512x256 -> + <<"sha512-256"/utf8>>; + + sha3x224 -> + <<"sha3-224"/utf8>>; + + sha3x256 -> + <<"sha3-256"/utf8>>; + + sha3x384 -> + <<"sha3-384"/utf8>>; + + sha3x512 -> + <<"sha3-512"/utf8>>; + + {shake128, _} -> + <<"shake128"/utf8>>; + + {shake256, _} -> + <<"shake256"/utf8>> + end. + +-file("src/kryptos/hash.gleam", 96). +?DOC(" Returns the output size in bytes for a hash algorithm.\n"). +-spec byte_size(hash_algorithm()) -> integer(). +byte_size(Algorithm) -> + case Algorithm of + blake2b -> + 64; + + blake2s -> + 32; + + md5 -> + 16; + + sha1 -> + 20; + + sha256 -> + 32; + + sha384 -> + 48; + + sha512 -> + 64; + + sha512x224 -> + 28; + + sha512x256 -> + 32; + + sha3x224 -> + 28; + + sha3x256 -> + 32; + + sha3x384 -> + 48; + + sha3x512 -> + 64; + + {shake128, Output_length} -> + Output_length; + + {shake256, Output_length@1} -> + Output_length@1 + end. + +-file("src/kryptos/hash.gleam", 125). +?DOC( + " Creates a new hasher for incremental hashing.\n" + "\n" + " Use this when you need to hash data in chunks, such as when streaming\n" + " or when the full input isn't available at once.\n" +). +-spec new(hash_algorithm()) -> {ok, hasher()} | {error, nil}. +new(Algorithm) -> + case Algorithm of + {shake128, Output_length} when Output_length =< 0 -> + {error, nil}; + + {shake256, Output_length} when Output_length =< 0 -> + {error, nil}; + + _ -> + kryptos_ffi:hash_new(Algorithm) + end. + +-file("src/kryptos/hash.gleam", 142). +?DOC( + " Adds data to an in-progress hash computation.\n" + "\n" + " Can be called multiple times to incrementally hash data.\n" +). +-spec update(hasher(), bitstring()) -> hasher(). +update(Hasher, Data) -> + kryptos_ffi:hash_update(Hasher, Data). + +-file("src/kryptos/hash.gleam", 149). +?DOC( + " Finalizes the hash computation and returns the digest.\n" + "\n" + " After calling this function, the hasher should not be reused.\n" +). +-spec final(hasher()) -> bitstring(). +final(Hasher) -> + kryptos_ffi:hash_final(Hasher). + +-file("src/kryptos/hash.gleam", 155). +?DOC( + " Checks if a hash algorithm is supported by the current runtime.\n" + "\n" + " Some algorithms may not be available depending on the platform or\n" + " OpenSSL/crypto library version.\n" +). +-spec is_supported(hash_algorithm()) -> boolean(). +is_supported(Algorithm) -> + case new(Algorithm) of + {ok, _} -> + true; + + {error, _} -> + false + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@hmac.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@hmac.erl new file mode 100644 index 0000000..c564fbf --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@hmac.erl @@ -0,0 +1,148 @@ +-module(kryptos@hmac). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/hmac.gleam"). +-export([supported_hash/1, new/2, update/2, final/1, verify/4]). +-export_type([hmac/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " Hash-based Message Authentication Code (HMAC).\n" + "\n" + " HMAC provides message authentication using a cryptographic hash function\n" + " combined with a secret key. Use it to verify both data integrity and\n" + " authenticity.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/hmac\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(h) = hmac.new(hash.Sha256, <<\"secret key\":utf8>>)\n" + " let mac = h |> hmac.update(<<\"hello\":utf8>>) |> hmac.final()\n" + " ```\n" +). + +-type hmac() :: any(). + +-file("src/kryptos/hmac.gleam", 22). +?DOC(" Checks if a hash algorithm is supported for HMAC operations.\n"). +-spec supported_hash(kryptos@hash:hash_algorithm()) -> boolean(). +supported_hash(Algorithm) -> + case Algorithm of + md5 -> + true; + + sha1 -> + true; + + sha256 -> + true; + + sha384 -> + true; + + sha512 -> + true; + + sha512x224 -> + true; + + sha512x256 -> + true; + + blake2b -> + false; + + blake2s -> + false; + + sha3x224 -> + false; + + sha3x256 -> + false; + + sha3x384 -> + false; + + sha3x512 -> + false; + + {shake128, _} -> + false; + + {shake256, _} -> + false + end. + +-file("src/kryptos/hmac.gleam", 51). +?DOC( + " Creates a new HMAC for incremental authentication.\n" + "\n" + " Use this when you need to authenticate data in chunks, such as when streaming\n" + " or when the full input isn't available at once.\n" +). +-spec new(kryptos@hash:hash_algorithm(), bitstring()) -> {ok, hmac()} | + {error, nil}. +new(Algorithm, Key) -> + case supported_hash(Algorithm) of + true -> + kryptos_ffi:hmac_new(Algorithm, Key); + + false -> + {error, nil} + end. + +-file("src/kryptos/hmac.gleam", 67). +?DOC( + " Adds data to an in-progress HMAC computation.\n" + "\n" + " Can be called multiple times to incrementally authenticate data.\n" +). +-spec update(hmac(), bitstring()) -> hmac(). +update(Hmac, Data) -> + crypto:mac_update(Hmac, Data). + +-file("src/kryptos/hmac.gleam", 74). +?DOC( + " Finalizes the HMAC computation and returns the authentication code.\n" + "\n" + " After calling this function, the HMAC should not be reused.\n" +). +-spec final(hmac()) -> bitstring(). +final(Hmac) -> + crypto:mac_final(Hmac). + +-file("src/kryptos/hmac.gleam", 80). +?DOC( + " Verifies that a MAC matches the expected value using constant-time comparison.\n" + "\n" + " Computes the HMAC and compares it to the expected value in constant time\n" + " to prevent timing attacks.\n" +). +-spec verify( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + bitstring() +) -> {ok, boolean()} | {error, nil}. +verify(Algorithm, Key, Data, Expected) -> + gleam@result:'try'( + new(Algorithm, Key), + fun(Hmac_state) -> + Actual = begin + _pipe = Hmac_state, + _pipe@1 = crypto:mac_update(_pipe, Data), + crypto:mac_final(_pipe@1) + end, + {ok, kryptos_ffi:constant_time_equal(Actual, Expected)} + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@concat_kdf.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@concat_kdf.erl new file mode 100644 index 0000000..e144422 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@concat_kdf.erl @@ -0,0 +1,84 @@ +-module(kryptos@internal@concat_kdf). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/concat_kdf.gleam"). +-export([derive_loop/6]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/concat_kdf.gleam", 13). +?DOC(false). +-spec derive_loop( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + integer(), + integer(), + gleam@bytes_tree:bytes_tree() +) -> {ok, bitstring()} | {error, nil}. +derive_loop(Algorithm, Secret, Info, Remaining, Counter, Acc) -> + gleam@bool:guard( + Remaining =< 0, + {ok, erlang:list_to_bitstring(Acc)}, + fun() -> + Input = gleam_stdlib:bit_array_concat( + [<>, Secret, Info] + ), + gleam@result:'try'( + kryptos@hash:new(Algorithm), + fun(Hasher) -> + Block = begin + _pipe = Hasher, + _pipe@1 = kryptos_ffi:hash_update(_pipe, Input), + kryptos_ffi:hash_final(_pipe@1) + end, + Length = erlang:byte_size(Block), + case Remaining =< Length of + true -> + Final_block@1 = case gleam_stdlib:bit_array_slice( + Block, + 0, + Remaining + ) of + {ok, Final_block} -> Final_block; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/concat_kdf"/utf8>>, + function => <<"derive_loop"/utf8>>, + line => 37, + value => _assert_fail, + start => 1013, + 'end' => 1078, + pattern_start => 1024, + pattern_end => 1039}) + end, + _pipe@2 = gleam@bytes_tree:append( + Acc, + Final_block@1 + ), + _pipe@3 = erlang:list_to_bitstring(_pipe@2), + {ok, _pipe@3}; + + false -> + derive_loop( + Algorithm, + Secret, + Info, + Remaining - Length, + Counter + 1, + gleam@bytes_tree:append(Acc, Block) + ) + end + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@der.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@der.erl new file mode 100644 index 0000000..8455486 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@der.erl @@ -0,0 +1,1574 @@ +-module(kryptos@internal@der). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/der.gleam"). +-export([encode_length/1, parse_length/1, encode_context_tag/2, encode_context_primitive_tag/2, parse_content/2, parse_tlv/1, parse_context_tag/2, decode_oid_components/1, encode_bool/1, parse_bool/1, encode_integer/1, encode_small_int/1, parse_integer/1, encode_bit_string/1, parse_bit_string/1, encode_octet_string/1, parse_octet_string/1, encode_oid/1, parse_oid/1, encode_utf8_string/1, parse_utf8_string/1, encode_printable_string/1, parse_printable_string/1, parse_teletex_string/1, encode_ia5_string/1, parse_ia5_string/1, parse_utc_time/1, encode_generalized_time/1, encode_timestamp/1, parse_generalized_time/1, parse_universal_string/1, parse_bmp_string/1, encode_sequence/1, parse_sequence/1, encode_set/1, parse_set/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/der.gleam", 46). +?DOC(false). +-spec encode_length(integer()) -> {ok, bitstring()} | {error, nil}. +encode_length(Len) -> + case Len of + L when L < 0 -> + {error, nil}; + + L@1 when L@1 < 128 -> + {ok, <>}; + + L@2 when L@2 < 256 -> + {ok, <<16#81, L@2:8>>}; + + L@3 when L@3 =< 65535 -> + {ok, <<16#82, L@3:16>>}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 60). +?DOC(false). +-spec parse_length(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. +parse_length(Bytes) -> + case Bytes of + <> when Len < 128 -> + {ok, {Len, Rest}}; + + <<16#81, Len@1:8, Rest@1/bitstring>> when Len@1 >= 128 -> + {ok, {Len@1, Rest@1}}; + + <<16#82, Len@2:16, Rest@2/bitstring>> when Len@2 >= 256 -> + {ok, {Len@2, Rest@2}}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 243). +?DOC(false). +-spec is_printable_char(integer()) -> boolean(). +is_printable_char(Codepoint) -> + case Codepoint of + C when (C >= 65) andalso (C =< 90) -> + true; + + C@1 when (C@1 >= 97) andalso (C@1 =< 122) -> + true; + + C@2 when (C@2 >= 48) andalso (C@2 =< 57) -> + true; + + 32 -> + true; + + 39 -> + true; + + 40 -> + true; + + 41 -> + true; + + 43 -> + true; + + 44 -> + true; + + 45 -> + true; + + 46 -> + true; + + 47 -> + true; + + 58 -> + true; + + 61 -> + true; + + 63 -> + true; + + _ -> + false + end. + +-file("src/kryptos/internal/der.gleam", 277). +?DOC(false). +-spec is_valid_printable_string(bitstring()) -> boolean(). +is_valid_printable_string(Value) -> + case Value of + <<>> -> + true; + + <> -> + is_printable_char(Byte) andalso is_valid_printable_string(Rest); + + _ -> + false + end. + +-file("src/kryptos/internal/der.gleam", 537). +?DOC(false). +-spec encode_context_tag(integer(), bitstring()) -> {ok, bitstring()} | + {error, nil}. +encode_context_tag(Tag, Content) -> + Tag_byte = erlang:'bor'(16#a0, Tag), + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, + gleam_stdlib:bit_array_concat( + [<>, Len_bytes, Content] + )} + end + ). + +-file("src/kryptos/internal/der.gleam", 559). +?DOC(false). +-spec encode_context_primitive_tag(integer(), bitstring()) -> {ok, bitstring()} | + {error, nil}. +encode_context_primitive_tag(Tag, Content) -> + Tag_byte = erlang:'bor'(16#80, Tag), + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, + gleam_stdlib:bit_array_concat( + [<>, Len_bytes, Content] + )} + end + ). + +-file("src/kryptos/internal/der.gleam", 569). +?DOC(false). +-spec parse_content(bitstring(), integer()) -> {ok, {bitstring(), bitstring()}} | + {error, nil}. +parse_content(Content, Len) -> + Content_size = erlang:byte_size(Content), + gleam@bool:guard( + Content_size < Len, + {error, nil}, + fun() -> + Inner@1 = case gleam_stdlib:bit_array_slice(Content, 0, Len) of + {ok, Inner} -> Inner; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_content"/utf8>>, + line => 576, + value => _assert_fail, + start => 20465, + 'end' => 20520, + pattern_start => 20476, + pattern_end => 20485}) + end, + Remaining@1 = case gleam_stdlib:bit_array_slice( + Content, + Len, + Content_size - Len + ) of + {ok, Remaining} -> Remaining; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_content"/utf8>>, + line => 577, + value => _assert_fail@1, + start => 20523, + 'end' => 20599, + pattern_start => 20534, + pattern_end => 20547}) + end, + {ok, {Inner@1, Remaining@1}} + end + ). + +-file("src/kryptos/internal/der.gleam", 582). +?DOC(false). +-spec parse_tlv(bitstring()) -> {ok, {integer(), bitstring(), bitstring()}} | + {error, nil}. +parse_tlv(Bytes) -> + case Bytes of + <> -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value, Remaining} = _use0@1, + {ok, {Tag, Value, Remaining}} + end + ) + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 593). +?DOC(false). +-spec require_tag( + bitstring(), + integer(), + fun((bitstring()) -> {ok, FAC} | {error, nil}) +) -> {ok, FAC} | {error, nil}. +require_tag(Bytes, Tag, Next) -> + case Bytes of + <> when T =:= Tag -> + Next(Rest); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 546). +?DOC(false). +-spec parse_context_tag(bitstring(), integer()) -> {ok, + {bitstring(), bitstring()}} | + {error, nil}. +parse_context_tag(Bytes, Tag) -> + Tag_byte = erlang:'bor'(16#a0, Tag), + require_tag( + Bytes, + Tag_byte, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + parse_content(Content, Len) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 604). +?DOC(false). +-spec reject_non_minimal_zeros(bitstring(), fun(() -> {ok, FAH} | {error, nil})) -> {ok, + FAH} | + {error, nil}. +reject_non_minimal_zeros(Value, Next) -> + case Value of + <<16#00, Second:8, _/bitstring>> when Second < 128 -> + {error, nil}; + + _ -> + Next() + end. + +-file("src/kryptos/internal/der.gleam", 614). +?DOC(false). +-spec bytes_from_list(list(integer())) -> bitstring(). +bytes_from_list(Bytes) -> + _pipe = Bytes, + _pipe@1 = gleam@list:fold( + _pipe, + gleam@bytes_tree:new(), + fun(Tree, Byte) -> gleam@bytes_tree:append(Tree, <>) end + ), + erlang:list_to_bitstring(_pipe@1). + +-file("src/kryptos/internal/der.gleam", 629). +?DOC(false). +-spec encode_oid_component_base128(integer(), list(integer())) -> list(integer()). +encode_oid_component_base128(Value, Acc) -> + case Value of + 0 -> + Acc; + + _ -> + Byte = erlang:'band'(Value, 16#7f), + Next_value = erlang:'bsr'(Value, 7), + New_byte = case Acc of + [] -> + Byte; + + _ -> + erlang:'bor'(Byte, 16#80) + end, + encode_oid_component_base128(Next_value, [New_byte | Acc]) + end. + +-file("src/kryptos/internal/der.gleam", 622). +?DOC(false). +-spec encode_oid_component(integer()) -> list(integer()). +encode_oid_component(Value) -> + case Value < 128 of + true -> + [Value]; + + false -> + encode_oid_component_base128(Value, []) + end. + +-file("src/kryptos/internal/der.gleam", 661). +?DOC(false). +-spec decode_first_oid_component(bitstring(), integer()) -> {ok, + {integer(), bitstring()}} | + {error, nil}. +decode_first_oid_component(Bytes, Acc) -> + case Bytes of + <<>> -> + {error, nil}; + + <> -> + Value = erlang:'bor'( + erlang:'bsl'(Acc, 7), + erlang:'band'(Byte, 16#7f) + ), + Is_continuation = erlang:'band'(Byte, 16#80) /= 0, + case Is_continuation of + true -> + decode_first_oid_component(Rest, Value); + + false -> + {ok, {Value, Rest}} + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 683). +?DOC(false). +-spec decode_oid_rest(bitstring(), integer(), list(integer())) -> {ok, + list(integer())} | + {error, nil}. +decode_oid_rest(Bytes, Acc, Components) -> + case Bytes of + <<>> when Acc =:= 0 -> + {ok, lists:reverse(Components)}; + + <<>> -> + {error, nil}; + + <> -> + Value = erlang:'bor'( + erlang:'bsl'(Acc, 7), + erlang:'band'(Byte, 16#7f) + ), + Is_continuation = erlang:'band'(Byte, 16#80) /= 0, + case Is_continuation of + true -> + decode_oid_rest(Rest, Value, Components); + + false -> + decode_oid_rest(Rest, 0, [Value | Components]) + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 647). +?DOC(false). +-spec decode_oid_components(bitstring()) -> {ok, list(integer())} | {error, nil}. +decode_oid_components(Bytes) -> + gleam@result:'try'( + decode_first_oid_component(Bytes, 0), + fun(_use0) -> + {First_value, Rest} = _use0, + First = case First_value of + V when V < 40 -> + 0; + + V@1 when V@1 < 80 -> + 1; + + _ -> + 2 + end, + Second = First_value - (First * 40), + gleam@result:'try'( + decode_oid_rest(Rest, 0, []), + fun(Rest_components) -> + {ok, [First, Second | Rest_components]} + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 718). +?DOC(false). +-spec latin1_to_utf8_loop(bitstring(), list(integer())) -> {ok, list(integer())} | + {error, nil}. +latin1_to_utf8_loop(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, Acc}; + + <> -> + case gleam@string:utf_codepoint(Byte) of + {ok, Cp} -> + latin1_to_utf8_loop(Rest, [Cp | Acc]); + + {error, _} -> + {error, nil} + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 711). +?DOC(false). +-spec latin1_to_utf8(bitstring()) -> {ok, binary()} | {error, nil}. +latin1_to_utf8(Bytes) -> + _pipe = latin1_to_utf8_loop(Bytes, []), + gleam@result:map(_pipe, fun(Codepoints) -> _pipe@1 = Codepoints, + _pipe@2 = lists:reverse(_pipe@1), + gleam_stdlib:utf_codepoint_list_to_string(_pipe@2) end). + +-file("src/kryptos/internal/der.gleam", 744). +?DOC(false). +-spec ucs2_to_utf8_loop(bitstring(), list(integer())) -> {ok, list(integer())} | + {error, nil}. +ucs2_to_utf8_loop(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, Acc}; + + <> -> + case gleam@string:utf_codepoint(Codepoint) of + {ok, Cp} -> + ucs2_to_utf8_loop(Rest, [Cp | Acc]); + + {error, _} -> + {error, nil} + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 737). +?DOC(false). +-spec ucs2_to_utf8(bitstring()) -> {ok, binary()} | {error, nil}. +ucs2_to_utf8(Bytes) -> + _pipe = ucs2_to_utf8_loop(Bytes, []), + gleam@result:map(_pipe, fun(Codepoints) -> _pipe@1 = Codepoints, + _pipe@2 = lists:reverse(_pipe@1), + gleam_stdlib:utf_codepoint_list_to_string(_pipe@2) end). + +-file("src/kryptos/internal/der.gleam", 770). +?DOC(false). +-spec ucs4_to_utf8_loop(bitstring(), list(integer())) -> {ok, list(integer())} | + {error, nil}. +ucs4_to_utf8_loop(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, Acc}; + + <> -> + case gleam@string:utf_codepoint(Codepoint) of + {ok, Cp} -> + ucs4_to_utf8_loop(Rest, [Cp | Acc]); + + {error, _} -> + {error, nil} + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 763). +?DOC(false). +-spec ucs4_to_utf8(bitstring()) -> {ok, binary()} | {error, nil}. +ucs4_to_utf8(Bytes) -> + _pipe = ucs4_to_utf8_loop(Bytes, []), + gleam@result:map(_pipe, fun(Codepoints) -> _pipe@1 = Codepoints, + _pipe@2 = lists:reverse(_pipe@1), + gleam_stdlib:utf_codepoint_list_to_string(_pipe@2) end). + +-file("src/kryptos/internal/der.gleam", 70). +?DOC(false). +-spec encode_bool(boolean()) -> bitstring(). +encode_bool(Value) -> + case Value of + true -> + <<16#01, 16#01, 16#ff>>; + + false -> + <<16#01, 16#01, 16#00>> + end. + +-file("src/kryptos/internal/der.gleam", 81). +?DOC(false). +-spec parse_bool(bitstring()) -> {ok, {boolean(), bitstring()}} | {error, nil}. +parse_bool(Bytes) -> + require_tag( + Bytes, + 16#01, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len /= 1, + {error, nil}, + fun() -> case Content of + <<16#00, Remaining/bitstring>> -> + {ok, {false, Remaining}}; + + <<_:8, Remaining@1/bitstring>> -> + {ok, {true, Remaining@1}}; + + _ -> + {error, nil} + end end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 96). +?DOC(false). +-spec encode_integer(bitstring()) -> {ok, bitstring()} | {error, nil}. +encode_integer(Value) -> + Stripped = kryptos@internal@utils:strip_leading_zeros(Value), + Int_bytes = case Stripped of + <> when High >= 128 -> + gleam_stdlib:bit_array_concat([<<16#00>>, Stripped]); + + <<>> -> + <<16#00>>; + + _ -> + Stripped + end, + gleam@result:'try'( + encode_length(erlang:byte_size(Int_bytes)), + fun(Len_bytes) -> + {ok, + gleam_stdlib:bit_array_concat([<<16#02>>, Len_bytes, Int_bytes])} + end + ). + +-file("src/kryptos/internal/der.gleam", 113). +?DOC(false). +-spec encode_small_int(integer()) -> {ok, bitstring()} | {error, nil}. +encode_small_int(N) -> + case N of + _ when N < 0 -> + {error, nil}; + + _ when N < 16#100 -> + encode_integer(<>); + + _ when N < 16#10000 -> + encode_integer(<>); + + _ when N < 16#1000000 -> + encode_integer(<>); + + _ when N < 16#100000000 -> + encode_integer(<>); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 128). +?DOC(false). +-spec parse_integer(bitstring()) -> {ok, {bitstring(), bitstring()}} | + {error, nil}. +parse_integer(Bytes) -> + require_tag( + Bytes, + 16#02, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len =< 0, + {error, nil}, + fun() -> + Content_size = erlang:byte_size(Content), + gleam@bool:guard( + Content_size < Len, + {error, nil}, + fun() -> + Value@1 = case gleam_stdlib:bit_array_slice( + Content, + 0, + Len + ) of + {ok, Value} -> Value; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_integer"/utf8>>, + line => 137, + value => _assert_fail, + start => 4080, + 'end' => 4135, + pattern_start => 4091, + pattern_end => 4100} + ) + end, + reject_non_minimal_zeros( + Value@1, + fun() -> + Remaining@1 = case gleam_stdlib:bit_array_slice( + Content, + Len, + Content_size - Len + ) of + {ok, Remaining} -> Remaining; + _assert_fail@1 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_integer"/utf8>>, + line => 141, + value => _assert_fail@1, + start => 4252, + 'end' => 4328, + pattern_start => 4263, + pattern_end => 4276} + ) + end, + {ok, {Value@1, Remaining@1}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 182). +?DOC(false). +-spec encode_bit_string(bitstring()) -> {ok, bitstring()} | {error, nil}. +encode_bit_string(Value) -> + Bit_size = erlang:bit_size(Value), + Unused_bits = case Bit_size rem 8 of + 0 -> + 0; + + Remainder -> + 8 - Remainder + end, + Padded = gleam_stdlib:bit_array_pad_to_bytes(Value), + Content = <>, + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#03>>, Len_bytes, Content])} + end + ). + +-file("src/kryptos/internal/der.gleam", 198). +?DOC(false). +-spec parse_bit_string(bitstring()) -> {ok, {bitstring(), bitstring()}} | + {error, nil}. +parse_bit_string(Bytes) -> + require_tag( + Bytes, + 16#03, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len < 1, + {error, nil}, + fun() -> + Content_size = erlang:byte_size(Content), + gleam@bool:guard( + Content_size < Len, + {error, nil}, + fun() -> case Content of + <<16#00, + Value:(Len - 1)/binary, + Remaining/bitstring>> -> + {ok, {Value, Remaining}}; + + _ -> + {error, nil} + end end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 214). +?DOC(false). +-spec encode_octet_string(bitstring()) -> {ok, bitstring()} | {error, nil}. +encode_octet_string(Value) -> + gleam@result:'try'( + encode_length(erlang:byte_size(Value)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#04>>, Len_bytes, Value])} + end + ). + +-file("src/kryptos/internal/der.gleam", 220). +?DOC(false). +-spec parse_octet_string(bitstring()) -> {ok, {bitstring(), bitstring()}} | + {error, nil}. +parse_octet_string(Bytes) -> + require_tag( + Bytes, + 16#04, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + parse_content(Content, Len) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 494). +?DOC(false). +-spec encode_oid(list(integer())) -> {ok, bitstring()} | {error, nil}. +encode_oid(Components) -> + case Components of + [First, Second | Rest] when (((First >= 0) andalso (First =< 2)) andalso (Second >= 0)) andalso ((First =:= 2) orelse (Second =< 39)) -> + First_value = (First * 40) + Second, + First_bytes = encode_oid_component(First_value), + Rest_bytes = gleam@list:flat_map(Rest, fun encode_oid_component/1), + Content = gleam_stdlib:bit_array_concat( + [bytes_from_list(First_bytes), bytes_from_list(Rest_bytes)] + ), + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, + gleam_stdlib:bit_array_concat( + [<<16#06>>, Len_bytes, Content] + )} + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/der.gleam", 518). +?DOC(false). +-spec parse_oid(bitstring()) -> {ok, {list(integer()), bitstring()}} | + {error, nil}. +parse_oid(Bytes) -> + require_tag( + Bytes, + 16#06, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len < 1, + {error, nil}, + fun() -> + Content_size = erlang:byte_size(Content), + gleam@bool:guard( + Content_size < Len, + {error, nil}, + fun() -> + Oid_bytes@1 = case gleam_stdlib:bit_array_slice( + Content, + 0, + Len + ) of + {ok, Oid_bytes} -> Oid_bytes; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_oid"/utf8>>, + line => 527, + value => _assert_fail, + start => 18708, + 'end' => 18767, + pattern_start => 18719, + pattern_end => 18732} + ) + end, + Remaining@1 = case gleam_stdlib:bit_array_slice( + Content, + Len, + Content_size - Len + ) of + {ok, Remaining} -> Remaining; + _assert_fail@1 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_oid"/utf8>>, + line => 528, + value => _assert_fail@1, + start => 18770, + 'end' => 18846, + pattern_start => 18781, + pattern_end => 18794} + ) + end, + gleam@result:'try'( + decode_oid_components(Oid_bytes@1), + fun(Components) -> + {ok, {Components, Remaining@1}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 227). +?DOC(false). +-spec encode_utf8_string(binary()) -> {ok, bitstring()} | {error, nil}. +encode_utf8_string(Value) -> + Content = gleam_stdlib:identity(Value), + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#0c>>, Len_bytes, Content])} + end + ). + +-file("src/kryptos/internal/der.gleam", 234). +?DOC(false). +-spec parse_utf8_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_utf8_string(Bytes) -> + require_tag( + Bytes, + 16#0c, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + gleam@bit_array:to_string(Value_bytes), + fun(Value) -> {ok, {Value, Remaining}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 289). +?DOC(false). +-spec encode_printable_string(binary()) -> {ok, bitstring()} | {error, nil}. +encode_printable_string(Value) -> + Content = gleam_stdlib:identity(Value), + gleam@bool:guard( + not is_valid_printable_string(Content), + {error, nil}, + fun() -> + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, + gleam_stdlib:bit_array_concat( + [<<16#13>>, Len_bytes, Content] + )} + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 302). +?DOC(false). +-spec parse_printable_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_printable_string(Bytes) -> + require_tag( + Bytes, + 16#13, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + gleam@bit_array:to_string(Value_bytes), + fun(Value) -> {ok, {Value, Remaining}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 334). +?DOC(false). +-spec parse_teletex_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_teletex_string(Bytes) -> + require_tag( + Bytes, + 16#14, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + latin1_to_utf8(Value_bytes), + fun(Value) -> {ok, {Value, Remaining}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 313). +?DOC(false). +-spec encode_ia5_string(binary()) -> {ok, bitstring()} | {error, nil}. +encode_ia5_string(Value) -> + Content = gleam_stdlib:identity(Value), + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#16>>, Len_bytes, Content])} + end + ). + +-file("src/kryptos/internal/der.gleam", 322). +?DOC(false). +-spec parse_ia5_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_ia5_string(Bytes) -> + require_tag( + Bytes, + 16#16, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + gleam@bit_array:to_string(Value_bytes), + fun(Value) -> {ok, {Value, Remaining}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 380). +?DOC(false). +-spec encode_utc_time(gleam@time@timestamp:timestamp()) -> {ok, bitstring()} | + {error, nil}. +encode_utc_time(Timestamp) -> + {Date, Time} = gleam@time@timestamp:to_calendar(Timestamp, {duration, 0, 0}), + Yy = erlang:element(2, Date) rem 100, + Pad2 = fun(_capture) -> + kryptos@internal@utils:int_to_padded_string(_capture, 2) + end, + Content = <<<<<<<<<<<<(Pad2(Yy))/binary, + (Pad2( + gleam@time@calendar:month_to_int( + erlang:element(3, Date) + ) + ))/binary>>/binary, + (Pad2(erlang:element(4, Date)))/binary>>/binary, + (Pad2(erlang:element(2, Time)))/binary>>/binary, + (Pad2(erlang:element(3, Time)))/binary>>/binary, + (Pad2(erlang:element(4, Time)))/binary>>/binary, + "Z"/utf8>>, + Bytes = gleam_stdlib:identity(Content), + gleam@result:'try'( + encode_length(erlang:byte_size(Bytes)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#17>>, Len_bytes, Bytes])} + end + ). + +-file("src/kryptos/internal/der.gleam", 399). +?DOC(false). +-spec parse_utc_time(bitstring()) -> {ok, + {gleam@time@timestamp:timestamp(), bitstring()}} | + {error, nil}. +parse_utc_time(Bytes) -> + require_tag( + Bytes, + 16#17, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len /= 13, + {error, nil}, + fun() -> + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Time_bytes, Remaining} = _use0@1, + gleam@result:'try'( + gleam@bit_array:to_string(Time_bytes), + fun(Time_str) -> + gleam@bool:guard( + not gleam_stdlib:string_ends_with( + Time_str, + <<"Z"/utf8>> + ), + {error, nil}, + fun() -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 0, + 2 + ) + ), + fun(Yy) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 2, + 2 + ) + ), + fun(Month_int) -> + gleam@result:'try'( + gleam@time@calendar:month_from_int( + Month_int + ), + fun( + Month + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 4, + 2 + ) + ), + fun( + Day + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 6, + 2 + ) + ), + fun( + Hour + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 8, + 2 + ) + ), + fun( + Minute + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 10, + 2 + ) + ), + fun( + Second + ) -> + Year = case Yy + >= 50 of + true -> + 1900 + + Yy; + + false -> + 2000 + + Yy + end, + Ts = gleam@time@timestamp:from_calendar( + {date, + Year, + Month, + Day}, + {time_of_day, + Hour, + Minute, + Second, + 0}, + {duration, + 0, + 0} + ), + {ok, + {Ts, + Remaining}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 436). +?DOC(false). +-spec encode_generalized_time(gleam@time@timestamp:timestamp()) -> {ok, + bitstring()} | + {error, nil}. +encode_generalized_time(Timestamp) -> + {Date, Time} = gleam@time@timestamp:to_calendar(Timestamp, {duration, 0, 0}), + Pad2 = fun(_capture) -> + kryptos@internal@utils:int_to_padded_string(_capture, 2) + end, + Pad4 = fun(_capture@1) -> + kryptos@internal@utils:int_to_padded_string(_capture@1, 4) + end, + Content = <<<<<<<<<<<<(Pad4(erlang:element(2, Date)))/binary, + (Pad2( + gleam@time@calendar:month_to_int( + erlang:element(3, Date) + ) + ))/binary>>/binary, + (Pad2(erlang:element(4, Date)))/binary>>/binary, + (Pad2(erlang:element(2, Time)))/binary>>/binary, + (Pad2(erlang:element(3, Time)))/binary>>/binary, + (Pad2(erlang:element(4, Time)))/binary>>/binary, + "Z"/utf8>>, + Bytes = gleam_stdlib:identity(Content), + gleam@result:'try'( + encode_length(erlang:byte_size(Bytes)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#18>>, Len_bytes, Bytes])} + end + ). + +-file("src/kryptos/internal/der.gleam", 371). +?DOC(false). +-spec encode_timestamp(gleam@time@timestamp:timestamp()) -> {ok, bitstring()} | + {error, nil}. +encode_timestamp(Timestamp) -> + {Date, _} = gleam@time@timestamp:to_calendar(Timestamp, {duration, 0, 0}), + case (erlang:element(2, Date) >= 1950) andalso (erlang:element(2, Date) < 2050) of + true -> + encode_utc_time(Timestamp); + + false -> + encode_generalized_time(Timestamp) + end. + +-file("src/kryptos/internal/der.gleam", 454). +?DOC(false). +-spec parse_generalized_time(bitstring()) -> {ok, + {gleam@time@timestamp:timestamp(), bitstring()}} | + {error, nil}. +parse_generalized_time(Bytes) -> + require_tag( + Bytes, + 16#18, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + Len /= 15, + {error, nil}, + fun() -> + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Time_bytes, Remaining} = _use0@1, + gleam@result:'try'( + gleam@bit_array:to_string(Time_bytes), + fun(Time_str) -> + gleam@bool:guard( + not gleam_stdlib:string_ends_with( + Time_str, + <<"Z"/utf8>> + ), + {error, nil}, + fun() -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 0, + 4 + ) + ), + fun(Year) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 4, + 2 + ) + ), + fun(Month_int) -> + gleam@result:'try'( + gleam@time@calendar:month_from_int( + Month_int + ), + fun( + Month + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 6, + 2 + ) + ), + fun( + Day + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 8, + 2 + ) + ), + fun( + Hour + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 10, + 2 + ) + ), + fun( + Minute + ) -> + gleam@result:'try'( + gleam_stdlib:parse_int( + gleam@string:slice( + Time_str, + 12, + 2 + ) + ), + fun( + Second + ) -> + Timestamp = gleam@time@timestamp:from_calendar( + {date, + Year, + Month, + Day}, + {time_of_day, + Hour, + Minute, + Second, + 0}, + {duration, + 0, + 0} + ), + {ok, + {Timestamp, + Remaining}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 359). +?DOC(false). +-spec parse_universal_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_universal_string(Bytes) -> + require_tag( + Bytes, + 16#1c, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + (Len rem 4) /= 0, + {error, nil}, + fun() -> + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + ucs4_to_utf8(Value_bytes), + fun(Value) -> + {ok, {Value, Remaining}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 346). +?DOC(false). +-spec parse_bmp_string(bitstring()) -> {ok, {binary(), bitstring()}} | + {error, nil}. +parse_bmp_string(Bytes) -> + require_tag( + Bytes, + 16#1e, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + gleam@bool:guard( + (Len rem 2) /= 0, + {error, nil}, + fun() -> + gleam@result:'try'( + parse_content(Content, Len), + fun(_use0@1) -> + {Value_bytes, Remaining} = _use0@1, + gleam@result:'try'( + ucs2_to_utf8(Value_bytes), + fun(Value) -> + {ok, {Value, Remaining}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 146). +?DOC(false). +-spec encode_sequence(bitstring()) -> {ok, bitstring()} | {error, nil}. +encode_sequence(Content) -> + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#30>>, Len_bytes, Content])} + end + ). + +-file("src/kryptos/internal/der.gleam", 152). +?DOC(false). +-spec parse_sequence(bitstring()) -> {ok, {bitstring(), bitstring()}} | + {error, nil}. +parse_sequence(Bytes) -> + require_tag( + Bytes, + 16#30, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + Content_size = erlang:byte_size(Content), + gleam@bool:guard( + Content_size < Len, + {error, nil}, + fun() -> + Inner@1 = case gleam_stdlib:bit_array_slice( + Content, + 0, + Len + ) of + {ok, Inner} -> Inner; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_sequence"/utf8>>, + line => 160, + value => _assert_fail, + start => 5061, + 'end' => 5116, + pattern_start => 5072, + pattern_end => 5081}) + end, + Remaining@1 = case gleam_stdlib:bit_array_slice( + Content, + Len, + Content_size - Len + ) of + {ok, Remaining} -> Remaining; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/der"/utf8>>, + function => <<"parse_sequence"/utf8>>, + line => 161, + value => _assert_fail@1, + start => 5119, + 'end' => 5195, + pattern_start => 5130, + pattern_end => 5143}) + end, + {ok, {Inner@1, Remaining@1}} + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/der.gleam", 166). +?DOC(false). +-spec encode_set(bitstring()) -> {ok, bitstring()} | {error, nil}. +encode_set(Content) -> + gleam@result:'try'( + encode_length(erlang:byte_size(Content)), + fun(Len_bytes) -> + {ok, gleam_stdlib:bit_array_concat([<<16#31>>, Len_bytes, Content])} + end + ). + +-file("src/kryptos/internal/der.gleam", 172). +?DOC(false). +-spec parse_set(bitstring()) -> {ok, {bitstring(), bitstring()}} | {error, nil}. +parse_set(Bytes) -> + require_tag( + Bytes, + 16#31, + fun(Rest) -> + gleam@result:'try'( + parse_length(Rest), + fun(_use0) -> + {Len, Content} = _use0, + parse_content(Content, Len) + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hchacha20.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hchacha20.erl new file mode 100644 index 0000000..56af6d7 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hchacha20.erl @@ -0,0 +1,192 @@ +-module(kryptos@internal@hchacha20). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/hchacha20.gleam"). +-export([subkey/2]). +-export_type([state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type state() :: {state, + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. + +-file("src/kryptos/internal/hchacha20.gleam", 157). +?DOC(false). +-spec add_modulo_32(integer(), integer()) -> integer(). +add_modulo_32(A, B) -> + erlang:'band'(A + B, 16#FFFFFFFF). + +-file("src/kryptos/internal/hchacha20.gleam", 161). +?DOC(false). +-spec rotate_left_32(integer(), integer()) -> integer(). +rotate_left_32(X, N) -> + Shifted_left = erlang:'bsl'(X, N), + Shifted_right = erlang:'bsr'(X, 32 - N), + erlang:'band'(erlang:'bor'(Shifted_left, Shifted_right), 16#FFFFFFFF). + +-file("src/kryptos/internal/hchacha20.gleam", 137). +?DOC(false). +-spec quarter_round(integer(), integer(), integer(), integer()) -> {integer(), + integer(), + integer(), + integer()}. +quarter_round(A, B, C, D) -> + A@1 = add_modulo_32(A, B), + D@1 = rotate_left_32(erlang:'bxor'(D, A@1), 16), + C@1 = add_modulo_32(C, D@1), + B@1 = rotate_left_32(erlang:'bxor'(B, C@1), 12), + A@2 = add_modulo_32(A@1, B@1), + D@2 = rotate_left_32(erlang:'bxor'(D@1, A@2), 8), + C@2 = add_modulo_32(C@1, D@2), + B@2 = rotate_left_32(erlang:'bxor'(B@1, C@2), 7), + {A@2, B@2, C@2, D@2}. + +-file("src/kryptos/internal/hchacha20.gleam", 91). +?DOC(false). +-spec perform_rounds(state(), integer()) -> state(). +perform_rounds(State, Remaining) -> + case Remaining =< 0 of + true -> + State; + + false -> + {S0, S4, S8, S12} = quarter_round( + erlang:element(2, State), + erlang:element(6, State), + erlang:element(10, State), + erlang:element(14, State) + ), + {S1, S5, S9, S13} = quarter_round( + erlang:element(3, State), + erlang:element(7, State), + erlang:element(11, State), + erlang:element(15, State) + ), + {S2, S6, S10, S14} = quarter_round( + erlang:element(4, State), + erlang:element(8, State), + erlang:element(12, State), + erlang:element(16, State) + ), + {S3, S7, S11, S15} = quarter_round( + erlang:element(5, State), + erlang:element(9, State), + erlang:element(13, State), + erlang:element(17, State) + ), + {S0@1, S5@1, S10@1, S15@1} = quarter_round(S0, S5, S10, S15), + {S1@1, S6@1, S11@1, S12@1} = quarter_round(S1, S6, S11, S12), + {S2@1, S7@1, S8@1, S13@1} = quarter_round(S2, S7, S8, S13), + {S3@1, S4@1, S9@1, S14@1} = quarter_round(S3, S4, S9, S14), + State@1 = {state, + S0@1, + S1@1, + S2@1, + S3@1, + S4@1, + S5@1, + S6@1, + S7@1, + S8@1, + S9@1, + S10@1, + S11@1, + S12@1, + S13@1, + S14@1, + S15@1}, + perform_rounds(State@1, Remaining - 1) + end. + +-file("src/kryptos/internal/hchacha20.gleam", 11). +?DOC(false). +-spec subkey(bitstring(), bitstring()) -> bitstring(). +subkey(Key, Input) -> + {K0@1, K1@1, K2@1, K3@1, K4@1, K5@1, K6@1, K7@1} = case Key of + <> -> {K0, K1, K2, K3, K4, K5, K6, K7}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/hchacha20"/utf8>>, + function => <<"subkey"/utf8>>, + line => 13, + value => _assert_fail, + start => 513, + 'end' => 753, + pattern_start => 524, + pattern_end => 747}) + end, + {N0@1, N1@1, N2@1, N3@1} = case Input of + <> -> {N0, N1, N2, N3}; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/hchacha20"/utf8>>, + function => <<"subkey"/utf8>>, + line => 25, + value => _assert_fail@1, + start => 806, + 'end' => 940, + pattern_start => 817, + pattern_end => 932}) + end, + State = {state, + 16#61707865, + 16#3320646E, + 16#79622D32, + 16#6B206574, + K0@1, + K1@1, + K2@1, + K3@1, + K4@1, + K5@1, + K6@1, + K7@1, + N0@1, + N1@1, + N2@1, + N3@1}, + State@1 = perform_rounds(State, 10), + <<(erlang:element(2, State@1)):32/little, + (erlang:element(3, State@1)):32/little, + (erlang:element(4, State@1)):32/little, + (erlang:element(5, State@1)):32/little, + (erlang:element(14, State@1)):32/little, + (erlang:element(15, State@1)):32/little, + (erlang:element(16, State@1)):32/little, + (erlang:element(17, State@1)):32/little>>. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hkdf.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hkdf.erl new file mode 100644 index 0000000..1d58f81 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@hkdf.erl @@ -0,0 +1,114 @@ +-module(kryptos@internal@hkdf). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/hkdf.gleam"). +-export([do_derive/5]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/hkdf.gleam", 45). +?DOC(false). +-spec expand_loop( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + integer(), + bitstring(), + integer(), + gleam@bytes_tree:bytes_tree() +) -> {ok, bitstring()} | {error, nil}. +expand_loop(Algorithm, Prk, Info, Remaining, Prev, Counter, Acc) -> + case Remaining =< 0 of + true -> + {ok, erlang:list_to_bitstring(Acc)}; + + false -> + Input = gleam_stdlib:bit_array_concat([Prev, Info, <>]), + gleam@result:'try'( + kryptos@hmac:new(Algorithm, Prk), + fun(Hmac_state) -> + T = begin + _pipe = Hmac_state, + _pipe@1 = crypto:mac_update(_pipe, Input), + crypto:mac_final(_pipe@1) + end, + T_len = erlang:byte_size(T), + case Remaining =< T_len of + true -> + Final_block@1 = case gleam_stdlib:bit_array_slice( + T, + 0, + Remaining + ) of + {ok, Final_block} -> Final_block; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/hkdf"/utf8>>, + function => <<"expand_loop"/utf8>>, + line => 69, + value => _assert_fail, + start => 1843, + 'end' => 1904, + pattern_start => 1854, + pattern_end => 1869}) + end, + _pipe@2 = gleam@bytes_tree:append( + Acc, + Final_block@1 + ), + _pipe@3 = erlang:list_to_bitstring(_pipe@2), + {ok, _pipe@3}; + + false -> + expand_loop( + Algorithm, + Prk, + Info, + Remaining - T_len, + T, + Counter + 1, + gleam@bytes_tree:append(Acc, T) + ) + end + end + ) + end. + +-file("src/kryptos/internal/hkdf.gleam", 36). +?DOC(false). +-spec expand(kryptos@hash:hash_algorithm(), bitstring(), bitstring(), integer()) -> {ok, + bitstring()} | + {error, nil}. +expand(Algorithm, Prk, Info, Length) -> + expand_loop(Algorithm, Prk, Info, Length, <<>>, 1, gleam@bytes_tree:new()). + +-file("src/kryptos/internal/hkdf.gleam", 13). +?DOC(false). +-spec do_derive( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + bitstring(), + integer() +) -> {ok, bitstring()} | {error, nil}. +do_derive(Algorithm, Ikm, Salt, Info, Length) -> + gleam@result:'try'( + kryptos@hmac:new(Algorithm, Salt), + fun(Hmac_state) -> + Prk = begin + _pipe = Hmac_state, + _pipe@1 = crypto:mac_update(_pipe, Ikm), + crypto:mac_final(_pipe@1) + end, + expand(Algorithm, Prk, Info, Length) + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@pbkdf2.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@pbkdf2.erl new file mode 100644 index 0000000..661bb2b --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@pbkdf2.erl @@ -0,0 +1,26 @@ +-module(kryptos@internal@pbkdf2). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/pbkdf2.gleam"). +-export([do_derive/5]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/pbkdf2.gleam", 10). +?DOC(false). +-spec do_derive( + kryptos@hash:hash_algorithm(), + bitstring(), + bitstring(), + integer(), + integer() +) -> {ok, bitstring()} | {error, nil}. +do_derive(Algorithm, Password, Salt, Iterations, Length) -> + kryptos_ffi:pbkdf2_derive(Algorithm, Password, Salt, Iterations, Length). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@rsa_crt.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@rsa_crt.erl new file mode 100644 index 0000000..30c8607 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@rsa_crt.erl @@ -0,0 +1,477 @@ +-module(kryptos@internal@rsa_crt). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/rsa_crt.gleam"). +-export([compute_crt_params/3]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/rsa_crt.gleam", 42). +?DOC(false). +-spec validate_components( + bigi:big_int(), + bigi:big_int(), + bigi:big_int(), + fun(() -> {ok, GBE} | {error, nil}) +) -> {ok, GBE} | {error, nil}. +validate_components(N, E, D, Next) -> + Zero = bigi_ffi:from(0), + One = bigi_ffi:from(1), + case ((bigi_ffi:compare(N, One) =:= gt) andalso (bigi_ffi:compare(E, One) + =:= gt)) + andalso (bigi_ffi:compare(D, Zero) =:= gt) of + true -> + Next(); + + false -> + {error, nil} + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 89). +?DOC(false). +-spec to_bytes_trimmed(bigi:big_int(), integer()) -> {ok, bitstring()} | + {error, nil}. +to_bytes_trimmed(Value, Max_byte_len) -> + gleam@result:'try'( + bigi_ffi:to_bytes(Value, big_endian, unsigned, Max_byte_len), + fun(Bytes) -> + {ok, kryptos@internal@utils:strip_leading_zeros(Bytes)} + end + ). + +-file("src/kryptos/internal/rsa_crt.gleam", 116). +?DOC(false). +-spec compute_byte_length(bigi:big_int(), integer()) -> integer(). +compute_byte_length(Value, Len) -> + Bound@1 = case bigi_ffi:power(bigi_ffi:from(256), bigi_ffi:from(Len)) of + {ok, Bound} -> Bound; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/rsa_crt"/utf8>>, + function => <<"compute_byte_length"/utf8>>, + line => 117, + value => _assert_fail, + start => 3224, + 'end' => 3297, + pattern_start => 3235, + pattern_end => 3244}) + end, + case bigi_ffi:compare(Value, Bound@1) =:= lt of + true -> + Len; + + false -> + compute_byte_length(Value, Len + 1) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 99). +?DOC(false). +-spec to_bytes_minimal(bigi:big_int()) -> {ok, bitstring()} | {error, nil}. +to_bytes_minimal(Value) -> + Zero = bigi_ffi:from(0), + case Value =:= Zero of + true -> + {ok, <<0>>}; + + false -> + Byte_len = compute_byte_length(Value, 1), + gleam@result:'try'( + bigi_ffi:to_bytes(Value, big_endian, unsigned, Byte_len), + fun(Bytes) -> + {ok, kryptos@internal@utils:strip_leading_zeros(Bytes)} + end + ) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 65). +?DOC(false). +-spec mod_pow(bigi:big_int(), bigi:big_int(), bigi:big_int(), integer()) -> {ok, + bigi:big_int()} | + {error, nil}. +mod_pow(Base, Exp, Mod, Byte_len) -> + gleam@result:'try'( + bigi_ffi:to_bytes(Base, big_endian, unsigned, Byte_len), + fun(Base_bytes) -> + gleam@result:'try'( + to_bytes_minimal(Exp), + fun(Exp_bytes) -> + gleam@result:'try'( + bigi_ffi:to_bytes(Mod, big_endian, unsigned, Byte_len), + fun(Mod_bytes) -> + Result_bytes = kryptos_ffi:mod_pow( + Base_bytes, + Exp_bytes, + Mod_bytes + ), + bigi_ffi:from_bytes( + Result_bytes, + big_endian, + unsigned + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/rsa_crt.gleam", 158). +?DOC(false). +-spec factor_out_twos(bigi:big_int(), bigi:big_int(), integer()) -> {integer(), + bigi:big_int()}. +factor_out_twos(K, Two, Count) -> + case bigi_ffi:modulo(K, Two) =:= bigi_ffi:from(0) of + true -> + Next@1 = case bigi:floor_divide(K, Two) of + {ok, Next} -> Next; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/rsa_crt"/utf8>>, + function => <<"factor_out_twos"/utf8>>, + line => 161, + value => _assert_fail, + start => 4442, + 'end' => 4508, + pattern_start => 4453, + pattern_end => 4461}) + end, + factor_out_twos(Next@1, Two, Count + 1); + + false -> + {Count, K} + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 225). +?DOC(false). +-spec gcd(bigi:big_int(), bigi:big_int()) -> bigi:big_int(). +gcd(A, B) -> + Zero = bigi_ffi:from(0), + case B =:= Zero of + true -> + erlang:abs(A); + + false -> + gcd(B, bigi_ffi:modulo(A, B)) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 183). +?DOC(false). +-spec try_factor_loop( + bigi:big_int(), + integer(), + bigi:big_int(), + integer(), + integer() +) -> {ok, {bigi:big_int(), bigi:big_int()}} | {error, nil}. +try_factor_loop(N, T, X, I, Byte_len) -> + case I > T of + true -> + {error, nil}; + + false -> + One = bigi_ffi:from(1), + Two = bigi_ffi:from(2), + N_minus_1 = bigi_ffi:subtract(N, One), + gleam@result:'try'( + mod_pow(X, Two, N, Byte_len), + fun(Y) -> case Y =:= One of + true -> + P = gcd(bigi_ffi:subtract(X, One), N), + case (bigi_ffi:compare(P, One) =:= gt) andalso (bigi_ffi:compare( + P, + N + ) + =:= lt) of + true -> + Q@1 = case bigi:floor_divide(N, P) of + {ok, Q} -> Q; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/rsa_crt"/utf8>>, + function => <<"try_factor_loop"/utf8>>, + line => 206, + value => _assert_fail, + start => 5456, + 'end' => 5517, + pattern_start => 5467, + pattern_end => 5472} + ) + end, + case bigi_ffi:compare(P, Q@1) =:= lt of + true -> + {ok, {P, Q@1}}; + + false -> + {ok, {Q@1, P}} + end; + + false -> + {error, nil} + end; + + false -> + case Y =:= N_minus_1 of + true -> + {error, nil}; + + false -> + try_factor_loop(N, T, Y, I + 1, Byte_len) + end + end end + ) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 168). +?DOC(false). +-spec try_factor(bigi:big_int(), integer(), bigi:big_int(), integer()) -> {ok, + {bigi:big_int(), bigi:big_int()}} | + {error, nil}. +try_factor(N, T, X, Byte_len) -> + One = bigi_ffi:from(1), + N_minus_1 = bigi_ffi:subtract(N, One), + case (X =:= One) orelse (X =:= N_minus_1) of + true -> + {error, nil}; + + false -> + try_factor_loop(N, T, X, 1, Byte_len) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 124). +?DOC(false). +-spec factor_rsa_modulus( + bigi:big_int(), + bigi:big_int(), + bigi:big_int(), + integer(), + integer() +) -> {ok, {bigi:big_int(), bigi:big_int()}} | {error, nil}. +factor_rsa_modulus(N, E, D, Byte_len, Attempts_left) -> + case Attempts_left of + 0 -> + {error, nil}; + + _ -> + One = bigi_ffi:from(1), + Two = bigi_ffi:from(2), + Three = bigi_ffi:from(3), + K = bigi_ffi:subtract(bigi_ffi:multiply(E, D), One), + {T, R} = factor_out_twos(K, Two, 0), + G_bytes = kryptos_ffi:random_bytes(Byte_len), + G_raw@1 = case bigi_ffi:from_bytes(G_bytes, big_endian, unsigned) of + {ok, G_raw} -> G_raw; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/rsa_crt"/utf8>>, + function => <<"factor_rsa_modulus"/utf8>>, + line => 142, + value => _assert_fail, + start => 3871, + 'end' => 3957, + pattern_start => 3882, + pattern_end => 3891}) + end, + N_minus_3 = bigi_ffi:subtract(N, Three), + G = bigi_ffi:add(bigi_ffi:modulo(G_raw@1, N_minus_3), Two), + gleam@result:'try'( + mod_pow(G, R, N, Byte_len), + fun(X) -> case try_factor(N, T, X, Byte_len) of + {ok, {P, Q}} -> + {ok, {P, Q}}; + + {error, nil} -> + factor_rsa_modulus( + N, + E, + D, + Byte_len, + Attempts_left - 1 + ) + end end + ) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 251). +?DOC(false). +-spec extended_gcd_loop( + bigi:big_int(), + bigi:big_int(), + bigi:big_int(), + bigi:big_int(), + bigi:big_int(), + bigi:big_int() +) -> {bigi:big_int(), bigi:big_int(), bigi:big_int()}. +extended_gcd_loop(Old_r, R, Old_s, S, Old_t, T) -> + Zero = bigi_ffi:from(0), + case R =:= Zero of + true -> + {Old_r, Old_s, Old_t}; + + false -> + Q@1 = case bigi:floor_divide(Old_r, R) of + {ok, Q} -> Q; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/rsa_crt"/utf8>>, + function => <<"extended_gcd_loop"/utf8>>, + line => 263, + value => _assert_fail, + start => 6764, + 'end' => 6829, + pattern_start => 6775, + pattern_end => 6780}) + end, + New_r = bigi_ffi:subtract(Old_r, bigi_ffi:multiply(Q@1, R)), + New_s = bigi_ffi:subtract(Old_s, bigi_ffi:multiply(Q@1, S)), + New_t = bigi_ffi:subtract(Old_t, bigi_ffi:multiply(Q@1, T)), + extended_gcd_loop(R, New_r, S, New_s, T, New_t) + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 233). +?DOC(false). +-spec mod_inverse(bigi:big_int(), bigi:big_int()) -> {ok, bigi:big_int()} | + {error, nil}. +mod_inverse(A, Mod) -> + Zero = bigi_ffi:from(0), + One = bigi_ffi:from(1), + {Old_r, Old_s, _} = extended_gcd_loop(A, Mod, One, Zero, Zero, One), + case Old_r =:= One of + true -> + Result = bigi_ffi:modulo(Old_s, Mod), + case bigi_ffi:compare(Result, Zero) =:= lt of + true -> + {ok, bigi_ffi:add(Result, Mod)}; + + false -> + {ok, Result} + end; + + false -> + {error, nil} + end. + +-file("src/kryptos/internal/rsa_crt.gleam", 12). +?DOC(false). +-spec compute_crt_params(bitstring(), bitstring(), bitstring()) -> {ok, + {bitstring(), bitstring(), bitstring(), bitstring(), bitstring()}} | + {error, nil}. +compute_crt_params(N_bytes, E_bytes, D_bytes) -> + gleam@result:'try'( + bigi_ffi:from_bytes(N_bytes, big_endian, unsigned), + fun(N) -> + gleam@result:'try'( + bigi_ffi:from_bytes(E_bytes, big_endian, unsigned), + fun(E) -> + gleam@result:'try'( + bigi_ffi:from_bytes(D_bytes, big_endian, unsigned), + fun(D) -> + validate_components( + N, + E, + D, + fun() -> + Byte_len = erlang:byte_size(N_bytes), + gleam@result:'try'( + factor_rsa_modulus( + N, + E, + D, + Byte_len, + 500 + ), + fun(_use0) -> + {P, Q} = _use0, + One = bigi_ffi:from(1), + Dp = bigi_ffi:modulo( + D, + bigi_ffi:subtract(P, One) + ), + Dq = bigi_ffi:modulo( + D, + bigi_ffi:subtract(Q, One) + ), + gleam@result:'try'( + mod_inverse(Q, P), + fun(Qi) -> + gleam@result:'try'( + to_bytes_trimmed( + P, + Byte_len + ), + fun(P_bytes) -> + gleam@result:'try'( + to_bytes_trimmed( + Q, + Byte_len + ), + fun(Q_bytes) -> + gleam@result:'try'( + to_bytes_trimmed( + Dp, + Byte_len + ), + fun( + Dp_bytes + ) -> + gleam@result:'try'( + to_bytes_trimmed( + Dq, + Byte_len + ), + fun( + Dq_bytes + ) -> + gleam@result:'try'( + to_bytes_trimmed( + Qi, + Byte_len + ), + fun( + Qi_bytes + ) -> + {ok, + {P_bytes, + Q_bytes, + Dp_bytes, + Dq_bytes, + Qi_bytes}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@subtle.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@subtle.erl new file mode 100644 index 0000000..525379e --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@subtle.erl @@ -0,0 +1,20 @@ +-module(kryptos@internal@subtle). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/subtle.gleam"). +-export([constant_time_equal/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/subtle.gleam", 8). +?DOC(false). +-spec constant_time_equal(bitstring(), bitstring()) -> boolean(). +constant_time_equal(A, B) -> + kryptos_ffi:constant_time_equal(A, B). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@utils.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@utils.erl new file mode 100644 index 0000000..537fdce --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@utils.erl @@ -0,0 +1,365 @@ +-module(kryptos@internal@utils). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/utils.gleam"). +-export([count_trailing_zeros/1, strip_leading_zeros/1, strip_trailing_zeros/1, pad_left/2, int_to_padded_string/2, is_ascii/1, chunk_string/2, parse_ip/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/kryptos/internal/utils.gleam", 27). +?DOC(false). +-spec trailing_zeros_in_byte(integer(), integer()) -> integer(). +trailing_zeros_in_byte(Byte, Count) -> + case erlang:'band'(Byte, 1) of + 0 -> + trailing_zeros_in_byte(erlang:'bsr'(Byte, 1), Count + 1); + + _ -> + Count + end. + +-file("src/kryptos/internal/utils.gleam", 14). +?DOC(false). +-spec do_count_trailing_zeros(bitstring(), integer(), integer()) -> integer(). +do_count_trailing_zeros(Bits, Byte_pos, Count) -> + case Byte_pos < 0 of + true -> + Count; + + false -> + Byte@1 = case gleam_stdlib:bit_array_slice(Bits, Byte_pos, 1) of + {ok, <>} -> Byte; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/utils"/utf8>>, + function => <<"do_count_trailing_zeros"/utf8>>, + line => 18, + value => _assert_fail, + start => 468, + 'end' => 528, + pattern_start => 479, + pattern_end => 491}) + end, + case Byte@1 of + 0 -> + do_count_trailing_zeros(Bits, Byte_pos - 1, Count + 8); + + _ -> + Count + trailing_zeros_in_byte(Byte@1, 0) + end + end. + +-file("src/kryptos/internal/utils.gleam", 9). +?DOC(false). +-spec count_trailing_zeros(bitstring()) -> integer(). +count_trailing_zeros(Bits) -> + Size = erlang:byte_size(Bits), + do_count_trailing_zeros(Bits, Size - 1, 0). + +-file("src/kryptos/internal/utils.gleam", 38). +?DOC(false). +-spec strip_leading_zeros(bitstring()) -> bitstring(). +strip_leading_zeros(Bytes) -> + case Bytes of + <<16#00, Rest/bitstring>> -> + case erlang:byte_size(Rest) > 0 of + true -> + strip_leading_zeros(Rest); + + false -> + Bytes + end; + + _ -> + Bytes + end. + +-file("src/kryptos/internal/utils.gleam", 59). +?DOC(false). +-spec strip_trailing_zeros_loop(bitstring(), integer()) -> bitstring(). +strip_trailing_zeros_loop(Data, Len) -> + case Len of + 0 -> + <<>>; + + _ -> + Last_byte@1 = case gleam_stdlib:bit_array_slice(Data, Len - 1, 1) of + {ok, <>} -> Last_byte; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/utils"/utf8>>, + function => <<"strip_trailing_zeros_loop"/utf8>>, + line => 63, + value => _assert_fail, + start => 1719, + 'end' => 1783, + pattern_start => 1730, + pattern_end => 1747}) + end, + case Last_byte@1 of + 0 -> + strip_trailing_zeros_loop(Data, Len - 1); + + _ -> + Result@1 = case gleam_stdlib:bit_array_slice(Data, 0, Len) of + {ok, Result} -> Result; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/utils"/utf8>>, + function => <<"strip_trailing_zeros_loop"/utf8>>, + line => 67, + value => _assert_fail@1, + start => 1886, + 'end' => 1939, + pattern_start => 1897, + pattern_end => 1907}) + end, + Result@1 + end + end. + +-file("src/kryptos/internal/utils.gleam", 54). +?DOC(false). +-spec strip_trailing_zeros(bitstring()) -> bitstring(). +strip_trailing_zeros(Data) -> + Len = erlang:byte_size(Data), + strip_trailing_zeros_loop(Data, Len). + +-file("src/kryptos/internal/utils.gleam", 78). +?DOC(false). +-spec pad_left(bitstring(), integer()) -> bitstring(). +pad_left(Value, Size) -> + Current_size = erlang:byte_size(Value), + case Current_size >= Size of + true -> + Value; + + false -> + Padding_size = Size - Current_size, + Padding = begin + _pipe = gleam@list:repeat(<<0>>, Padding_size), + gleam_stdlib:bit_array_concat(_pipe) + end, + gleam_stdlib:bit_array_concat([Padding, Value]) + end. + +-file("src/kryptos/internal/utils.gleam", 93). +?DOC(false). +-spec int_to_padded_string(integer(), integer()) -> binary(). +int_to_padded_string(N, Width) -> + S = erlang:integer_to_binary(N), + Padding = gleam@string:repeat( + <<"0"/utf8>>, + gleam@int:max(0, Width - string:length(S)) + ), + <>. + +-file("src/kryptos/internal/utils.gleam", 100). +?DOC(false). +-spec is_ascii(binary()) -> boolean(). +is_ascii(S) -> + _pipe = S, + _pipe@1 = gleam@string:to_utf_codepoints(_pipe), + gleam@list:all(_pipe@1, fun(Cp) -> gleam_stdlib:identity(Cp) =< 127 end). + +-file("src/kryptos/internal/utils.gleam", 108). +?DOC(false). +-spec chunk_string(binary(), integer()) -> list(binary()). +chunk_string(S, Size) -> + case Size > 0 of + false -> + []; + + true -> + case string:length(S) =< Size of + true -> + [S]; + + false -> + Chunk = gleam@string:slice(S, 0, Size), + Rest = gleam@string:slice(S, Size, string:length(S) - Size), + [Chunk | chunk_string(Rest, Size)] + end + end. + +-file("src/kryptos/internal/utils.gleam", 138). +?DOC(false). +-spec parse_ipv4_octet(binary()) -> {ok, integer()} | {error, nil}. +parse_ipv4_octet(S) -> + gleam@result:'try'( + gleam_stdlib:parse_int(S), + fun(N) -> + gleam@bool:guard( + (N < 0) orelse (N > 255), + {error, nil}, + fun() -> {ok, N} end + ) + end + ). + +-file("src/kryptos/internal/utils.gleam", 131). +?DOC(false). +-spec parse_ipv4(binary()) -> {ok, bitstring()} | {error, nil}. +parse_ipv4(Ip) -> + Parts = gleam@string:split(Ip, <<"."/utf8>>), + gleam@bool:guard( + erlang:length(Parts) /= 4, + {error, nil}, + fun() -> + gleam@result:'try'( + gleam@list:try_map(Parts, fun parse_ipv4_octet/1), + fun(Bytes) -> + {ok, + gleam_stdlib:bit_array_concat( + gleam@list:map(Bytes, fun(B) -> <> end) + )} + end + ) + end + ). + +-file("src/kryptos/internal/utils.gleam", 192). +?DOC(false). +-spec parse_ipv6_word(binary()) -> {ok, integer()} | {error, nil}. +parse_ipv6_word(S) -> + gleam@result:'try'( + gleam@int:base_parse(S, 16), + fun(N) -> + gleam@bool:guard( + (N < 0) orelse (N > 16#ffff), + {error, nil}, + fun() -> {ok, N} end + ) + end + ). + +-file("src/kryptos/internal/utils.gleam", 160). +?DOC(false). +-spec parse_ipv6_full(binary()) -> {ok, bitstring()} | {error, nil}. +parse_ipv6_full(Ip) -> + Parts = gleam@string:split(Ip, <<":"/utf8>>), + gleam@bool:guard( + erlang:length(Parts) /= 8, + {error, nil}, + fun() -> + gleam@result:'try'( + gleam@list:try_map(Parts, fun parse_ipv6_word/1), + fun(Words) -> + {ok, + gleam_stdlib:bit_array_concat( + gleam@list:map(Words, fun(W) -> <> end) + )} + end + ) + end + ). + +-file("src/kryptos/internal/utils.gleam", 167). +?DOC(false). +-spec parse_ipv6_compressed(binary()) -> {ok, bitstring()} | {error, nil}. +parse_ipv6_compressed(Ip) -> + gleam@result:'try'(case gleam@string:split(Ip, <<"::"/utf8>>) of + [L, R] -> + {ok, {L, R}}; + + _ -> + {error, nil} + end, fun(_use0) -> + {Left, Right} = _use0, + Left_parts = case Left of + <<""/utf8>> -> + []; + + _ -> + gleam@string:split(Left, <<":"/utf8>>) + end, + Right_parts = case Right of + <<""/utf8>> -> + []; + + _ -> + gleam@string:split(Right, <<":"/utf8>>) + end, + Total = erlang:length(Left_parts) + erlang:length(Right_parts), + gleam@bool:guard( + Total > 7, + {error, nil}, + fun() -> + Zeros = gleam@list:repeat(0, 8 - Total), + gleam@result:'try'( + gleam@list:try_map(Left_parts, fun parse_ipv6_word/1), + fun(Left_words) -> + gleam@result:'try'( + gleam@list:try_map( + Right_parts, + fun parse_ipv6_word/1 + ), + fun(Right_words) -> + All_words = lists:append( + [Left_words, Zeros, Right_words] + ), + {ok, + gleam_stdlib:bit_array_concat( + gleam@list:map( + All_words, + fun(W) -> <> end + ) + )} + end + ) + end + ) + end + ) + end). + +-file("src/kryptos/internal/utils.gleam", 144). +?DOC(false). +-spec parse_ipv6(binary()) -> {ok, bitstring()} | {error, nil}. +parse_ipv6(Ip) -> + Ip@1 = case gleam_stdlib:string_starts_with(Ip, <<"::"/utf8>>) of + true -> + <<"0"/utf8, Ip/binary>>; + + false -> + Ip + end, + Ip@2 = case gleam_stdlib:string_ends_with(Ip@1, <<"::"/utf8>>) of + true -> + <>; + + false -> + Ip@1 + end, + case gleam_stdlib:contains_string(Ip@2, <<"::"/utf8>>) of + true -> + parse_ipv6_compressed(Ip@2); + + false -> + parse_ipv6_full(Ip@2) + end. + +-file("src/kryptos/internal/utils.gleam", 124). +?DOC(false). +-spec parse_ip(binary()) -> {ok, bitstring()} | {error, nil}. +parse_ip(Ip) -> + case gleam_stdlib:contains_string(Ip, <<":"/utf8>>) of + true -> + parse_ipv6(Ip); + + false -> + parse_ipv4(Ip) + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@x509.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@x509.erl new file mode 100644 index 0000000..fb59046 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@internal@x509.erl @@ -0,0 +1,994 @@ +-module(kryptos@internal@x509). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/internal/x509.gleam"). +-export([verify_signature/4, parse_sequence_with_header/1, parse_signature_algorithm/1, parse_name/1, encode_name/1, parse_public_key/1, parse_general_name/2, parse_general_names/3, encode_general_name/1, parse_single_extension/1, parse_san_extension/2, encode_algorithm_identifier/1, extract_spki_public_key_bytes/1, decode_pem_all/3, decode_pem/3, encode_pem/3, encode_san_extension/2, rsa_sig_alg_info/1, ecdsa_sig_alg_info/1, eddsa_sig_alg_info/1]). +-export_type([pem_error/0, sig_alg_info/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type pem_error() :: pem_not_found | pem_malformed. + +-type sig_alg_info() :: {sig_alg_info, kryptos@x509:oid(), boolean()}. + +-file("src/kryptos/internal/x509.gleam", 103). +?DOC(false). +-spec sig_alg_to_hash(kryptos@x509:signature_algorithm()) -> {ok, + kryptos@hash:hash_algorithm()} | + {error, nil}. +sig_alg_to_hash(Sig_alg) -> + case Sig_alg of + ecdsa_sha1 -> + {ok, sha1}; + + rsa_sha1 -> + {ok, sha1}; + + ecdsa_sha256 -> + {ok, sha256}; + + rsa_sha256 -> + {ok, sha256}; + + ecdsa_sha384 -> + {ok, sha384}; + + rsa_sha384 -> + {ok, sha384}; + + ecdsa_sha512 -> + {ok, sha512}; + + rsa_sha512 -> + {ok, sha512}; + + ed25519 -> + {error, nil}; + + ed448 -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 79). +?DOC(false). +-spec verify_signature( + kryptos@x509:public_key(), + bitstring(), + bitstring(), + kryptos@x509:signature_algorithm() +) -> boolean(). +verify_signature(Public_key, Data, Signature, Signature_algorithm) -> + case {Public_key, Signature_algorithm} of + {{ec_public_key, Key}, Sig_alg} -> + case sig_alg_to_hash(Sig_alg) of + {ok, Hash_alg} -> + kryptos_ffi:ecdsa_verify(Key, Data, Signature, Hash_alg); + + {error, nil} -> + false + end; + + {{rsa_public_key, Key@1}, Sig_alg@1} -> + case sig_alg_to_hash(Sig_alg@1) of + {ok, Hash_alg@1} -> + kryptos_ffi:rsa_verify( + Key@1, + Data, + Signature, + Hash_alg@1, + pkcs1v15 + ); + + {error, nil} -> + false + end; + + {{ed_public_key, Key@2}, ed25519} -> + kryptos_ffi:eddsa_verify(Key@2, Data, Signature); + + {{ed_public_key, Key@2}, ed448} -> + kryptos_ffi:eddsa_verify(Key@2, Data, Signature); + + {{ed_public_key, _}, _} -> + false; + + {{xdh_public_key, _}, _} -> + false + end. + +-file("src/kryptos/internal/x509.gleam", 119). +?DOC(false). +-spec parse_sequence_with_header(bitstring()) -> {ok, + {bitstring(), bitstring()}} | + {error, nil}. +parse_sequence_with_header(Bytes) -> + case Bytes of + <<16#30, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Inner, Remaining} = _use0, + Inner_len = erlang:byte_size(Inner), + Header_len = (erlang:byte_size(Bytes) - erlang:byte_size( + Remaining + )) + - Inner_len, + Total_len = Header_len + Inner_len, + Full_seq@1 = case gleam_stdlib:bit_array_slice( + Bytes, + 0, + Total_len + ) of + {ok, Full_seq} -> Full_seq; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/internal/x509"/utf8>>, + function => <<"parse_sequence_with_header"/utf8>>, + line => 130, + value => _assert_fail, + start => 4174, + 'end' => 4236, + pattern_start => 4185, + pattern_end => 4197}) + end, + {ok, {Full_seq@1, Remaining}} + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 141). +?DOC(false). +-spec parse_signature_algorithm(bitstring()) -> {ok, + kryptos@x509:signature_algorithm()} | + {error, kryptos@x509:oid()}. +parse_signature_algorithm(Bytes) -> + case kryptos@internal@der:parse_oid(Bytes) of + {ok, {Oid_components, _}} -> + case Oid_components of + [1, 2, 840, 113549, 1, 1, 5] -> + {ok, rsa_sha1}; + + [1, 2, 840, 113549, 1, 1, 11] -> + {ok, rsa_sha256}; + + [1, 2, 840, 113549, 1, 1, 12] -> + {ok, rsa_sha384}; + + [1, 2, 840, 113549, 1, 1, 13] -> + {ok, rsa_sha512}; + + [1, 2, 840, 10045, 4, 1] -> + {ok, ecdsa_sha1}; + + [1, 2, 840, 10045, 4, 3, 2] -> + {ok, ecdsa_sha256}; + + [1, 2, 840, 10045, 4, 3, 3] -> + {ok, ecdsa_sha384}; + + [1, 2, 840, 10045, 4, 3, 4] -> + {ok, ecdsa_sha512}; + + [1, 3, 101, 112] -> + {ok, ed25519}; + + [1, 3, 101, 113] -> + {ok, ed448}; + + _ -> + {error, {oid, Oid_components}} + end; + + {error, _} -> + {error, {oid, []}} + end. + +-file("src/kryptos/internal/x509.gleam", 206). +?DOC(false). +-spec parse_attribute_value(bitstring()) -> {ok, + {kryptos@x509:attribute_value(), bitstring()}} | + {error, nil}. +parse_attribute_value(Bytes) -> + case Bytes of + <<16#0c, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_utf8_string(Bytes), + fun(_use0) -> + {S, Rest} = _use0, + {ok, {kryptos@x509:utf8_string(S), Rest}} + end + ); + + <<16#13, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_printable_string(Bytes), + fun(_use0@1) -> + {S@1, Rest@1} = _use0@1, + {ok, {kryptos@x509:printable_string(S@1), Rest@1}} + end + ); + + <<16#14, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_teletex_string(Bytes), + fun(_use0@2) -> + {S@2, Rest@2} = _use0@2, + {ok, {kryptos@x509:utf8_string(S@2), Rest@2}} + end + ); + + <<16#16, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_ia5_string(Bytes), + fun(_use0@3) -> + {S@3, Rest@3} = _use0@3, + {ok, {kryptos@x509:ia5_string(S@3), Rest@3}} + end + ); + + <<16#1c, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_universal_string(Bytes), + fun(_use0@4) -> + {S@4, Rest@4} = _use0@4, + {ok, {kryptos@x509:utf8_string(S@4), Rest@4}} + end + ); + + <<16#1e, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_bmp_string(Bytes), + fun(_use0@5) -> + {S@5, Rest@5} = _use0@5, + {ok, {kryptos@x509:utf8_string(S@5), Rest@5}} + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 189). +?DOC(false). +-spec parse_rdn_attributes( + bitstring(), + list({kryptos@x509:oid(), kryptos@x509:attribute_value()}) +) -> {ok, list({kryptos@x509:oid(), kryptos@x509:attribute_value()})} | + {error, nil}. +parse_rdn_attributes(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, lists:reverse(Acc)}; + + _ -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Attr_bytes, Rest} = _use0, + gleam@result:'try'( + kryptos@internal@der:parse_oid(Attr_bytes), + fun(_use0@1) -> + {Oid_components, After_oid} = _use0@1, + gleam@result:'try'( + parse_attribute_value(After_oid), + fun(_use0@2) -> + {Value, Remaining} = _use0@2, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> + parse_rdn_attributes( + Rest, + [{{oid, Oid_components}, Value} | + Acc] + ) + end + ) + end + ) + end + ) + end + ) + end. + +-file("src/kryptos/internal/x509.gleam", 172). +?DOC(false). +-spec parse_rdns(bitstring(), list(kryptos@x509:rdn())) -> {ok, + list(kryptos@x509:rdn())} | + {error, nil}. +parse_rdns(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, lists:reverse(Acc)}; + + _ -> + gleam@result:'try'( + kryptos@internal@der:parse_set(Bytes), + fun(_use0) -> + {Rdn_bytes, Rest} = _use0, + _pipe = parse_rdn_attributes(Rdn_bytes, []), + gleam@result:'try'( + _pipe, + fun(Attributes) -> + parse_rdns(Rest, [{rdn, Attributes} | Acc]) + end + ) + end + ) + end. + +-file("src/kryptos/internal/x509.gleam", 167). +?DOC(false). +-spec parse_name(bitstring()) -> {ok, kryptos@x509:name()} | {error, nil}. +parse_name(Bytes) -> + _pipe = parse_rdns(Bytes, []), + gleam@result:map(_pipe, fun(Field@0) -> {name, Field@0} end). + +-file("src/kryptos/internal/x509.gleam", 264). +?DOC(false). +-spec encode_attribute_type_and_value( + {kryptos@x509:oid(), kryptos@x509:attribute_value()} +) -> {ok, bitstring()} | {error, nil}. +encode_attribute_type_and_value(Attr) -> + {{oid, Oid_components}, Value} = Attr, + gleam@result:'try'( + kryptos@x509:encode_attribute_value(Value), + fun(Encoded_value) -> + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Oid_encoded, Encoded_value] + ) + ) + end + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 254). +?DOC(false). +-spec encode_rdn(kryptos@x509:rdn()) -> {ok, bitstring()} | {error, nil}. +encode_rdn(Rdn) -> + {rdn, Attributes} = Rdn, + gleam@result:'try'( + gleam@list:try_map(Attributes, fun encode_attribute_type_and_value/1), + fun(Encoded_attrs) -> + Sorted_attrs = gleam@list:sort( + Encoded_attrs, + fun gleam@bit_array:compare/2 + ), + kryptos@internal@der:encode_set( + gleam_stdlib:bit_array_concat(Sorted_attrs) + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 245). +?DOC(false). +-spec encode_name(kryptos@x509:name()) -> {ok, bitstring()} | {error, nil}. +encode_name(Name) -> + {name, Rdns} = Name, + _pipe = gleam@list:try_map(Rdns, fun encode_rdn/1), + gleam@result:'try'( + _pipe, + fun(Encoded_rdns) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat(Encoded_rdns) + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 297). +?DOC(false). +-spec dispatch_public_key_parse(list(integer()), bitstring()) -> {ok, + kryptos@x509:public_key()} | + {error, kryptos@x509:oid()}. +dispatch_public_key_parse(Alg_oid, Spki_bytes) -> + case Alg_oid of + [1, 2, 840, 10045, 2, 1] -> + _pipe = kryptos_ffi:ec_import_public_key_der(Spki_bytes), + _pipe@1 = gleam@result:map( + _pipe, + fun(Field@0) -> {ec_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@1, {oid, Alg_oid}); + + [1, 2, 840, 113549, 1, 1, 1] -> + _pipe@2 = kryptos_ffi:rsa_import_public_key_der(Spki_bytes, spki), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(Field@0) -> {rsa_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@3, {oid, Alg_oid}); + + [1, 3, 101, 110] -> + _pipe@4 = kryptos_ffi:xdh_import_public_key_der(Spki_bytes), + _pipe@5 = gleam@result:map( + _pipe@4, + fun(Field@0) -> {xdh_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@5, {oid, Alg_oid}); + + [1, 3, 101, 111] -> + _pipe@4 = kryptos_ffi:xdh_import_public_key_der(Spki_bytes), + _pipe@5 = gleam@result:map( + _pipe@4, + fun(Field@0) -> {xdh_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@5, {oid, Alg_oid}); + + [1, 3, 101, 112] -> + _pipe@6 = kryptos_ffi:eddsa_import_public_key_der(Spki_bytes), + _pipe@7 = gleam@result:map( + _pipe@6, + fun(Field@0) -> {ed_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@7, {oid, Alg_oid}); + + [1, 3, 101, 113] -> + _pipe@6 = kryptos_ffi:eddsa_import_public_key_der(Spki_bytes), + _pipe@7 = gleam@result:map( + _pipe@6, + fun(Field@0) -> {ed_public_key, Field@0} end + ), + gleam@result:replace_error(_pipe@7, {oid, Alg_oid}); + + _ -> + {error, {oid, Alg_oid}} + end. + +-file("src/kryptos/internal/x509.gleam", 277). +?DOC(false). +-spec parse_public_key(bitstring()) -> {ok, kryptos@x509:public_key()} | + {error, kryptos@x509:oid()}. +parse_public_key(Spki_bytes) -> + Result = begin + _pipe = begin + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Spki_bytes), + fun(_use0) -> + {Spki_content, _} = _use0, + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Spki_content), + fun(_use0@1) -> + {Alg_id_bytes, After_alg} = _use0@1, + gleam@result:'try'( + kryptos@internal@der:parse_oid(Alg_id_bytes), + fun(_use0@2) -> + {Alg_oid, _} = _use0@2, + gleam@result:'try'( + kryptos@internal@der:parse_bit_string( + After_alg + ), + fun(_) -> {ok, Alg_oid} end + ) + end + ) + end + ) + end + ) + end, + gleam@result:replace_error(_pipe, {oid, []}) + end, + _pipe@1 = Result, + gleam@result:'try'( + _pipe@1, + fun(_capture) -> dispatch_public_key_parse(_capture, Spki_bytes) end + ). + +-file("src/kryptos/internal/x509.gleam", 350). +?DOC(false). +-spec parse_general_name(bitstring(), boolean()) -> {ok, + {kryptos@x509:subject_alt_name(), bitstring()}} | + {error, nil}. +parse_general_name(Bytes, Is_critical) -> + gleam@result:'try'( + kryptos@internal@der:parse_tlv(Bytes), + fun(_use0) -> + {Tag, Value, Rest} = _use0, + case Tag of + 16#a0 -> + gleam@result:'try'( + kryptos@internal@der:parse_oid(Value), + fun(_use0@1) -> + {Oid_components, After_oid} = _use0@1, + gleam@result:'try'( + kryptos@internal@der:parse_context_tag( + After_oid, + 0 + ), + fun(_use0@2) -> + {Other_value, _} = _use0@2, + {ok, + {{other_name, + {oid, Oid_components}, + Other_value}, + Rest}} + end + ) + end + ); + + 16#81 -> + _pipe = gleam@bit_array:to_string(Value), + gleam@result:map(_pipe, fun(S) -> {{email, S}, Rest} end); + + 16#82 -> + _pipe@1 = gleam@bit_array:to_string(Value), + gleam@result:map( + _pipe@1, + fun(S@1) -> {{dns_name, S@1}, Rest} end + ); + + 16#a4 -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Value), + fun(_use0@3) -> + {Name_content, Remaining} = _use0@3, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> + gleam@result:'try'( + parse_name(Name_content), + fun(Name) -> + {ok, {{directory_name, Name}, Rest}} + end + ) + end + ) + end + ); + + 16#86 -> + _pipe@2 = gleam@bit_array:to_string(Value), + gleam@result:map( + _pipe@2, + fun(S@2) -> {{uri, S@2}, Rest} end + ); + + 16#87 -> + {ok, {{ip_address, Value}, Rest}}; + + 16#88 -> + gleam@result:'try'( + kryptos@internal@der:decode_oid_components(Value), + fun(Oid_components@1) -> + {ok, + {{registered_id, {oid, Oid_components@1}}, Rest}} + end + ); + + _ -> + case Is_critical of + true -> + {error, nil}; + + false -> + {ok, {{unknown, Tag, Value}, Rest}} + end + end + end + ). + +-file("src/kryptos/internal/x509.gleam", 331). +?DOC(false). +-spec parse_general_names( + bitstring(), + list(kryptos@x509:subject_alt_name()), + boolean() +) -> {ok, list(kryptos@x509:subject_alt_name())} | {error, nil}. +parse_general_names(Bytes, Acc, Is_critical) -> + case Bytes of + <<>> -> + {ok, lists:reverse(Acc)}; + + _ -> + gleam@result:'try'( + parse_general_name(Bytes, Is_critical), + fun(_use0) -> + {San, Rest} = _use0, + parse_general_names(Rest, [San | Acc], Is_critical) + end + ) + end. + +-file("src/kryptos/internal/x509.gleam", 407). +?DOC(false). +-spec encode_general_name(kryptos@x509:subject_alt_name()) -> {ok, bitstring()} | + {error, nil}. +encode_general_name(San) -> + case San of + {dns_name, Name} -> + kryptos@internal@der:encode_context_primitive_tag( + 2, + gleam_stdlib:identity(Name) + ); + + {email, Email} -> + kryptos@internal@der:encode_context_primitive_tag( + 1, + gleam_stdlib:identity(Email) + ); + + {ip_address, Ip} -> + kryptos@internal@der:encode_context_primitive_tag(7, Ip); + + {uri, Uri} -> + kryptos@internal@der:encode_context_primitive_tag( + 6, + gleam_stdlib:identity(Uri) + ); + + {directory_name, Name@1} -> + gleam@result:'try'( + encode_name(Name@1), + fun(Encoded_name) -> + kryptos@internal@der:encode_context_tag(4, Encoded_name) + end + ); + + {registered_id, {oid, Components}} -> + gleam@result:'try'( + kryptos@internal@der:encode_oid(Components), + fun(Oid_encoded) -> + gleam@result:'try'( + kryptos@internal@der:parse_tlv(Oid_encoded), + fun(_use0) -> + {_, Oid_content, _} = _use0, + kryptos@internal@der:encode_context_primitive_tag( + 8, + Oid_content + ) + end + ) + end + ); + + {other_name, {oid, Oid_components}, Value} -> + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded@1) -> + gleam@result:'try'( + kryptos@internal@der:encode_context_tag(0, Value), + fun(Value_tagged) -> + Content = gleam_stdlib:bit_array_concat( + [Oid_encoded@1, Value_tagged] + ), + kryptos@internal@der:encode_context_tag(0, Content) + end + ) + end + ); + + {unknown, _, _} -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 440). +?DOC(false). +-spec parse_single_extension(bitstring()) -> {ok, + {kryptos@x509:oid(), boolean(), bitstring()}} | + {error, nil}. +parse_single_extension(Bytes) -> + gleam@result:'try'( + kryptos@internal@der:parse_oid(Bytes), + fun(_use0) -> + {Oid_components, After_oid} = _use0, + {Is_critical, After_critical} = case After_oid of + <<16#01, 16#01, Critical_byte, Rest/bitstring>> -> + {Critical_byte /= 0, Rest}; + + Other -> + {false, Other} + end, + gleam@result:'try'( + kryptos@internal@der:parse_octet_string(After_critical), + fun(_use0@1) -> + {Value, Remaining} = _use0@1, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> + {ok, {{oid, Oid_components}, Is_critical, Value}} + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 458). +?DOC(false). +-spec parse_san_extension(bitstring(), boolean()) -> {ok, + list(kryptos@x509:subject_alt_name())} | + {error, nil}. +parse_san_extension(Bytes, Is_critical) -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {San_content, _} = _use0, + parse_general_names(San_content, [], Is_critical) + end + ). + +-file("src/kryptos/internal/x509.gleam", 470). +?DOC(false). +-spec encode_algorithm_identifier(sig_alg_info()) -> {ok, bitstring()} | + {error, nil}. +encode_algorithm_identifier(Sig_alg) -> + {sig_alg_info, Oid, Include_null_params} = Sig_alg, + {oid, Components} = Oid, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Components), + fun(Oid_encoded) -> case Include_null_params of + true -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Oid_encoded, <<16#05, 16#00>>] + ) + ); + + false -> + kryptos@internal@der:encode_sequence(Oid_encoded) + end end + ). + +-file("src/kryptos/internal/x509.gleam", 484). +?DOC(false). +-spec extract_spki_public_key_bytes(bitstring()) -> {ok, bitstring()} | + {error, nil}. +extract_spki_public_key_bytes(Spki) -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Spki), + fun(_use0) -> + {Spki_content, _} = _use0, + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Spki_content), + fun(_use0@1) -> + {_, After_alg} = _use0@1, + gleam@result:'try'( + kryptos@internal@der:parse_bit_string(After_alg), + fun(_use0@2) -> + {Pub_key_bytes, _} = _use0@2, + {ok, Pub_key_bytes} + end + ) + end + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 538). +?DOC(false). +-spec extract_pem_body( + list(binary()), + boolean(), + list(binary()), + binary(), + binary() +) -> {ok, {list(binary()), list(binary())}} | {error, pem_error()}. +extract_pem_body(Lines, In_body, Acc, Begin_marker, End_marker) -> + case {Lines, In_body} of + {[], false} -> + {error, pem_not_found}; + + {[], true} -> + {error, pem_malformed}; + + {[Line | Rest], false} -> + case gleam_stdlib:string_starts_with(Line, Begin_marker) of + true -> + extract_pem_body(Rest, true, Acc, Begin_marker, End_marker); + + false -> + extract_pem_body(Rest, false, Acc, Begin_marker, End_marker) + end; + + {[Line@1 | Rest@1], true} -> + case gleam_stdlib:string_starts_with(Line@1, End_marker) of + true -> + {ok, {lists:reverse(Acc), Rest@1}}; + + false -> + extract_pem_body( + Rest@1, + true, + [Line@1 | Acc], + Begin_marker, + End_marker + ) + end + end. + +-file("src/kryptos/internal/x509.gleam", 524). +?DOC(false). +-spec extract_all_pem_bodies( + list(binary()), + binary(), + binary(), + list(list(binary())) +) -> {ok, list(list(binary()))} | {error, nil}. +extract_all_pem_bodies(Lines, Begin_marker, End_marker, Acc) -> + case extract_pem_body(Lines, false, [], Begin_marker, End_marker) of + {error, pem_not_found} -> + {ok, lists:reverse(Acc)}; + + {error, pem_malformed} -> + {error, nil}; + + {ok, {Body, Remaining}} -> + extract_all_pem_bodies( + Remaining, + Begin_marker, + End_marker, + [Body | Acc] + ) + end. + +-file("src/kryptos/internal/x509.gleam", 508). +?DOC(false). +-spec decode_pem_all(binary(), binary(), binary()) -> {ok, list(bitstring())} | + {error, nil}. +decode_pem_all(Pem, Begin_marker, End_marker) -> + Lines = gleam@string:split(Pem, <<"\n"/utf8>>), + Lines@1 = gleam@list:map(Lines, fun gleam@string:trim/1), + gleam@result:'try'( + extract_all_pem_bodies(Lines@1, Begin_marker, End_marker, []), + fun(Blocks) -> + gleam@list:try_map( + Blocks, + fun(Body_lines) -> + Body = gleam@string:join(Body_lines, <<""/utf8>>), + gleam@bit_array:base64_decode(Body) + end + ) + end + ). + +-file("src/kryptos/internal/x509.gleam", 495). +?DOC(false). +-spec decode_pem(binary(), binary(), binary()) -> {ok, bitstring()} | + {error, nil}. +decode_pem(Pem, Begin_marker, End_marker) -> + _pipe = decode_pem_all(Pem, Begin_marker, End_marker), + gleam@result:'try'(_pipe, fun gleam@list:first/1). + +-file("src/kryptos/internal/x509.gleam", 568). +?DOC(false). +-spec encode_pem(bitstring(), binary(), binary()) -> binary(). +encode_pem(Der, Begin_marker, End_marker) -> + Encoded = gleam_stdlib:base64_encode(Der, true), + Lines = begin + _pipe = kryptos@internal@utils:chunk_string(Encoded, 64), + gleam@list:map(_pipe, fun(Line) -> <> end) + end, + _pipe@1 = gleam@string_tree:new(), + _pipe@2 = gleam@string_tree:append( + _pipe@1, + <> + ), + _pipe@3 = gleam_stdlib:iodata_append(_pipe@2, gleam_stdlib:identity(Lines)), + _pipe@4 = gleam@string_tree:append( + _pipe@3, + <> + ), + unicode:characters_to_binary(_pipe@4). + +-file("src/kryptos/internal/x509.gleam", 590). +?DOC(false). +-spec encode_san_extension(list(kryptos@x509:subject_alt_name()), boolean()) -> {ok, + bitstring()} | + {error, nil}. +encode_san_extension(Sans, Critical) -> + Oid_components = [2, 5, 29, 17], + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> _pipe = Sans, + _pipe@1 = lists:reverse(_pipe), + _pipe@2 = gleam@list:try_map(_pipe@1, fun encode_general_name/1), + _pipe@3 = gleam@result:map( + _pipe@2, + fun gleam_stdlib:bit_array_concat/1 + ), + _pipe@4 = gleam@result:'try'( + _pipe@3, + fun kryptos@internal@der:encode_sequence/1 + ), + _pipe@5 = gleam@result:'try'( + _pipe@4, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@6 = gleam@result:map( + _pipe@5, + fun(Value_octet) -> case Critical of + true -> + gleam_stdlib:bit_array_concat( + [Oid_encoded, + kryptos@internal@der:encode_bool(true), + Value_octet] + ); + + false -> + gleam_stdlib:bit_array_concat( + [Oid_encoded, Value_octet] + ) + end end + ), + gleam@result:'try'( + _pipe@6, + fun kryptos@internal@der:encode_sequence/1 + ) end + ). + +-file("src/kryptos/internal/x509.gleam", 60). +?DOC(false). +-spec rsa_sig_alg_info(kryptos@hash:hash_algorithm()) -> {ok, sig_alg_info()} | + {error, nil}. +rsa_sig_alg_info(Hash) -> + case Hash of + sha1 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 113549, 1, 1, 5]}, true}}; + + sha256 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 113549, 1, 1, 11]}, true}}; + + sha384 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 113549, 1, 1, 12]}, true}}; + + sha512 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 113549, 1, 1, 13]}, true}}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 49). +?DOC(false). +-spec ecdsa_sig_alg_info(kryptos@hash:hash_algorithm()) -> {ok, sig_alg_info()} | + {error, nil}. +ecdsa_sig_alg_info(Hash) -> + case Hash of + sha1 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 10045, 4, 1]}, false}}; + + sha256 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 10045, 4, 3, 2]}, false}}; + + sha384 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 10045, 4, 3, 3]}, false}}; + + sha512 -> + {ok, {sig_alg_info, {oid, [1, 2, 840, 10045, 4, 3, 4]}, false}}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/internal/x509.gleam", 71). +?DOC(false). +-spec eddsa_sig_alg_info(kryptos@eddsa:curve()) -> sig_alg_info(). +eddsa_sig_alg_info(Curve) -> + case Curve of + ed25519 -> + {sig_alg_info, {oid, [1, 3, 101, 112]}, false}; + + ed448 -> + {sig_alg_info, {oid, [1, 3, 101, 113]}, false} + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@rsa.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@rsa.erl new file mode 100644 index 0000000..3739b18 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@rsa.erl @@ -0,0 +1,396 @@ +-module(kryptos@rsa). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/rsa.gleam"). +-export([sign/4, verify/5, encrypt/3, decrypt/3, from_pem/2, from_der/2, to_pem/2, to_der/2, public_key_from_pem/2, public_key_from_der/2, public_key_to_pem/2, public_key_to_der/2, public_key_from_private_key/1, modulus_bits/1, public_key_modulus_bits/1, public_exponent/1, public_key_exponent/1, modulus/1, public_key_modulus/1, public_exponent_bytes/1, public_key_exponent_bytes/1, private_exponent_bytes/1, prime1/1, prime2/1, exponent1/1, exponent2/1, coefficient/1, public_key_from_components/2, from_full_components/8, from_components/3, generate_key_pair/1]). +-export_type([private_key/0, public_key/0, private_key_format/0, public_key_format/0, pss_salt_length/0, sign_padding/0, encrypt_padding/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " RSA (Rivest-Shamir-Adleman) cryptography.\n" + "\n" + " RSA key generation, signing, and encryption.\n" + " RSA keys can be used for both digital signatures and encryption.\n" + "\n" + " ## Key Generation\n" + "\n" + " ```gleam\n" + " import kryptos/rsa\n" + "\n" + " let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048)\n" + " ```\n" + "\n" + " ## Signing (RSA-PSS)\n" + "\n" + " ```gleam\n" + " import kryptos/rsa\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048)\n" + " let message = <<\"hello world\":utf8>>\n" + " let padding = rsa.Pss(rsa.SaltLengthHashLen)\n" + " let signature = rsa.sign(private_key, message, hash.Sha256, padding)\n" + " let valid = rsa.verify(public_key, message, signature, hash.Sha256, padding)\n" + " ```\n" + "\n" + " ## Encryption (RSA-OAEP)\n" + "\n" + " ```gleam\n" + " import kryptos/rsa\n" + " import kryptos/hash\n" + "\n" + " let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048)\n" + " let plaintext = <<\"secret\":utf8>>\n" + " let padding = rsa.Oaep(hash: hash.Sha256, label: <<>>)\n" + " let assert Ok(ciphertext) = rsa.encrypt(public_key, plaintext, padding)\n" + " let assert Ok(decrypted) = rsa.decrypt(private_key, ciphertext, padding)\n" + " ```\n" +). + +-type private_key() :: any(). + +-type public_key() :: any(). + +-type private_key_format() :: pkcs8 | pkcs1. + +-type public_key_format() :: spki | rsa_public_key. + +-type pss_salt_length() :: salt_length_hash_len | + salt_length_max | + {salt_length_explicit, integer()}. + +-type sign_padding() :: pkcs1v15 | {pss, pss_salt_length()}. + +-type encrypt_padding() :: encrypt_pkcs1v15 | + {oaep, kryptos@hash:hash_algorithm(), bitstring()}. + +-file("src/kryptos/rsa.gleam", 130). +?DOC( + " Signs a message using RSA with the specified hash algorithm and padding.\n" + "\n" + " The message is hashed internally using the provided algorithm before signing.\n" +). +-spec sign( + private_key(), + bitstring(), + kryptos@hash:hash_algorithm(), + sign_padding() +) -> bitstring(). +sign(Private_key, Message, Hash, Padding) -> + kryptos_ffi:rsa_sign(Private_key, Message, Hash, Padding). + +-file("src/kryptos/rsa.gleam", 143). +?DOC( + " Verifies an RSA signature against a message.\n" + "\n" + " The same hash algorithm and padding used during signing must be used\n" + " for verification.\n" +). +-spec verify( + public_key(), + bitstring(), + bitstring(), + kryptos@hash:hash_algorithm(), + sign_padding() +) -> boolean(). +verify(Public_key, Message, Signature, Hash, Padding) -> + kryptos_ffi:rsa_verify(Public_key, Message, Signature, Hash, Padding). + +-file("src/kryptos/rsa.gleam", 158). +?DOC( + " Encrypts data using RSA with the specified padding scheme.\n" + "\n" + " **Note**: RSA encryption should only be used for small amounts of data\n" + " (typically symmetric keys). For bulk encryption, use a symmetric cipher\n" + " with a randomly generated key, then encrypt that key with RSA.\n" +). +-spec encrypt(public_key(), bitstring(), encrypt_padding()) -> {ok, bitstring()} | + {error, nil}. +encrypt(Public_key, Plaintext, Padding) -> + kryptos_ffi:rsa_encrypt(Public_key, Plaintext, Padding). + +-file("src/kryptos/rsa.gleam", 167). +?DOC(" Decrypts data using RSA with the specified padding scheme.\n"). +-spec decrypt(private_key(), bitstring(), encrypt_padding()) -> {ok, + bitstring()} | + {error, nil}. +decrypt(Private_key, Ciphertext, Padding) -> + kryptos_ffi:rsa_decrypt(Private_key, Ciphertext, Padding). + +-file("src/kryptos/rsa.gleam", 176). +?DOC(" Imports an RSA private key from PEM-encoded data.\n"). +-spec from_pem(binary(), private_key_format()) -> {ok, + {private_key(), public_key()}} | + {error, nil}. +from_pem(Pem, Format) -> + kryptos_ffi:rsa_import_private_key_pem(Pem, Format). + +-file("src/kryptos/rsa.gleam", 184). +?DOC(" Imports an RSA private key from DER-encoded data.\n"). +-spec from_der(bitstring(), private_key_format()) -> {ok, + {private_key(), public_key()}} | + {error, nil}. +from_der(Der, Format) -> + kryptos_ffi:rsa_import_private_key_der(Der, Format). + +-file("src/kryptos/rsa.gleam", 190). +?DOC(" Exports an RSA private key to PEM format.\n"). +-spec to_pem(private_key(), private_key_format()) -> {ok, binary()} | + {error, nil}. +to_pem(Key, Format) -> + _pipe = kryptos_ffi:rsa_export_private_key_pem(Key, Format), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/rsa.gleam", 201). +?DOC(" Exports an RSA private key to DER format.\n"). +-spec to_der(private_key(), private_key_format()) -> {ok, bitstring()} | + {error, nil}. +to_der(Key, Format) -> + kryptos_ffi:rsa_export_private_key_der(Key, Format). + +-file("src/kryptos/rsa.gleam", 209). +?DOC(" Imports an RSA public key from PEM-encoded data.\n"). +-spec public_key_from_pem(binary(), public_key_format()) -> {ok, public_key()} | + {error, nil}. +public_key_from_pem(Pem, Format) -> + kryptos_ffi:rsa_import_public_key_pem(Pem, Format). + +-file("src/kryptos/rsa.gleam", 217). +?DOC(" Imports an RSA public key from DER-encoded data.\n"). +-spec public_key_from_der(bitstring(), public_key_format()) -> {ok, + public_key()} | + {error, nil}. +public_key_from_der(Der, Format) -> + kryptos_ffi:rsa_import_public_key_der(Der, Format). + +-file("src/kryptos/rsa.gleam", 223). +?DOC(" Exports an RSA public key to PEM format.\n"). +-spec public_key_to_pem(public_key(), public_key_format()) -> {ok, binary()} | + {error, nil}. +public_key_to_pem(Key, Format) -> + _pipe = kryptos_ffi:rsa_export_public_key_pem(Key, Format), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/rsa.gleam", 241). +?DOC(" Exports an RSA public key to DER format.\n"). +-spec public_key_to_der(public_key(), public_key_format()) -> {ok, bitstring()} | + {error, nil}. +public_key_to_der(Key, Format) -> + kryptos_ffi:rsa_export_public_key_der(Key, Format). + +-file("src/kryptos/rsa.gleam", 249). +?DOC(" Derives the public key from an RSA private key.\n"). +-spec public_key_from_private_key(private_key()) -> public_key(). +public_key_from_private_key(Key) -> + kryptos_ffi:rsa_public_key_from_private(Key). + +-file("src/kryptos/rsa.gleam", 254). +?DOC(" Returns the modulus size in bits for an RSA private key.\n"). +-spec modulus_bits(private_key()) -> integer(). +modulus_bits(Key) -> + kryptos_ffi:rsa_private_key_modulus_bits(Key). + +-file("src/kryptos/rsa.gleam", 259). +?DOC(" Returns the modulus size in bits for an RSA public key.\n"). +-spec public_key_modulus_bits(public_key()) -> integer(). +public_key_modulus_bits(Key) -> + kryptos_ffi:rsa_public_key_modulus_bits(Key). + +-file("src/kryptos/rsa.gleam", 264). +?DOC(" Returns the public exponent for an RSA private key.\n"). +-spec public_exponent(private_key()) -> integer(). +public_exponent(Key) -> + kryptos_ffi:rsa_private_key_public_exponent(Key). + +-file("src/kryptos/rsa.gleam", 269). +?DOC(" Returns the public exponent for an RSA public key.\n"). +-spec public_key_exponent(public_key()) -> integer(). +public_key_exponent(Key) -> + kryptos_ffi:rsa_public_key_public_exponent(Key). + +-file("src/kryptos/rsa.gleam", 274). +?DOC(" Returns the modulus (n) as big-endian bytes for an RSA private key.\n"). +-spec modulus(private_key()) -> bitstring(). +modulus(Key) -> + kryptos_ffi:rsa_private_key_modulus(Key). + +-file("src/kryptos/rsa.gleam", 279). +?DOC(" Returns the modulus (n) as big-endian bytes for an RSA public key.\n"). +-spec public_key_modulus(public_key()) -> bitstring(). +public_key_modulus(Key) -> + kryptos_ffi:rsa_public_key_modulus(Key). + +-file("src/kryptos/rsa.gleam", 284). +?DOC(" Returns the public exponent (e) as big-endian bytes for an RSA private key.\n"). +-spec public_exponent_bytes(private_key()) -> bitstring(). +public_exponent_bytes(Key) -> + kryptos_ffi:rsa_private_key_public_exponent_bytes(Key). + +-file("src/kryptos/rsa.gleam", 289). +?DOC(" Returns the public exponent (e) as big-endian bytes for an RSA public key.\n"). +-spec public_key_exponent_bytes(public_key()) -> bitstring(). +public_key_exponent_bytes(Key) -> + kryptos_ffi:rsa_public_key_exponent_bytes(Key). + +-file("src/kryptos/rsa.gleam", 294). +?DOC(" Returns the private exponent (d) as big-endian bytes for an RSA private key.\n"). +-spec private_exponent_bytes(private_key()) -> bitstring(). +private_exponent_bytes(Key) -> + kryptos_ffi:rsa_private_key_private_exponent_bytes(Key). + +-file("src/kryptos/rsa.gleam", 302). +?DOC( + " Returns the first prime factor (p) as big-endian bytes.\n" + "\n" + " The RSA modulus n = p * q. This is part of the CRT (Chinese Remainder\n" + " Theorem) parameters used for efficient RSA operations.\n" +). +-spec prime1(private_key()) -> bitstring(). +prime1(Key) -> + kryptos_ffi:rsa_private_key_prime1(Key). + +-file("src/kryptos/rsa.gleam", 310). +?DOC( + " Returns the second prime factor (q) as big-endian bytes.\n" + "\n" + " The RSA modulus n = p * q. This is part of the CRT (Chinese Remainder\n" + " Theorem) parameters used for efficient RSA operations.\n" +). +-spec prime2(private_key()) -> bitstring(). +prime2(Key) -> + kryptos_ffi:rsa_private_key_prime2(Key). + +-file("src/kryptos/rsa.gleam", 318). +?DOC( + " Returns the first CRT exponent (dp = d mod (p-1)) as big-endian bytes.\n" + "\n" + " This is part of the CRT (Chinese Remainder Theorem) parameters used\n" + " for efficient RSA operations.\n" +). +-spec exponent1(private_key()) -> bitstring(). +exponent1(Key) -> + kryptos_ffi:rsa_private_key_exponent1(Key). + +-file("src/kryptos/rsa.gleam", 326). +?DOC( + " Returns the second CRT exponent (dq = d mod (q-1)) as big-endian bytes.\n" + "\n" + " This is part of the CRT (Chinese Remainder Theorem) parameters used\n" + " for efficient RSA operations.\n" +). +-spec exponent2(private_key()) -> bitstring(). +exponent2(Key) -> + kryptos_ffi:rsa_private_key_exponent2(Key). + +-file("src/kryptos/rsa.gleam", 334). +?DOC( + " Returns the CRT coefficient (qi = q^-1 mod p) as big-endian bytes.\n" + "\n" + " This is part of the CRT (Chinese Remainder Theorem) parameters used\n" + " for efficient RSA operations.\n" +). +-spec coefficient(private_key()) -> bitstring(). +coefficient(Key) -> + kryptos_ffi:rsa_private_key_coefficient(Key). + +-file("src/kryptos/rsa.gleam", 339). +?DOC(" Constructs an RSA public key from its components.\n"). +-spec public_key_from_components(bitstring(), bitstring()) -> {ok, public_key()} | + {error, nil}. +public_key_from_components(N, E) -> + kryptos_ffi:rsa_public_key_from_components(N, E). + +-file("src/kryptos/rsa.gleam", 377). +?DOC(" Constructs an RSA private key from all components including CRT parameters.\n"). +-spec from_full_components( + bitstring(), + bitstring(), + bitstring(), + bitstring(), + bitstring(), + bitstring(), + bitstring(), + bitstring() +) -> {ok, {private_key(), public_key()}} | {error, nil}. +from_full_components(N, E, D, P, Q, Dp, Dq, Qi) -> + kryptos_ffi:rsa_private_key_from_full_components(N, E, D, P, Q, Dp, Dq, Qi). + +-file("src/kryptos/rsa.gleam", 365). +?DOC( + " Constructs an RSA private key from its components.\n" + "\n" + " Creates a private key from the minimal set of components (n, e, d).\n" + " CRT parameters are computed automatically using Miller's algorithm.\n" + "\n" + " Note: This function is not constant-time. The CRT parameter derivation\n" + " involves operations that may leak timing information. This is acceptable\n" + " for key import since the caller already possesses the secret material,\n" + " but avoid calling this in timing-sensitive contexts.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/rsa\n" + "\n" + " let assert Ok(#(private_key, _public_key)) = rsa.generate_key_pair(2048)\n" + " let n = rsa.modulus(private_key)\n" + " let e = rsa.public_exponent_bytes(private_key)\n" + " let d = rsa.private_exponent_bytes(private_key)\n" + " let assert Ok(#(reconstructed, _pub)) = rsa.from_components(n, e, d)\n" + " ```\n" +). +-spec from_components(bitstring(), bitstring(), bitstring()) -> {ok, + {private_key(), public_key()}} | + {error, nil}. +from_components(N, E, D) -> + gleam@result:'try'( + kryptos@internal@rsa_crt:compute_crt_params(N, E, D), + fun(_use0) -> + {P, Q, Dp, Dq, Qi} = _use0, + kryptos_ffi:rsa_private_key_from_full_components( + N, + E, + D, + P, + Q, + Dp, + Dq, + Qi + ) + end + ). + +-file("src/kryptos/rsa.gleam", 114). +?DOC( + " Generates an RSA key pair with the specified key size.\n" + "\n" + " The key size must be >= 1024 bits.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " let assert Ok(#(private_key, public_key)) = rsa.generate_key_pair(2048)\n" + " ```\n" +). +-spec generate_key_pair(integer()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +generate_key_pair(Bits) -> + case Bits >= 1024 of + true -> + {ok, kryptos_ffi:rsa_generate_key_pair(Bits)}; + + false -> + {error, nil} + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@x509.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509.erl new file mode 100644 index 0000000..e8f10c4 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509.erl @@ -0,0 +1,322 @@ +-module(kryptos@x509). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/x509.gleam"). +-export([name/1, encode_attribute_value/1, utf8_string/1, printable_string/1, ia5_string/1, attribute_value_to_string/1, name_to_string/1, cn/1, organization/1, organizational_unit/1, country/1, state/1, locality/1, email_address/1]). +-export_type([oid/0, name/0, rdn/0, attribute_value/0, subject_alt_name/0, extensions/0, public_key/0, signature_algorithm/0, basic_constraints/0, key_usage/0, extended_key_usage/0, validity/0, authority_key_identifier/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " X.509 certificate and CSR types and utilities.\n" + "\n" + " Types and helpers for X.509 public key infrastructure:\n" + "\n" + " - Distinguished Names: Type-safe construction of subject/issuer names\n" + " - Extensions: Subject Alternative Names, Basic Constraints, Key Usage, etc.\n" + " - Public Keys: Unified representation for RSA, ECDSA, EdDSA, and XDH keys\n" + " - Signature Algorithms: Common signing algorithms for certificates and CSRs\n" + " - Certificate Fields: Validity periods, authority key identifiers\n" + "\n" + " See also:\n" + " - `x509/csr` for creating and parsing Certificate Signing Requests\n" + " - `x509/certificate` for parsing and working with X.509 certificates\n" + "\n" + " ## Distinguished Names\n" + "\n" + " Distinguished names (DNs) identify subjects and issuers in X.509 structures.\n" + " They consist of attribute-value pairs like Common Name (CN), Organization (O),\n" + " and Country (C). Helper functions provide type-safe construction with correct\n" + " ASN.1 string encodings:\n" + "\n" + " ```gleam\n" + " import kryptos/x509\n" + "\n" + " let subject = x509.name([\n" + " x509.cn(\"example.com\"),\n" + " x509.organization(\"Acme Inc\"),\n" + " x509.organizational_unit(\"Engineering\"),\n" + " x509.locality(\"San Francisco\"),\n" + " x509.state(\"California\"),\n" + " x509.country(\"US\"),\n" + " ])\n" + " ```\n" + "\n" + " ## Extensions\n" + "\n" + " Subject Alternative Names (SANs) specify additional identities:\n" + "\n" + " ```gleam\n" + " import kryptos/x509.{DnsName, Email, IpAddress, Uri}\n" + "\n" + " let extensions = x509.Extensions([\n" + " DnsName(\"www.example.com\"),\n" + " DnsName(\"api.example.com\"),\n" + " Email(\"admin@example.com\"),\n" + " Uri(\"https://example.com/cps\"),\n" + " ])\n" + " ```\n" +). + +-type oid() :: {oid, list(integer())}. + +-type name() :: {name, list(rdn())}. + +-type rdn() :: {rdn, list({oid(), attribute_value()})}. + +-opaque attribute_value() :: {utf8_string, binary()} | + {printable_string, binary()} | + {ia5_string, binary()}. + +-type subject_alt_name() :: {dns_name, binary()} | + {ip_address, bitstring()} | + {email, binary()} | + {uri, binary()} | + {directory_name, name()} | + {registered_id, oid()} | + {other_name, oid(), bitstring()} | + {unknown, integer(), bitstring()}. + +-type extensions() :: {extensions, list(subject_alt_name())}. + +-type public_key() :: {ec_public_key, kryptos@ec:public_key()} | + {rsa_public_key, kryptos@rsa:public_key()} | + {ed_public_key, kryptos@eddsa:public_key()} | + {xdh_public_key, kryptos@xdh:public_key()}. + +-type signature_algorithm() :: rsa_sha1 | + rsa_sha256 | + rsa_sha384 | + rsa_sha512 | + ecdsa_sha1 | + ecdsa_sha256 | + ecdsa_sha384 | + ecdsa_sha512 | + ed25519 | + ed448. + +-type basic_constraints() :: {basic_constraints, + boolean(), + gleam@option:option(integer())}. + +-type key_usage() :: digital_signature | + non_repudiation | + key_encipherment | + data_encipherment | + key_agreement | + key_cert_sign | + crl_sign | + encipher_only | + decipher_only. + +-type extended_key_usage() :: server_auth | + client_auth | + code_signing | + email_protection | + ocsp_signing. + +-type validity() :: {validity, + gleam@time@timestamp:timestamp(), + gleam@time@timestamp:timestamp()}. + +-type authority_key_identifier() :: {authority_key_identifier, + gleam@option:option(bitstring()), + gleam@option:option(list(subject_alt_name())), + gleam@option:option(bitstring())}. + +-file("src/kryptos/x509.gleam", 224). +?DOC( + " Builds a distinguished name from a list of attribute-value pairs.\n" + "\n" + " Creates a Name with each attribute in its own Relative Distinguished Name\n" + " (RDN). Use helper functions like `cn`, `organization`, `country`, etc.\n" + " to construct the attribute list.\n" +). +-spec name(list({oid(), attribute_value()})) -> name(). +name(Attributes) -> + {name, gleam@list:map(Attributes, fun(Attr) -> {rdn, [Attr]} end)}. + +-file("src/kryptos/x509.gleam", 281). +?DOC(false). +-spec encode_attribute_value(attribute_value()) -> {ok, bitstring()} | + {error, nil}. +encode_attribute_value(Value) -> + case Value of + {utf8_string, S} -> + kryptos@internal@der:encode_utf8_string(S); + + {printable_string, S@1} -> + kryptos@internal@der:encode_printable_string(S@1); + + {ia5_string, S@2} -> + kryptos@internal@der:encode_ia5_string(S@2) + end. + +-file("src/kryptos/x509.gleam", 290). +?DOC(false). +-spec utf8_string(binary()) -> attribute_value(). +utf8_string(Value) -> + {utf8_string, Value}. + +-file("src/kryptos/x509.gleam", 295). +?DOC(false). +-spec printable_string(binary()) -> attribute_value(). +printable_string(Value) -> + {printable_string, Value}. + +-file("src/kryptos/x509.gleam", 300). +?DOC(false). +-spec ia5_string(binary()) -> attribute_value(). +ia5_string(Value) -> + {ia5_string, Value}. + +-file("src/kryptos/x509.gleam", 308). +?DOC( + " Extracts the string value from an AttributeValue.\n" + "\n" + " Returns the underlying string regardless of encoding type\n" + " (UTF8String, PrintableString, or IA5String).\n" +). +-spec attribute_value_to_string(attribute_value()) -> binary(). +attribute_value_to_string(Value) -> + case Value of + {utf8_string, S} -> + S; + + {printable_string, S@1} -> + S@1; + + {ia5_string, S@2} -> + S@2 + end. + +-file("src/kryptos/x509.gleam", 335). +-spec oid_to_abbrev(oid()) -> binary(). +oid_to_abbrev(Oid) -> + case Oid of + {oid, [2, 5, 4, 3]} -> + <<"CN"/utf8>>; + + {oid, [2, 5, 4, 6]} -> + <<"C"/utf8>>; + + {oid, [2, 5, 4, 7]} -> + <<"L"/utf8>>; + + {oid, [2, 5, 4, 8]} -> + <<"ST"/utf8>>; + + {oid, [2, 5, 4, 10]} -> + <<"O"/utf8>>; + + {oid, [2, 5, 4, 11]} -> + <<"OU"/utf8>>; + + {oid, [1, 2, 840, 113549, 1, 9, 1]} -> + <<"emailAddress"/utf8>>; + + {oid, Components} -> + gleam@string:join( + gleam@list:map(Components, fun erlang:integer_to_binary/1), + <<"."/utf8>> + ) + end. + +-file("src/kryptos/x509.gleam", 322). +?DOC( + " Converts a distinguished name to a human-readable string.\n" + "\n" + " Formats the name in OpenSSL style: \"CN=example.com, O=Acme Inc, C=US\"\n" + "\n" + " Known OIDs are displayed with their standard abbreviations (CN, O, OU, C, ST, L).\n" + " Unknown OIDs are displayed in dotted-decimal notation (e.g., \"1.2.3.4=value\").\n" +). +-spec name_to_string(name()) -> binary(). +name_to_string(Name) -> + {name, Rdns} = Name, + _pipe = Rdns, + _pipe@1 = gleam@list:flat_map( + _pipe, + fun(Rdn) -> + {rdn, Attributes} = Rdn, + gleam@list:map( + Attributes, + fun(Attr) -> + {Oid, Value} = Attr, + <<<<(oid_to_abbrev(Oid))/binary, "="/utf8>>/binary, + (attribute_value_to_string(Value))/binary>> + end + ) + end + ), + gleam@string:join(_pipe@1, <<", "/utf8>>). + +-file("src/kryptos/x509.gleam", 233). +?DOC( + " Creates a Common Name (CN) attribute.\n" + "\n" + " The Common Name typically contains the primary identifier for the subject,\n" + " such as a domain name for server certificates or a person's name for\n" + " client certificates.\n" +). +-spec cn(binary()) -> {oid(), attribute_value()}. +cn(Value) -> + {{oid, [2, 5, 4, 3]}, {utf8_string, Value}}. + +-file("src/kryptos/x509.gleam", 238). +?DOC(" Creates an Organization (O) attribute.\n"). +-spec organization(binary()) -> {oid(), attribute_value()}. +organization(Value) -> + {{oid, [2, 5, 4, 10]}, {utf8_string, Value}}. + +-file("src/kryptos/x509.gleam", 243). +?DOC(" Creates an Organizational Unit (OU) attribute.\n"). +-spec organizational_unit(binary()) -> {oid(), attribute_value()}. +organizational_unit(Value) -> + {{oid, [2, 5, 4, 11]}, {utf8_string, Value}}. + +-file("src/kryptos/x509.gleam", 254). +?DOC( + " Creates a Country (C) attribute.\n" + "\n" + " Uses PrintableString encoding as required by X.520.\n" + "\n" + " **Important:** The value must be a two-letter uppercase ISO 3166-1 alpha-2\n" + " country code (e.g., \"US\", \"GB\", \"DE\"). Non-ASCII or incorrectly formatted\n" + " values will produce non-compliant DER that may be rejected by CAs and clients.\n" +). +-spec country(binary()) -> {oid(), attribute_value()}. +country(Value) -> + {{oid, [2, 5, 4, 6]}, {printable_string, Value}}. + +-file("src/kryptos/x509.gleam", 259). +?DOC(" Creates a State or Province (ST) attribute.\n"). +-spec state(binary()) -> {oid(), attribute_value()}. +state(Value) -> + {{oid, [2, 5, 4, 8]}, {utf8_string, Value}}. + +-file("src/kryptos/x509.gleam", 264). +?DOC(" Creates a Locality (L) attribute.\n"). +-spec locality(binary()) -> {oid(), attribute_value()}. +locality(Value) -> + {{oid, [2, 5, 4, 7]}, {utf8_string, Value}}. + +-file("src/kryptos/x509.gleam", 276). +?DOC( + " Creates an Email Address attribute.\n" + "\n" + " Note: emailAddress in the DN is deprecated; prefer using\n" + " Subject Alternative Names via `csr.with_email` instead.\n" + "\n" + " **Important:** The value must contain only ASCII characters.\n" + " Non-ASCII values will produce non-compliant DER that may be rejected\n" + " by CAs and clients.\n" +). +-spec email_address(binary()) -> {oid(), attribute_value()}. +email_address(Value) -> + {{oid, [1, 2, 840, 113549, 1, 9, 1]}, {ia5_string, Value}}. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@certificate.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@certificate.erl new file mode 100644 index 0000000..ffa1ee0 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@certificate.erl @@ -0,0 +1,2918 @@ +-module(kryptos@x509@certificate). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/x509/certificate.gleam"). +-export([new/0, with_subject/2, with_validity/2, with_basic_constraints/3, with_key_usage/2, with_extended_key_usage/2, with_dns_name/2, with_email/2, with_ip/2, with_serial_number/2, with_subject_key_identifier/2, with_authority_key_identifier/2, generate_serial_number/0, to_der/1, version/1, serial_number/1, signature_algorithm/1, issuer/1, validity/1, subject/1, public_key/1, basic_constraints/1, key_usage/1, extended_key_usage/1, subject_alt_names/1, subject_key_identifier/1, authority_key_identifier/1, extensions/1, from_der/1, verify/2, verify_self_signed/1, to_pem/1, from_pem/1, self_signed_with_ecdsa/3, self_signed_with_rsa/3, self_signed_with_eddsa/2]). +-export_type([built/0, parsed/0, certificate_error/0, extensions_acc/0, subject_key_identifier_config/0, authority_key_identifier_config/0, certificate/1, builder/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " X.509 Certificate generation and parsing.\n" + "\n" + " Builder for creating self-signed X.509 certificates.\n" + " CA-signing is not currently supported.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import gleam/option\n" + " import gleam/time/duration\n" + " import gleam/time/timestamp\n" + " import kryptos/ec\n" + " import kryptos/hash\n" + " import kryptos/x509\n" + " import kryptos/x509/certificate\n" + "\n" + " let #(private_key, _) = ec.generate_key_pair(ec.P256)\n" + "\n" + " let subject =\n" + " x509.name([\n" + " x509.cn(\"example.com\"),\n" + " x509.organization(\"Acme Inc\"),\n" + " ])\n" + "\n" + " let now = timestamp.system_time()\n" + " // 86,400 seconds per day per CA/Browser Forum definition\n" + " let one_year_later = timestamp.add(now, duration.seconds(86_400 * 365))\n" + " let validity = x509.Validity(not_before: now, not_after: one_year_later)\n" + "\n" + " let assert Ok(builder) =\n" + " certificate.new()\n" + " |> certificate.with_subject(subject)\n" + " |> certificate.with_validity(validity)\n" + " |> certificate.with_basic_constraints(ca: False, path_len_constraint: option.None)\n" + " |> certificate.with_key_usage(x509.DigitalSignature)\n" + " |> certificate.with_extended_key_usage(x509.ServerAuth)\n" + " |> certificate.with_dns_name(\"example.com\")\n" + "\n" + " let assert Ok(cert) =\n" + " certificate.self_signed_with_ecdsa(builder, private_key, hash.Sha256)\n" + " ```\n" + "\n" + " ## Parsing Certificates\n" + "\n" + " ```gleam\n" + " import kryptos/x509/certificate\n" + "\n" + " let pem = \"-----BEGIN CERTIFICATE-----\n" + " MIIBkTCB+wIJAK...\n" + " -----END CERTIFICATE-----\"\n" + "\n" + " let assert Ok([cert]) = certificate.from_pem(pem)\n" + "\n" + " // Access certificate fields\n" + " let subject = certificate.subject(cert)\n" + " let validity = certificate.validity(cert)\n" + " let public_key = certificate.public_key(cert)\n" + "\n" + " // Verify a self-signed certificate\n" + " let assert Ok(Nil) = certificate.verify_self_signed(cert)\n" + " ```\n" +). + +-type built() :: any(). + +-type parsed() :: any(). + +-type certificate_error() :: parse_error | + {unsupported_algorithm, kryptos@x509:oid()} | + signature_verification_failed | + {unrecognized_critical_extension, kryptos@x509:oid()}. + +-type extensions_acc() :: {extensions_acc, + gleam@option:option(kryptos@x509:basic_constraints()), + list(kryptos@x509:key_usage()), + list(kryptos@x509:extended_key_usage()), + list(kryptos@x509:subject_alt_name()), + gleam@option:option(bitstring()), + gleam@option:option(kryptos@x509:authority_key_identifier()), + list({kryptos@x509:oid(), boolean(), bitstring()}), + gleam@set:set(list(integer()))}. + +-type subject_key_identifier_config() :: ski_auto | {ski_explicit, bitstring()}. + +-type authority_key_identifier_config() :: aki_auto | + {aki_explicit, bitstring()} | + aki_exclude. + +-opaque certificate(HGD) :: {built_certificate, bitstring()} | + {parsed_certificate, + bitstring(), + bitstring(), + bitstring(), + integer(), + bitstring(), + kryptos@x509:signature_algorithm(), + kryptos@x509:name(), + kryptos@x509:validity(), + kryptos@x509:name(), + kryptos@x509:public_key(), + gleam@option:option(kryptos@x509:basic_constraints()), + list(kryptos@x509:key_usage()), + list(kryptos@x509:extended_key_usage()), + list(kryptos@x509:subject_alt_name()), + gleam@option:option(bitstring()), + gleam@option:option(kryptos@x509:authority_key_identifier()), + list({kryptos@x509:oid(), boolean(), bitstring()})} | + {gleam_phantom, HGD}. + +-opaque builder() :: {builder, + kryptos@x509:name(), + gleam@option:option(kryptos@x509:validity()), + gleam@option:option({boolean(), gleam@option:option(integer())}), + list(kryptos@x509:key_usage()), + list(kryptos@x509:extended_key_usage()), + list(kryptos@x509:subject_alt_name()), + gleam@option:option(bitstring()), + gleam@option:option(subject_key_identifier_config()), + authority_key_identifier_config()}. + +-file("src/kryptos/x509/certificate.gleam", 148). +-spec empty_extensions_acc() -> extensions_acc(). +empty_extensions_acc() -> + {extensions_acc, none, [], [], [], none, none, [], gleam@set:new()}. + +-file("src/kryptos/x509/certificate.gleam", 236). +?DOC( + " Creates a new certificate builder with default values.\n" + "\n" + " Use the `with_*` functions to configure the builder, then call\n" + " a signing function to generate the certificate.\n" +). +-spec new() -> builder(). +new() -> + {builder, + kryptos@x509:name([]), + none, + none, + [], + [], + [], + none, + none, + aki_auto}. + +-file("src/kryptos/x509/certificate.gleam", 251). +?DOC(" Sets the distinguished name subject for the certificate.\n"). +-spec with_subject(builder(), kryptos@x509:name()) -> builder(). +with_subject(Builder, Subject) -> + {builder, + Subject, + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 256). +?DOC(" Sets the validity period for the certificate.\n"). +-spec with_validity(builder(), kryptos@x509:validity()) -> builder(). +with_validity(Builder, Validity) -> + {builder, + erlang:element(2, Builder), + {some, Validity}, + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 265). +?DOC( + " Sets the Basic Constraints extension.\n" + "\n" + " This extension indicates whether the certificate is a CA certificate\n" + " and optionally limits the path length of the certification chain.\n" + " Per RFC 5280, path_len_constraint is only meaningful when ca is True.\n" +). +-spec with_basic_constraints( + builder(), + boolean(), + gleam@option:option(integer()) +) -> builder(). +with_basic_constraints(Builder, Ca, Path_len_constraint) -> + Effective_path_len = case Ca of + true -> + Path_len_constraint; + + false -> + none + end, + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + {some, {Ca, Effective_path_len}}, + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 280). +?DOC( + " Adds a Key Usage flag to the certificate.\n" + "\n" + " Multiple usages can be added by chaining calls.\n" +). +-spec with_key_usage(builder(), kryptos@x509:key_usage()) -> builder(). +with_key_usage(Builder, Usage) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + [Usage | erlang:element(5, Builder)], + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 288). +?DOC( + " Adds an Extended Key Usage purpose to the certificate.\n" + "\n" + " EKU narrows allowed purposes beyond Key Usage (e.g., ServerAuth,\n" + " CodeSigning). Multiple usages can be added by chaining calls.\n" +). +-spec with_extended_key_usage(builder(), kryptos@x509:extended_key_usage()) -> builder(). +with_extended_key_usage(Builder, Usage) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + [Usage | erlang:element(6, Builder)], + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 298). +?DOC( + " Adds a DNS name to the Subject Alternative Names extension.\n" + "\n" + " The name must contain only ASCII characters.\n" +). +-spec with_dns_name(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_dns_name(Builder, Name) -> + case kryptos@internal@utils:is_ascii(Name) of + true -> + _pipe = {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + [{dns_name, Name} | erlang:element(7, Builder)], + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}, + {ok, _pipe}; + + false -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 313). +?DOC( + " Adds an email address to the Subject Alternative Names extension.\n" + "\n" + " The email must contain only ASCII characters.\n" +). +-spec with_email(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_email(Builder, Email) -> + case kryptos@internal@utils:is_ascii(Email) of + true -> + _pipe = {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + [{email, Email} | erlang:element(7, Builder)], + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)}, + {ok, _pipe}; + + false -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 328). +?DOC( + " Adds an IP address to the Subject Alternative Names extension.\n" + "\n" + " Accepts IPv4 (e.g., \"192.168.1.1\") or IPv6 (e.g., \"2001:db8::1\") addresses.\n" +). +-spec with_ip(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_ip(Builder, Ip) -> + _pipe = kryptos@internal@utils:parse_ip(Ip), + gleam@result:map( + _pipe, + fun(Parsed) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + [{ip_address, Parsed} | erlang:element(7, Builder)], + erlang:element(8, Builder), + erlang:element(9, Builder), + erlang:element(10, Builder)} + end + ). + +-file("src/kryptos/x509/certificate.gleam", 341). +?DOC( + " Sets the serial number for the certificate.\n" + "\n" + " If not set, a random serial number will be generated during signing.\n" +). +-spec with_serial_number(builder(), bitstring()) -> builder(). +with_serial_number(Builder, Serial) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + {some, Serial}, + erlang:element(9, Builder), + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 350). +?DOC( + " Enables the Subject Key Identifier extension in the certificate.\n" + "\n" + " If not called, the SKI extension will not be included. Use `SkiAuto` to\n" + " compute from the public key (SHA-1 hash per RFC 5280 method 1) or\n" + " `SkiExplicit(bytes)` for a custom value.\n" +). +-spec with_subject_key_identifier(builder(), subject_key_identifier_config()) -> builder(). +with_subject_key_identifier(Builder, Ski) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + {some, Ski}, + erlang:element(10, Builder)}. + +-file("src/kryptos/x509/certificate.gleam", 362). +?DOC( + " Configures the Authority Key Identifier extension for the certificate.\n" + "\n" + " By default, self-signed certificates include an AKI with keyIdentifier\n" + " computed as the SHA-1 hash of the signing public key. Use `AkiExplicit`\n" + " for a custom value or `AkiExclude` to omit the extension.\n" +). +-spec with_authority_key_identifier( + builder(), + authority_key_identifier_config() +) -> builder(). +with_authority_key_identifier(Builder, Aki) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder), + erlang:element(8, Builder), + erlang:element(9, Builder), + Aki}. + +-file("src/kryptos/x509/certificate.gleam", 370). +?DOC(" Generates a random 20-byte serial number with the high bit cleared per RFC 5280.\n"). +-spec generate_serial_number() -> bitstring(). +generate_serial_number() -> + Bytes = kryptos_ffi:random_bytes(20), + {First@1, Rest@1} = case Bytes of + <> -> {First, Rest}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"generate_serial_number"/utf8>>, + line => 372, + value => _assert_fail, + start => 11779, + 'end' => 11820, + pattern_start => 11790, + pattern_end => 11812}) + end, + <<(erlang:'band'(First@1, 16#7f)):8, Rest@1/bitstring>>. + +-file("src/kryptos/x509/certificate.gleam", 474). +?DOC(" Exports the certificate as DER-encoded bytes.\n"). +-spec to_der(certificate(any())) -> bitstring(). +to_der(Cert) -> + case Cert of + {built_certificate, Der} -> + Der; + + {parsed_certificate, + Der@1, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> + Der@1 + end. + +-file("src/kryptos/x509/certificate.gleam", 639). +?DOC(" Returns the version of a parsed certificate (0 = v1, 1 = v2, 2 = v3).\n"). +-spec version(certificate(parsed())) -> integer(). +version(Cert) -> + Version@1 = case Cert of + {parsed_certificate, + _, + _, + _, + Version, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> Version; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"version"/utf8>>, + line => 640, + value => _assert_fail, + start => 20268, + 'end' => 20317, + pattern_start => 20279, + pattern_end => 20310}) + end, + Version@1. + +-file("src/kryptos/x509/certificate.gleam", 645). +?DOC(" Returns the serial number of a parsed certificate.\n"). +-spec serial_number(certificate(parsed())) -> bitstring(). +serial_number(Cert) -> + Serial_number@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + Serial_number, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> Serial_number; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"serial_number"/utf8>>, + line => 646, + value => _assert_fail, + start => 20450, + 'end' => 20505, + pattern_start => 20461, + pattern_end => 20498}) + end, + Serial_number@1. + +-file("src/kryptos/x509/certificate.gleam", 651). +?DOC(" Returns the signature algorithm used to sign the certificate.\n"). +-spec signature_algorithm(certificate(parsed())) -> kryptos@x509:signature_algorithm(). +signature_algorithm(Cert) -> + Signature_algorithm@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + Signature_algorithm, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> Signature_algorithm; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"signature_algorithm"/utf8>>, + line => 652, + value => _assert_fail, + start => 20676, + 'end' => 20737, + pattern_start => 20687, + pattern_end => 20730}) + end, + Signature_algorithm@1. + +-file("src/kryptos/x509/certificate.gleam", 659). +?DOC( + " Returns the issuer distinguished name.\n" + "\n" + " For self-signed certificates, issuer equals subject.\n" +). +-spec issuer(certificate(parsed())) -> kryptos@x509:name(). +issuer(Cert) -> + Issuer@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + Issuer, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> Issuer; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"issuer"/utf8>>, + line => 660, + value => _assert_fail, + start => 20925, + 'end' => 20973, + pattern_start => 20936, + pattern_end => 20966}) + end, + Issuer@1. + +-file("src/kryptos/x509/certificate.gleam", 665). +?DOC(" Returns the validity period of the certificate.\n"). +-spec validity(certificate(parsed())) -> kryptos@x509:validity(). +validity(Cert) -> + Validity@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + Validity, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> Validity; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"validity"/utf8>>, + line => 666, + value => _assert_fail, + start => 21102, + 'end' => 21152, + pattern_start => 21113, + pattern_end => 21145}) + end, + Validity@1. + +-file("src/kryptos/x509/certificate.gleam", 671). +?DOC(" Returns the subject distinguished name.\n"). +-spec subject(certificate(parsed())) -> kryptos@x509:name(). +subject(Cert) -> + Subject@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + Subject, + _, + _, + _, + _, + _, + _, + _, + _} -> Subject; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"subject"/utf8>>, + line => 672, + value => _assert_fail, + start => 21270, + 'end' => 21319, + pattern_start => 21281, + pattern_end => 21312}) + end, + Subject@1. + +-file("src/kryptos/x509/certificate.gleam", 677). +?DOC(" Returns the public key embedded in the certificate.\n"). +-spec public_key(certificate(parsed())) -> kryptos@x509:public_key(). +public_key(Cert) -> + Public_key@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Public_key, + _, + _, + _, + _, + _, + _, + _} -> Public_key; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"public_key"/utf8>>, + line => 678, + value => _assert_fail, + start => 21456, + 'end' => 21508, + pattern_start => 21467, + pattern_end => 21501}) + end, + Public_key@1. + +-file("src/kryptos/x509/certificate.gleam", 683). +?DOC(" Returns the Basic Constraints extension from a parsed certificate.\n"). +-spec basic_constraints(certificate(parsed())) -> {ok, + kryptos@x509:basic_constraints()} | + {error, nil}. +basic_constraints(Cert) -> + Basic_constraints@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Basic_constraints, + _, + _, + _, + _, + _, + _} -> Basic_constraints; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"basic_constraints"/utf8>>, + line => 686, + value => _assert_fail, + start => 21695, + 'end' => 21754, + pattern_start => 21706, + pattern_end => 21747}) + end, + gleam@option:to_result(Basic_constraints@1, nil). + +-file("src/kryptos/x509/certificate.gleam", 691). +?DOC(" Returns the Key Usage flags from a parsed certificate.\n"). +-spec key_usage(certificate(parsed())) -> list(kryptos@x509:key_usage()). +key_usage(Cert) -> + Key_usage@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Key_usage, + _, + _, + _, + _, + _} -> Key_usage; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"key_usage"/utf8>>, + line => 692, + value => _assert_fail, + start => 21931, + 'end' => 21982, + pattern_start => 21942, + pattern_end => 21975}) + end, + Key_usage@1. + +-file("src/kryptos/x509/certificate.gleam", 697). +?DOC(" Returns the Extended Key Usage purposes from a parsed certificate.\n"). +-spec extended_key_usage(certificate(parsed())) -> list(kryptos@x509:extended_key_usage()). +extended_key_usage(Cert) -> + Extended_key_usage@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Extended_key_usage, + _, + _, + _, + _} -> Extended_key_usage; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"extended_key_usage"/utf8>>, + line => 700, + value => _assert_fail, + start => 22162, + 'end' => 22222, + pattern_start => 22173, + pattern_end => 22215}) + end, + Extended_key_usage@1. + +-file("src/kryptos/x509/certificate.gleam", 705). +?DOC(" Returns the Subject Alternative Names (SANs) from a parsed certificate.\n"). +-spec subject_alt_names(certificate(parsed())) -> list(kryptos@x509:subject_alt_name()). +subject_alt_names(Cert) -> + Subject_alt_names@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Subject_alt_names, + _, + _, + _} -> Subject_alt_names; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"subject_alt_names"/utf8>>, + line => 706, + value => _assert_fail, + start => 22408, + 'end' => 22467, + pattern_start => 22419, + pattern_end => 22460}) + end, + Subject_alt_names@1. + +-file("src/kryptos/x509/certificate.gleam", 711). +?DOC(" Returns the Subject Key Identifier (SKI) from a parsed certificate.\n"). +-spec subject_key_identifier(certificate(parsed())) -> {ok, bitstring()} | + {error, nil}. +subject_key_identifier(Cert) -> + Subject_key_identifier@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Subject_key_identifier, + _, + _} -> Subject_key_identifier; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"subject_key_identifier"/utf8>>, + line => 714, + value => _assert_fail, + start => 22654, + 'end' => 22718, + pattern_start => 22665, + pattern_end => 22711}) + end, + gleam@option:to_result(Subject_key_identifier@1, nil). + +-file("src/kryptos/x509/certificate.gleam", 719). +?DOC(" Returns the Authority Key Identifier (AKI) from a parsed certificate.\n"). +-spec authority_key_identifier(certificate(parsed())) -> {ok, + kryptos@x509:authority_key_identifier()} | + {error, nil}. +authority_key_identifier(Cert) -> + Authority_key_identifier@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Authority_key_identifier, + _} -> Authority_key_identifier; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"authority_key_identifier"/utf8>>, + line => 722, + value => _assert_fail, + start => 22956, + 'end' => 23022, + pattern_start => 22967, + pattern_end => 23015}) + end, + gleam@option:to_result(Authority_key_identifier@1, nil). + +-file("src/kryptos/x509/certificate.gleam", 730). +?DOC( + " Returns all extensions as raw (OID, critical, value) tuples.\n" + "\n" + " Includes all extensions, even those with typed representations.\n" + " The Bool indicates whether the extension was marked as critical per RFC 5280.\n" +). +-spec extensions(certificate(parsed())) -> list({kryptos@x509:oid(), + boolean(), + bitstring()}). +extensions(Cert) -> + Extensions@1 = case Cert of + {parsed_certificate, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + Extensions} -> Extensions; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"extensions"/utf8>>, + line => 733, + value => _assert_fail, + start => 23386, + 'end' => 23438, + pattern_start => 23397, + pattern_end => 23431}) + end, + Extensions@1. + +-file("src/kryptos/x509/certificate.gleam", 776). +-spec parse_certificate_version(bitstring()) -> {ok, {integer(), bitstring()}} | + {error, certificate_error()}. +parse_certificate_version(Bytes) -> + case kryptos@internal@der:parse_context_tag(Bytes, 0) of + {ok, {Version_content, Rest}} -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_integer(Version_content), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(_use0) -> + {Version_bytes, _} = _use0, + case Version_bytes of + <<0>> -> + {ok, {0, Rest}}; + + <<1>> -> + {ok, {1, Rest}}; + + <<2>> -> + {ok, {2, Rest}}; + + <<_:8>> -> + {error, parse_error}; + + _ -> + {error, parse_error} + end + end + ); + + {error, _} -> + {ok, {0, Bytes}} + end. + +-file("src/kryptos/x509/certificate.gleam", 808). +-spec parse_time(bitstring()) -> {ok, + {gleam@time@timestamp:timestamp(), bitstring()}} | + {error, nil}. +parse_time(Bytes) -> + case Bytes of + <<16#17, _/bitstring>> -> + kryptos@internal@der:parse_utc_time(Bytes); + + <<16#18, _/bitstring>> -> + kryptos@internal@der:parse_generalized_time(Bytes); + + _ -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 798). +-spec parse_validity(bitstring()) -> {ok, kryptos@x509:validity()} | + {error, certificate_error()}. +parse_validity(Bytes) -> + gleam@result:'try'( + begin + _pipe = parse_time(Bytes), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(_use0) -> + {Not_before, After_not_before} = _use0, + gleam@result:'try'( + begin + _pipe@1 = parse_time(After_not_before), + gleam@result:replace_error(_pipe@1, parse_error) + end, + fun(_use0@1) -> + {Not_after, _} = _use0@1, + {ok, {validity, Not_before, Not_after}} + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 817). +?DOC(" Parse optional unique IDs with RFC 5280 version validation.\n"). +-spec parse_optional_unique_ids(bitstring(), integer()) -> {ok, bitstring()} | + {error, certificate_error()}. +parse_optional_unique_ids(Bytes, Version) -> + case Bytes of + <<16#81, _/bitstring>> -> + gleam@bool:guard( + Version < 1, + {error, parse_error}, + fun() -> case kryptos@internal@der:parse_tlv(Bytes) of + {ok, {_, _, Remaining}} -> + parse_optional_unique_ids(Remaining, Version); + + {error, _} -> + {error, parse_error} + end end + ); + + <<16#82, _/bitstring>> -> + gleam@bool:guard( + Version < 1, + {error, parse_error}, + fun() -> case kryptos@internal@der:parse_tlv(Bytes) of + {ok, {_, _, Remaining}} -> + parse_optional_unique_ids(Remaining, Version); + + {error, _} -> + {error, parse_error} + end end + ); + + _ -> + {ok, Bytes} + end. + +-file("src/kryptos/x509/certificate.gleam", 852). +-spec parse_raw_extensions( + bitstring(), + list({kryptos@x509:oid(), boolean(), bitstring()}) +) -> {ok, list({kryptos@x509:oid(), boolean(), bitstring()})} | {error, nil}. +parse_raw_extensions(Bytes, Acc) -> + case Bytes of + <<>> -> + {ok, lists:reverse(Acc)}; + + _ -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Ext_bytes, Rest} = _use0, + _pipe = kryptos@internal@x509:parse_single_extension( + Ext_bytes + ), + gleam@result:'try'( + _pipe, + fun(Ext) -> parse_raw_extensions(Rest, [Ext | Acc]) end + ) + end + ) + end. + +-file("src/kryptos/x509/certificate.gleam", 941). +-spec parse_subject_key_identifier_ext(bitstring()) -> {ok, bitstring()} | + {error, nil}. +parse_subject_key_identifier_ext(Bytes) -> + gleam@result:'try'( + kryptos@internal@der:parse_octet_string(Bytes), + fun(_use0) -> + {Value, Remaining} = _use0, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> {ok, Value} end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 960). +-spec parse_aki_fields( + bitstring(), + gleam@option:option(bitstring()), + gleam@option:option(list(kryptos@x509:subject_alt_name())), + gleam@option:option(bitstring()) +) -> {ok, kryptos@x509:authority_key_identifier()} | {error, nil}. +parse_aki_fields(Bytes, Key_id, Issuer, Serial) -> + case Bytes of + <<>> -> + {ok, {authority_key_identifier, Key_id, Issuer, Serial}}; + + <<16#80, Len:8, Rest/bitstring>> -> + gleam@bool:guard( + erlang:byte_size(Rest) < Len, + {error, nil}, + fun() -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Rest, 0, Len), + fun(Key_bytes) -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice( + Rest, + Len, + erlang:byte_size(Rest) - Len + ), + fun(Remaining) -> + parse_aki_fields( + Remaining, + {some, Key_bytes}, + Issuer, + Serial + ) + end + ) + end + ) + end + ); + + <<16#a1, _/bitstring>> -> + gleam@result:'try'( + kryptos@internal@der:parse_context_tag(Bytes, 1), + fun(_use0) -> + {Issuer_content, Remaining@1} = _use0, + gleam@result:'try'( + kryptos@internal@x509:parse_general_names( + Issuer_content, + [], + false + ), + fun(Parsed_issuers) -> + parse_aki_fields( + Remaining@1, + Key_id, + {some, Parsed_issuers}, + Serial + ) + end + ) + end + ); + + <<16#82, Len@1:8, Rest@1/bitstring>> -> + gleam@bool:guard( + erlang:byte_size(Rest@1) < Len@1, + {error, nil}, + fun() -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice(Rest@1, 0, Len@1), + fun(Serial_bytes) -> + gleam@result:'try'( + gleam_stdlib:bit_array_slice( + Rest@1, + Len@1, + erlang:byte_size(Rest@1) - Len@1 + ), + fun(Remaining@2) -> + parse_aki_fields( + Remaining@2, + Key_id, + Issuer, + {some, Serial_bytes} + ) + end + ) + end + ) + end + ); + + _ -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 947). +-spec parse_authority_key_identifier_ext(bitstring()) -> {ok, + kryptos@x509:authority_key_identifier()} | + {error, nil}. +parse_authority_key_identifier_ext(Bytes) -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Inner, Remaining} = _use0, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> parse_aki_fields(Inner, none, none, none) end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1068). +-spec bytes_to_int(bitstring()) -> {ok, integer()} | {error, nil}. +bytes_to_int(Bytes) -> + case Bytes of + <> -> + {ok, N}; + + <> -> + {ok, N@1}; + + <> -> + {ok, N@2}; + + <> -> + {ok, N@3}; + + _ -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 1035). +-spec parse_basic_constraints_ext(bitstring()) -> {ok, + kryptos@x509:basic_constraints()} | + {error, nil}. +parse_basic_constraints_ext(Bytes) -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Seq_content, Remaining} = _use0, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> + gleam@bool:guard( + erlang:byte_size(Seq_content) =:= 0, + {ok, {basic_constraints, false, none}}, + fun() -> + gleam@result:'try'( + kryptos@internal@der:parse_bool(Seq_content), + fun(_use0@1) -> + {Ca, After_ca} = _use0@1, + gleam@bool:guard( + erlang:byte_size(After_ca) =:= 0, + {ok, {basic_constraints, Ca, none}}, + fun() -> + gleam@bool:guard( + not Ca, + {ok, + {basic_constraints, + false, + none}}, + fun() -> + gleam@result:'try'( + kryptos@internal@der:parse_integer( + After_ca + ), + fun(_use0@2) -> + {Path_len_bytes, + Remaining@1} = _use0@2, + gleam@bool:guard( + Remaining@1 /= <<>>, + {error, nil}, + fun() -> + gleam@result:'try'( + bytes_to_int( + Path_len_bytes + ), + fun( + Path_len + ) -> + {ok, + {basic_constraints, + Ca, + {some, + Path_len}}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1088). +-spec decode_key_usage_bits(bitstring()) -> list(kryptos@x509:key_usage()). +decode_key_usage_bits(Bytes) -> + case Bytes of + <> -> + Usages = begin + _pipe = [{Digital_signature, digital_signature}, + {Non_repudiation, non_repudiation}, + {Key_encipherment, key_encipherment}, + {Data_encipherment, data_encipherment}, + {Key_agreement, key_agreement}, + {Key_cert_sign, key_cert_sign}, + {Crl_sign, crl_sign}, + {Encipher_only, encipher_only}], + gleam@list:filter_map( + _pipe, + fun(Pair) -> + {Bit, Usage} = Pair, + case Bit =:= 1 of + true -> + {ok, Usage}; + + false -> + {error, nil} + end + end + ) + end, + case Rest of + <<1:1, _/bitstring>> -> + [decipher_only | Usages]; + + _ -> + Usages + end; + + _ -> + [] + end. + +-file("src/kryptos/x509/certificate.gleam", 1078). +-spec parse_key_usage_ext(bitstring()) -> {ok, list(kryptos@x509:key_usage())} | + {error, nil}. +parse_key_usage_ext(Bytes) -> + case Bytes of + <<16#03, Len:8, Unused_bits:8, Rest/bitstring>> when (Unused_bits =< 7) andalso (Len >= 2) -> + _pipe = gleam_stdlib:bit_array_slice(Rest, 0, Len - 1), + gleam@result:map(_pipe, fun decode_key_usage_bits/1); + + _ -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 1139). +-spec parse_eku_oids( + bitstring(), + list(kryptos@x509:extended_key_usage()), + boolean() +) -> {ok, list(kryptos@x509:extended_key_usage())} | + {error, certificate_error()}. +parse_eku_oids(Bytes, Acc, Is_critical) -> + case Bytes of + <<>> -> + {ok, lists:reverse(Acc)}; + + _ -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_oid(Bytes), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(_use0) -> + {Oid_components, Rest} = _use0, + Eku = case Oid_components of + [1, 3, 6, 1, 5, 5, 7, 3, 1] -> + {some, server_auth}; + + [1, 3, 6, 1, 5, 5, 7, 3, 2] -> + {some, client_auth}; + + [1, 3, 6, 1, 5, 5, 7, 3, 3] -> + {some, code_signing}; + + [1, 3, 6, 1, 5, 5, 7, 3, 4] -> + {some, email_protection}; + + [1, 3, 6, 1, 5, 5, 7, 3, 9] -> + {some, ocsp_signing}; + + _ -> + none + end, + case {Eku, Is_critical} of + {{some, E}, _} -> + parse_eku_oids(Rest, [E | Acc], Is_critical); + + {none, false} -> + parse_eku_oids(Rest, Acc, Is_critical); + + {none, true} -> + {error, + {unrecognized_critical_extension, + {oid, Oid_components}}} + end + end + ) + end. + +-file("src/kryptos/x509/certificate.gleam", 1129). +-spec parse_extended_key_usage_ext(bitstring(), boolean()) -> {ok, + list(kryptos@x509:extended_key_usage())} | + {error, certificate_error()}. +parse_extended_key_usage_ext(Bytes, Is_critical) -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_sequence(Bytes), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(_use0) -> + {Seq_content, _} = _use0, + parse_eku_oids(Seq_content, [], Is_critical) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 866). +-spec process_extension( + extensions_acc(), + {kryptos@x509:oid(), boolean(), bitstring()} +) -> {ok, extensions_acc()} | {error, certificate_error()}. +process_extension(Acc, Ext) -> + {Oid, Is_critical, Value} = Ext, + case Oid of + {oid, [2, 5, 29, 19]} -> + _pipe = parse_basic_constraints_ext(Value), + _pipe@1 = gleam@result:replace_error(_pipe, parse_error), + gleam@result:map( + _pipe@1, + fun(Bc) -> + {extensions_acc, + {some, Bc}, + erlang:element(3, Acc), + erlang:element(4, Acc), + erlang:element(5, Acc), + erlang:element(6, Acc), + erlang:element(7, Acc), + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + {oid, [2, 5, 29, 15]} -> + _pipe@2 = parse_key_usage_ext(Value), + _pipe@3 = gleam@result:replace_error(_pipe@2, parse_error), + gleam@result:map( + _pipe@3, + fun(Key_usage) -> + {extensions_acc, + erlang:element(2, Acc), + Key_usage, + erlang:element(4, Acc), + erlang:element(5, Acc), + erlang:element(6, Acc), + erlang:element(7, Acc), + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + {oid, [2, 5, 29, 37]} -> + _pipe@4 = parse_extended_key_usage_ext(Value, Is_critical), + gleam@result:map( + _pipe@4, + fun(Extended_key_usage) -> + {extensions_acc, + erlang:element(2, Acc), + erlang:element(3, Acc), + Extended_key_usage, + erlang:element(5, Acc), + erlang:element(6, Acc), + erlang:element(7, Acc), + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + {oid, [2, 5, 29, 17]} -> + _pipe@5 = kryptos@internal@x509:parse_san_extension( + Value, + Is_critical + ), + _pipe@6 = gleam@result:replace_error(_pipe@5, parse_error), + gleam@result:map( + _pipe@6, + fun(Subject_alt_names) -> + {extensions_acc, + erlang:element(2, Acc), + erlang:element(3, Acc), + erlang:element(4, Acc), + Subject_alt_names, + erlang:element(6, Acc), + erlang:element(7, Acc), + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + {oid, [2, 5, 29, 14]} -> + _pipe@7 = parse_subject_key_identifier_ext(Value), + _pipe@8 = gleam@result:replace_error(_pipe@7, parse_error), + gleam@result:map( + _pipe@8, + fun(Ski) -> + {extensions_acc, + erlang:element(2, Acc), + erlang:element(3, Acc), + erlang:element(4, Acc), + erlang:element(5, Acc), + {some, Ski}, + erlang:element(7, Acc), + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + {oid, [2, 5, 29, 35]} -> + _pipe@9 = parse_authority_key_identifier_ext(Value), + _pipe@10 = gleam@result:replace_error(_pipe@9, parse_error), + gleam@result:map( + _pipe@10, + fun(Aki) -> + {extensions_acc, + erlang:element(2, Acc), + erlang:element(3, Acc), + erlang:element(4, Acc), + erlang:element(5, Acc), + erlang:element(6, Acc), + {some, Aki}, + erlang:element(8, Acc), + erlang:element(9, Acc)} + end + ); + + _ -> + case Is_critical of + true -> + {error, {unrecognized_critical_extension, Oid}}; + + false -> + {ok, Acc} + end + end. + +-file("src/kryptos/x509/certificate.gleam", 920). +-spec parse_extensions_content(bitstring()) -> {ok, extensions_acc()} | + {error, certificate_error()}. +parse_extensions_content(Bytes) -> + gleam@result:'try'( + begin + _pipe = parse_raw_extensions(Bytes, []), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(Raw) -> + _pipe@2 = gleam@list:try_fold( + Raw, + empty_extensions_acc(), + fun(Acc, Ext) -> + {{oid, Components}, _, _} = Ext, + case gleam@set:contains(erlang:element(9, Acc), Components) of + true -> + {error, parse_error}; + + false -> + _pipe@1 = {extensions_acc, + erlang:element(2, Acc), + erlang:element(3, Acc), + erlang:element(4, Acc), + erlang:element(5, Acc), + erlang:element(6, Acc), + erlang:element(7, Acc), + erlang:element(8, Acc), + gleam@set:insert( + erlang:element(9, Acc), + Components + )}, + process_extension(_pipe@1, Ext) + end + end + ), + gleam@result:map( + _pipe@2, + fun(Acc@1) -> + {extensions_acc, + erlang:element(2, Acc@1), + erlang:element(3, Acc@1), + erlang:element(4, Acc@1), + erlang:element(5, Acc@1), + erlang:element(6, Acc@1), + erlang:element(7, Acc@1), + Raw, + erlang:element(9, Acc@1)} + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 834). +?DOC(" Parse extensions with RFC 5280 version validation.\n"). +-spec parse_certificate_extensions(bitstring(), integer()) -> {ok, + extensions_acc()} | + {error, certificate_error()}. +parse_certificate_extensions(Bytes, Version) -> + gleam@result:'try'( + parse_optional_unique_ids(Bytes, Version), + fun(Bytes@1) -> + case kryptos@internal@der:parse_context_tag(Bytes@1, 3) of + {ok, {Exts_content, _}} -> + gleam@bool:guard( + Version < 2, + {error, parse_error}, + fun() -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_sequence( + Exts_content + ), + gleam@result:replace_error( + _pipe, + parse_error + ) + end, + fun(_use0) -> + {Exts_seq, _} = _use0, + parse_extensions_content(Exts_seq) + end + ) + end + ); + + {error, _} -> + {ok, empty_extensions_acc()} + end + end + ). + +-file("src/kryptos/x509/certificate.gleam", 510). +?DOC( + " Parse a DER-encoded X.509 certificate.\n" + "\n" + " Validates the ASN.1 structure and extracts all standard fields and\n" + " extensions. Unknown non-critical extensions are preserved but not parsed.\n" + "\n" + " **Note:** This function does NOT verify the certificate's cryptographic\n" + " signature. To verify a certificate was signed by an issuer, use `verify()`.\n" + " For self-signed certificates, use `verify_self_signed()`.\n" +). +-spec from_der(bitstring()) -> {ok, certificate(parsed())} | + {error, certificate_error()}. +from_der(Der) -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_sequence(Der), + gleam@result:replace_error(_pipe, parse_error) + end, + fun(_use0) -> + {Cert_content, Remaining} = _use0, + gleam@bool:guard( + erlang:byte_size(Remaining) /= 0, + {error, parse_error}, + fun() -> + gleam@result:'try'( + begin + _pipe@1 = kryptos@internal@x509:parse_sequence_with_header( + Cert_content + ), + gleam@result:replace_error(_pipe@1, parse_error) + end, + fun(_use0@1) -> + {Tbs_bytes, After_tbs} = _use0@1, + gleam@result:'try'( + begin + _pipe@2 = kryptos@internal@der:parse_sequence( + Tbs_bytes + ), + gleam@result:replace_error( + _pipe@2, + parse_error + ) + end, + fun(_use0@2) -> + {Tbs_content, _} = _use0@2, + gleam@result:'try'( + parse_certificate_version(Tbs_content), + fun(_use0@3) -> + {Version, After_version} = _use0@3, + gleam@result:'try'( + begin + _pipe@3 = kryptos@internal@der:parse_integer( + After_version + ), + gleam@result:replace_error( + _pipe@3, + parse_error + ) + end, + fun(_use0@4) -> + {Serial_number, + After_serial} = _use0@4, + gleam@result:'try'( + begin + _pipe@4 = kryptos@internal@der:parse_sequence( + After_serial + ), + gleam@result:replace_error( + _pipe@4, + parse_error + ) + end, + fun(_use0@5) -> + {Sig_alg_bytes, + After_sig_alg} = _use0@5, + gleam@result:'try'( + begin + _pipe@5 = kryptos@internal@x509:parse_signature_algorithm( + Sig_alg_bytes + ), + gleam@result:map_error( + _pipe@5, + fun(Field@0) -> {unsupported_algorithm, Field@0} end + ) + end, + fun( + Signature_algorithm + ) -> + gleam@result:'try'( + begin + _pipe@6 = kryptos@internal@der:parse_sequence( + After_sig_alg + ), + gleam@result:replace_error( + _pipe@6, + parse_error + ) + end, + fun( + _use0@6 + ) -> + {Issuer_bytes, + After_issuer} = _use0@6, + gleam@result:'try'( + begin + _pipe@7 = kryptos@internal@x509:parse_name( + Issuer_bytes + ), + gleam@result:replace_error( + _pipe@7, + parse_error + ) + end, + fun( + Issuer + ) -> + gleam@result:'try'( + begin + _pipe@8 = kryptos@internal@der:parse_sequence( + After_issuer + ), + gleam@result:replace_error( + _pipe@8, + parse_error + ) + end, + fun( + _use0@7 + ) -> + {Validity_bytes, + After_validity} = _use0@7, + gleam@result:'try'( + parse_validity( + Validity_bytes + ), + fun( + Validity + ) -> + gleam@result:'try'( + begin + _pipe@9 = kryptos@internal@der:parse_sequence( + After_validity + ), + gleam@result:replace_error( + _pipe@9, + parse_error + ) + end, + fun( + _use0@8 + ) -> + {Subject_bytes, + After_subject} = _use0@8, + gleam@result:'try'( + begin + _pipe@10 = kryptos@internal@x509:parse_name( + Subject_bytes + ), + gleam@result:replace_error( + _pipe@10, + parse_error + ) + end, + fun( + Subject + ) -> + gleam@result:'try'( + begin + _pipe@11 = kryptos@internal@x509:parse_sequence_with_header( + After_subject + ), + gleam@result:replace_error( + _pipe@11, + parse_error + ) + end, + fun( + _use0@9 + ) -> + {Spki_bytes, + After_spki} = _use0@9, + gleam@result:'try'( + begin + _pipe@12 = kryptos@internal@x509:parse_public_key( + Spki_bytes + ), + gleam@result:map_error( + _pipe@12, + fun( + Oid + ) -> + case Oid of + {oid, + []} -> + parse_error; + + _ -> + {unsupported_algorithm, + Oid} + end + end + ) + end, + fun( + Public_key + ) -> + gleam@result:'try'( + parse_certificate_extensions( + After_spki, + Version + ), + fun( + Exts + ) -> + gleam@result:'try'( + begin + _pipe@13 = kryptos@internal@der:parse_sequence( + After_tbs + ), + gleam@result:replace_error( + _pipe@13, + parse_error + ) + end, + fun( + _use0@10 + ) -> + {Outer_sig_alg_bytes, + After_outer_sig_alg} = _use0@10, + gleam@result:'try'( + begin + _pipe@14 = kryptos@internal@x509:parse_signature_algorithm( + Outer_sig_alg_bytes + ), + gleam@result:replace_error( + _pipe@14, + parse_error + ) + end, + fun( + Outer_signature_algorithm + ) -> + gleam@bool:guard( + Signature_algorithm + /= Outer_signature_algorithm, + {error, + parse_error}, + fun( + + ) -> + gleam@result:'try'( + begin + _pipe@15 = kryptos@internal@der:parse_bit_string( + After_outer_sig_alg + ), + gleam@result:replace_error( + _pipe@15, + parse_error + ) + end, + fun( + _use0@11 + ) -> + {Signature, + _} = _use0@11, + {ok, + {parsed_certificate, + Der, + Tbs_bytes, + Signature, + Version, + Serial_number, + Signature_algorithm, + Issuer, + Validity, + Subject, + Public_key, + erlang:element( + 2, + Exts + ), + erlang:element( + 3, + Exts + ), + erlang:element( + 4, + Exts + ), + erlang:element( + 5, + Exts + ), + erlang:element( + 6, + Exts + ), + erlang:element( + 7, + Exts + ), + erlang:element( + 8, + Exts + )}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1199). +-spec encode_version() -> {ok, bitstring()} | {error, nil}. +encode_version() -> + _pipe = kryptos@internal@der:encode_integer(<<2>>), + gleam@result:'try'( + _pipe, + fun(_capture) -> + kryptos@internal@der:encode_context_tag(0, _capture) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1204). +-spec encode_validity(kryptos@x509:validity()) -> {ok, bitstring()} | + {error, nil}. +encode_validity(Validity) -> + {validity, Not_before, Not_after} = Validity, + gleam@result:'try'( + kryptos@internal@der:encode_timestamp(Not_before), + fun(Not_before_der) -> + gleam@result:'try'( + kryptos@internal@der:encode_timestamp(Not_after), + fun(Not_after_der) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Not_before_der, Not_after_der] + ) + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1264). +-spec encode_san_opt(list(kryptos@x509:subject_alt_name()), boolean()) -> {ok, + {ok, bitstring()} | {error, nil}} | + {error, nil}. +encode_san_opt(Sans, Critical) -> + gleam@bool:guard( + gleam@list:is_empty(Sans), + {ok, {error, nil}}, + fun() -> + gleam@result:map( + kryptos@internal@x509:encode_san_extension(Sans, Critical), + fun(Field@0) -> {ok, Field@0} end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1399). +-spec compute_ski(bitstring()) -> {ok, bitstring()} | {error, nil}. +compute_ski(Spki) -> + _pipe = Spki, + _pipe@1 = kryptos@internal@x509:extract_spki_public_key_bytes(_pipe), + gleam@result:'try'( + _pipe@1, + fun(_capture) -> kryptos@crypto:hash(sha1, _capture) end + ). + +-file("src/kryptos/x509/certificate.gleam", 1432). +-spec encode_certificate( + bitstring(), + kryptos@internal@x509:sig_alg_info(), + bitstring() +) -> {ok, bitstring()} | {error, nil}. +encode_certificate(Tbs, Sig_alg, Signature) -> + gleam@result:'try'( + kryptos@internal@x509:encode_algorithm_identifier(Sig_alg), + fun(Sig_alg_der) -> + gleam@result:'try'( + kryptos@internal@der:encode_bit_string(Signature), + fun(Sig_bits) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Tbs, Sig_alg_der, Sig_bits] + ) + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1444). +-spec is_xdh_key(kryptos@x509:public_key()) -> boolean(). +is_xdh_key(Key) -> + case Key of + {xdh_public_key, _} -> + true; + + _ -> + false + end. + +-file("src/kryptos/x509/certificate.gleam", 1451). +-spec xdh_key_oid(kryptos@x509:public_key()) -> {ok, kryptos@x509:oid()} | + {error, nil}. +xdh_key_oid(Key) -> + case Key of + {xdh_public_key, Xdh_key} -> + case kryptos_ffi:xdh_public_key_curve(Xdh_key) of + x25519 -> + {ok, {oid, [1, 3, 101, 110]}}; + + x448 -> + {ok, {oid, [1, 3, 101, 111]}} + end; + + _ -> + {error, nil} + end. + +-file("src/kryptos/x509/certificate.gleam", 740). +?DOC( + " Verify a certificate's signature against an issuer's public key.\n" + "\n" + " The public key must be RSA, ECDSA, or EdDSA (XDH keys cannot sign).\n" +). +-spec verify(certificate(parsed()), kryptos@x509:public_key()) -> {ok, nil} | + {error, certificate_error()}. +verify(Cert, Issuer_public_key) -> + {Tbs_bytes@1, Signature@1, Signature_algorithm@1} = case Cert of + {parsed_certificate, + _, + Tbs_bytes, + Signature, + _, + _, + Signature_algorithm, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _} -> {Tbs_bytes, Signature, Signature_algorithm}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/certificate"/utf8>>, + function => <<"verify"/utf8>>, + line => 744, + value => _assert_fail, + start => 23720, + 'end' => 23809, + pattern_start => 23731, + pattern_end => 23798}) + end, + gleam@bool:lazy_guard( + is_xdh_key(Issuer_public_key), + fun() -> + Oid = begin + _pipe = xdh_key_oid(Issuer_public_key), + gleam@result:unwrap(_pipe, {oid, []}) + end, + {error, {unsupported_algorithm, Oid}} + end, + fun() -> + Verified = kryptos@internal@x509:verify_signature( + Issuer_public_key, + Tbs_bytes@1, + Signature@1, + Signature_algorithm@1 + ), + case Verified of + true -> + {ok, nil}; + + false -> + {error, signature_verification_failed} + end + end + ). + +-file("src/kryptos/x509/certificate.gleam", 769). +?DOC(" Verify a self-signed certificate against its own public key.\n"). +-spec verify_self_signed(certificate(parsed())) -> {ok, nil} | + {error, certificate_error()}. +verify_self_signed(Cert) -> + _pipe = public_key(Cert), + verify(Cert, _pipe). + +-file("src/kryptos/x509/certificate.gleam", 1418). +-spec encode_authority_key_identifier_extension(bitstring()) -> {ok, + bitstring()} | + {error, nil}. +encode_authority_key_identifier_extension(Key_identifier) -> + {oid, Oid_components} = {oid, [2, 5, 29, 35]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> _pipe = Key_identifier, + _pipe@1 = kryptos@internal@der:encode_context_primitive_tag( + 0, + _pipe + ), + _pipe@2 = gleam@result:'try'( + _pipe@1, + fun kryptos@internal@der:encode_sequence/1 + ), + _pipe@3 = gleam@result:'try'( + _pipe@2, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@4 = gleam@result:map( + _pipe@3, + fun(Value_octet) -> + gleam_stdlib:bit_array_concat([Oid_encoded, Value_octet]) + end + ), + gleam@result:'try'( + _pipe@4, + fun kryptos@internal@der:encode_sequence/1 + ) end + ). + +-file("src/kryptos/x509/certificate.gleam", 1288). +-spec encode_aki_opt(authority_key_identifier_config(), bitstring()) -> {ok, + {ok, bitstring()} | {error, nil}} | + {error, nil}. +encode_aki_opt(Config, Spki) -> + case Config of + aki_exclude -> + {ok, {error, nil}}; + + aki_auto -> + _pipe = compute_ski(Spki), + _pipe@1 = gleam@result:'try'( + _pipe, + fun encode_authority_key_identifier_extension/1 + ), + gleam@result:map(_pipe@1, fun(Field@0) -> {ok, Field@0} end); + + {aki_explicit, Key_id} -> + _pipe@2 = encode_authority_key_identifier_extension(Key_id), + gleam@result:map(_pipe@2, fun(Field@0) -> {ok, Field@0} end) + end. + +-file("src/kryptos/x509/certificate.gleam", 1304). +-spec encode_basic_constraints_extension( + boolean(), + gleam@option:option(integer()) +) -> {ok, bitstring()} | {error, nil}. +encode_basic_constraints_extension(Ca, Path_len) -> + {oid, Oid_components} = {oid, [2, 5, 29, 19]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> + Ca_bool = case Ca of + true -> + kryptos@internal@der:encode_bool(true); + + false -> + <<>> + end, + _pipe = case Path_len of + {some, N} -> + kryptos@internal@der:encode_small_int(N); + + none -> + {ok, <<>>} + end, + _pipe@1 = gleam@result:map( + _pipe, + fun(Path_len_int) -> + gleam_stdlib:bit_array_concat([Ca_bool, Path_len_int]) + end + ), + _pipe@2 = gleam@result:'try'( + _pipe@1, + fun kryptos@internal@der:encode_sequence/1 + ), + _pipe@3 = gleam@result:'try'( + _pipe@2, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@4 = gleam@result:map( + _pipe@3, + fun(Value_octet) -> + gleam_stdlib:bit_array_concat( + [Oid_encoded, + kryptos@internal@der:encode_bool(true), + Value_octet] + ) + end + ), + gleam@result:'try'( + _pipe@4, + fun kryptos@internal@der:encode_sequence/1 + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1240). +-spec encode_basic_constraints_opt( + gleam@option:option({boolean(), gleam@option:option(integer())}) +) -> {ok, {ok, bitstring()} | {error, nil}} | {error, nil}. +encode_basic_constraints_opt(Config) -> + case Config of + none -> + {ok, {error, nil}}; + + {some, {Ca, Path_len}} -> + gleam@result:map( + encode_basic_constraints_extension(Ca, Path_len), + fun(Field@0) -> {ok, Field@0} end + ) + end. + +-file("src/kryptos/x509/certificate.gleam", 1375). +-spec encode_extended_key_usage_extension( + list(kryptos@x509:extended_key_usage()) +) -> {ok, bitstring()} | {error, nil}. +encode_extended_key_usage_extension(Usages) -> + {oid, Oid_components} = {oid, [2, 5, 29, 37]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> _pipe = Usages, + _pipe@1 = gleam@list:try_map( + _pipe, + fun(Usage) -> + {oid, Components} = case Usage of + server_auth -> + {oid, [1, 3, 6, 1, 5, 5, 7, 3, 1]}; + + client_auth -> + {oid, [1, 3, 6, 1, 5, 5, 7, 3, 2]}; + + code_signing -> + {oid, [1, 3, 6, 1, 5, 5, 7, 3, 3]}; + + email_protection -> + {oid, [1, 3, 6, 1, 5, 5, 7, 3, 4]}; + + ocsp_signing -> + {oid, [1, 3, 6, 1, 5, 5, 7, 3, 9]} + end, + kryptos@internal@der:encode_oid(Components) + end + ), + _pipe@2 = gleam@result:map( + _pipe@1, + fun gleam_stdlib:bit_array_concat/1 + ), + _pipe@3 = gleam@result:'try'( + _pipe@2, + fun kryptos@internal@der:encode_sequence/1 + ), + _pipe@4 = gleam@result:'try'( + _pipe@3, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@5 = gleam@result:map( + _pipe@4, + fun(Value_octet) -> + gleam_stdlib:bit_array_concat([Oid_encoded, Value_octet]) + end + ), + gleam@result:'try'( + _pipe@5, + fun kryptos@internal@der:encode_sequence/1 + ) end + ). + +-file("src/kryptos/x509/certificate.gleam", 1257). +-spec encode_extended_key_usage_opt(list(kryptos@x509:extended_key_usage())) -> {ok, + {ok, bitstring()} | {error, nil}} | + {error, nil}. +encode_extended_key_usage_opt(Usages) -> + gleam@bool:guard( + gleam@list:is_empty(Usages), + {ok, {error, nil}}, + fun() -> + gleam@result:map( + encode_extended_key_usage_extension(Usages), + fun(Field@0) -> {ok, Field@0} end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1405). +-spec encode_subject_key_identifier_extension(bitstring()) -> {ok, bitstring()} | + {error, nil}. +encode_subject_key_identifier_extension(Ski) -> + {oid, Oid_components} = {oid, [2, 5, 29, 14]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> _pipe = Ski, + _pipe@1 = kryptos@internal@der:encode_octet_string(_pipe), + _pipe@2 = gleam@result:'try'( + _pipe@1, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(Value_octet) -> + gleam_stdlib:bit_array_concat([Oid_encoded, Value_octet]) + end + ), + gleam@result:'try'( + _pipe@3, + fun kryptos@internal@der:encode_sequence/1 + ) end + ). + +-file("src/kryptos/x509/certificate.gleam", 1272). +-spec encode_ski_opt( + gleam@option:option(subject_key_identifier_config()), + bitstring() +) -> {ok, {ok, bitstring()} | {error, nil}} | {error, nil}. +encode_ski_opt(Config, Spki) -> + case Config of + none -> + {ok, {error, nil}}; + + {some, ski_auto} -> + _pipe = compute_ski(Spki), + _pipe@1 = gleam@result:'try'( + _pipe, + fun encode_subject_key_identifier_extension/1 + ), + gleam@result:map(_pipe@1, fun(Field@0) -> {ok, Field@0} end); + + {some, {ski_explicit, Ski}} -> + _pipe@2 = encode_subject_key_identifier_extension(Ski), + gleam@result:map(_pipe@2, fun(Field@0) -> {ok, Field@0} end) + end. + +-file("src/kryptos/x509/certificate.gleam", 482). +?DOC(" Exports the certificate as a PEM-encoded string.\n"). +-spec to_pem(certificate(any())) -> binary(). +to_pem(Cert) -> + kryptos@internal@x509:encode_pem( + to_der(Cert), + <<"-----BEGIN CERTIFICATE-----"/utf8>>, + <<"-----END CERTIFICATE-----"/utf8>> + ). + +-file("src/kryptos/x509/certificate.gleam", 494). +?DOC( + " Parse all PEM-encoded certificates from a string.\n" + "\n" + " Extracts and parses all `-----BEGIN CERTIFICATE-----` blocks from the input.\n" + " Certificates are returned in the order they appear.\n" + "\n" + " **Note:** This function does NOT verify the certificates' cryptographic\n" + " signatures. To verify a certificate was signed by an issuer, use `verify()`.\n" + " For self-signed certificates, use `verify_self_signed()`.\n" +). +-spec from_pem(binary()) -> {ok, list(certificate(parsed()))} | + {error, certificate_error()}. +from_pem(Pem) -> + _pipe = kryptos@internal@x509:decode_pem_all( + Pem, + <<"-----BEGIN CERTIFICATE-----"/utf8>>, + <<"-----END CERTIFICATE-----"/utf8>> + ), + _pipe@1 = gleam@result:replace_error(_pipe, parse_error), + gleam@result:'try'( + _pipe@1, + fun(_capture) -> gleam@list:try_map(_capture, fun from_der/1) end + ). + +-file("src/kryptos/x509/certificate.gleam", 1341). +-spec encode_key_usage_extension(list(kryptos@x509:key_usage())) -> {ok, + bitstring()} | + {error, nil}. +encode_key_usage_extension(Usages) -> + {oid, Oid_components} = {oid, [2, 5, 29, 15]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Oid_components), + fun(Oid_encoded) -> + Last_set_index = gleam@list:index_fold( + [digital_signature, + non_repudiation, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only], + 0, + fun(Last_index, Usage, Index) -> + case gleam@list:contains(Usages, Usage) of + true -> + Index + 1; + + false -> + Last_index + end + end + ), + Key_usage_bits = begin + _pipe = [digital_signature, + non_repudiation, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only], + _pipe@1 = gleam@list:take(_pipe, Last_set_index), + gleam@list:fold( + _pipe@1, + <<>>, + fun(Acc, Usage@1) -> + Bit = case gleam@list:contains(Usages, Usage@1) of + true -> + 1; + + false -> + 0 + end, + <> + end + ) + end, + _pipe@2 = Key_usage_bits, + _pipe@3 = kryptos@internal@der:encode_bit_string(_pipe@2), + _pipe@4 = gleam@result:'try'( + _pipe@3, + fun kryptos@internal@der:encode_octet_string/1 + ), + _pipe@5 = gleam@result:map( + _pipe@4, + fun(Value_octet) -> + gleam_stdlib:bit_array_concat( + [Oid_encoded, + kryptos@internal@der:encode_bool(true), + Value_octet] + ) + end + ), + gleam@result:'try'( + _pipe@5, + fun kryptos@internal@der:encode_sequence/1 + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1250). +-spec encode_key_usage_opt(list(kryptos@x509:key_usage())) -> {ok, + {ok, bitstring()} | {error, nil}} | + {error, nil}. +encode_key_usage_opt(Usages) -> + gleam@bool:guard( + gleam@list:is_empty(Usages), + {ok, {error, nil}}, + fun() -> + gleam@result:map( + encode_key_usage_extension(Usages), + fun(Field@0) -> {ok, Field@0} end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1211). +-spec encode_extensions(builder(), bitstring()) -> {ok, bitstring()} | + {error, nil}. +encode_extensions(Builder, Spki) -> + {name, Rdns} = erlang:element(2, Builder), + Subject_is_empty = gleam@list:is_empty(Rdns), + Sans_is_empty = gleam@list:is_empty(erlang:element(7, Builder)), + gleam@bool:guard( + Subject_is_empty andalso Sans_is_empty, + {error, nil}, + fun() -> + Extension_results = [encode_basic_constraints_opt( + erlang:element(4, Builder) + ), + encode_key_usage_opt(erlang:element(5, Builder)), + encode_extended_key_usage_opt(erlang:element(6, Builder)), + encode_san_opt(erlang:element(7, Builder), Subject_is_empty), + encode_ski_opt(erlang:element(9, Builder), Spki), + encode_aki_opt(erlang:element(10, Builder), Spki)], + gleam@result:'try'( + gleam@result:all(Extension_results), + fun(Results) -> + Encoded = gleam@result:values(Results), + case Encoded of + [] -> + {ok, <<>>}; + + _ -> + _pipe = Encoded, + _pipe@1 = gleam_stdlib:bit_array_concat(_pipe), + _pipe@2 = kryptos@internal@der:encode_sequence( + _pipe@1 + ), + gleam@result:'try'( + _pipe@2, + fun(_capture) -> + kryptos@internal@der:encode_context_tag( + 3, + _capture + ) + end + ) + end + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 1168). +-spec encode_tbs_certificate( + builder(), + bitstring(), + kryptos@internal@x509:sig_alg_info(), + bitstring(), + kryptos@x509:validity() +) -> {ok, bitstring()} | {error, nil}. +encode_tbs_certificate(Builder, Serial, Sig_alg, Spki, Validity) -> + gleam@result:'try'( + encode_version(), + fun(Version) -> + gleam@result:'try'( + kryptos@internal@der:encode_integer(Serial), + fun(Serial_int) -> + gleam@result:'try'( + kryptos@internal@x509:encode_algorithm_identifier( + Sig_alg + ), + fun(Sig_alg_der) -> + gleam@result:'try'( + kryptos@internal@x509:encode_name( + erlang:element(2, Builder) + ), + fun(Issuer) -> + gleam@result:'try'( + encode_validity(Validity), + fun(Validity_der) -> + gleam@result:'try'( + kryptos@internal@x509:encode_name( + erlang:element(2, Builder) + ), + fun(Subject) -> + gleam@result:'try'( + encode_extensions( + Builder, + Spki + ), + fun(Extensions) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Version, + Serial_int, + Sig_alg_der, + Issuer, + Validity_der, + Subject, + Spki, + Extensions] + ) + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 380). +?DOC( + " Signs a self-signed certificate with an ECDSA private key.\n" + "\n" + " The public key is derived from the private key and used as both\n" + " the issuer and subject public key.\n" +). +-spec self_signed_with_ecdsa( + builder(), + kryptos@ec:private_key(), + kryptos@hash:hash_algorithm() +) -> {ok, certificate(built())} | {error, nil}. +self_signed_with_ecdsa(Builder, Key, Hash) -> + gleam@result:'try'( + gleam@option:to_result(erlang:element(3, Builder), nil), + fun(Validity) -> + gleam@result:'try'( + kryptos@internal@x509:ecdsa_sig_alg_info(Hash), + fun(Sig_alg) -> + Public_key = kryptos_ffi:ec_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:ec_export_public_key_der(Public_key), + fun(Spki) -> + Serial = case erlang:element(8, Builder) of + {some, S} -> + S; + + none -> + generate_serial_number() + end, + gleam@result:'try'( + encode_tbs_certificate( + Builder, + Serial, + Sig_alg, + Spki, + Validity + ), + fun(Tbs) -> + Signature = kryptos_ffi:ecdsa_sign( + Key, + Tbs, + Hash + ), + gleam@result:'try'( + encode_certificate( + Tbs, + Sig_alg, + Signature + ), + fun(Cert_der) -> + {ok, {built_certificate, Cert_der}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 412). +?DOC( + " Signs a self-signed certificate with an RSA private key using PKCS#1 v1.5 padding.\n" + "\n" + " The public key is derived from the private key and used as both\n" + " the issuer and subject public key.\n" +). +-spec self_signed_with_rsa( + builder(), + kryptos@rsa:private_key(), + kryptos@hash:hash_algorithm() +) -> {ok, certificate(built())} | {error, nil}. +self_signed_with_rsa(Builder, Key, Hash) -> + gleam@result:'try'( + gleam@option:to_result(erlang:element(3, Builder), nil), + fun(Validity) -> + gleam@result:'try'( + kryptos@internal@x509:rsa_sig_alg_info(Hash), + fun(Sig_alg) -> + Public_key = kryptos_ffi:rsa_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:rsa_export_public_key_der(Public_key, spki), + fun(Spki) -> + Serial = case erlang:element(8, Builder) of + {some, S} -> + S; + + none -> + generate_serial_number() + end, + gleam@result:'try'( + encode_tbs_certificate( + Builder, + Serial, + Sig_alg, + Spki, + Validity + ), + fun(Tbs) -> + Signature = kryptos_ffi:rsa_sign( + Key, + Tbs, + Hash, + pkcs1v15 + ), + gleam@result:'try'( + encode_certificate( + Tbs, + Sig_alg, + Signature + ), + fun(Cert_der) -> + {ok, {built_certificate, Cert_der}} + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/certificate.gleam", 444). +?DOC( + " Signs a self-signed certificate with an EdDSA private key.\n" + "\n" + " The public key is derived from the private key and used as both\n" + " the issuer and subject public key. EdDSA has built-in hashing, so no\n" + " hash algorithm parameter is needed.\n" +). +-spec self_signed_with_eddsa(builder(), kryptos@eddsa:private_key()) -> {ok, + certificate(built())} | + {error, nil}. +self_signed_with_eddsa(Builder, Key) -> + gleam@result:'try'( + gleam@option:to_result(erlang:element(3, Builder), nil), + fun(Validity) -> + Sig_alg = case kryptos_ffi:eddsa_private_key_curve(Key) of + ed25519 -> + {sig_alg_info, {oid, [1, 3, 101, 112]}, false}; + + ed448 -> + {sig_alg_info, {oid, [1, 3, 101, 113]}, false} + end, + Public_key = kryptos_ffi:eddsa_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:eddsa_export_public_key_der(Public_key), + fun(Spki) -> + Serial = case erlang:element(8, Builder) of + {some, S} -> + S; + + none -> + generate_serial_number() + end, + gleam@result:'try'( + encode_tbs_certificate( + Builder, + Serial, + Sig_alg, + Spki, + Validity + ), + fun(Tbs) -> + Signature = kryptos_ffi:eddsa_sign(Key, Tbs), + gleam@result:'try'( + encode_certificate(Tbs, Sig_alg, Signature), + fun(Cert_der) -> + {ok, {built_certificate, Cert_der}} + end + ) + end + ) + end + ) + end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@csr.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@csr.erl new file mode 100644 index 0000000..5449f9c --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@x509@csr.erl @@ -0,0 +1,1122 @@ +-module(kryptos@x509@csr). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/x509/csr.gleam"). +-export([new/0, with_subject/2, with_dns_name/2, with_email/2, with_ip/2, to_der/1, version/1, subject/1, public_key/1, signature_algorithm/1, subject_alt_names/1, extensions/1, attributes/1, from_der_unverified/1, from_der/1, sign_with_ecdsa/3, sign_with_rsa/3, sign_with_eddsa/2, to_pem/1, from_pem/1, from_pem_unverified/1]). +-export_type([built/0, parsed/0, csr/1, csr_error/0, builder/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " X.509 Certificate Signing Request (CSR) generation.\n" + "\n" + " Builder for creating PKCS#10 Certificate Signing Requests (CSRs).\n" + " CSRs are used to request certificates from a Certificate Authority.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import gleam/result\n" + " import kryptos/ec\n" + " import kryptos/hash\n" + " import kryptos/x509\n" + " import kryptos/x509/csr\n" + "\n" + " let #(private_key, _) = ec.generate_key_pair(ec.P256)\n" + "\n" + " let subject = x509.name([\n" + " x509.cn(\"example.com\"),\n" + " x509.organization(\"Acme Inc\"),\n" + " x509.country(\"US\"),\n" + " ])\n" + "\n" + " let assert Ok(builder) =\n" + " csr.new()\n" + " |> csr.with_subject(subject)\n" + " |> csr.with_dns_name(\"example.com\")\n" + " |> result.try(csr.with_dns_name(_, \"www.example.com\"))\n" + "\n" + " let assert Ok(my_csr) = csr.sign_with_ecdsa(builder, private_key, hash.Sha256)\n" + "\n" + " let pem = csr.to_pem(my_csr)\n" + " ```\n" +). + +-type built() :: any(). + +-type parsed() :: any(). + +-opaque csr(IXB) :: {built_csr, bitstring()} | + {parsed_csr, + bitstring(), + integer(), + kryptos@x509:name(), + kryptos@x509:public_key(), + kryptos@x509:signature_algorithm(), + list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()}), + list({kryptos@x509:oid(), bitstring()})} | + {gleam_phantom, IXB}. + +-type csr_error() :: invalid_pem | + invalid_structure | + {unsupported_signature_algorithm, kryptos@x509:oid()} | + {unsupported_key_type, kryptos@x509:oid()} | + signature_verification_failed | + {unsupported_version, integer()}. + +-opaque builder() :: {builder, kryptos@x509:name(), kryptos@x509:extensions()}. + +-file("src/kryptos/x509/csr.gleam", 105). +?DOC( + " Creates a new CSR builder with an empty subject and no extensions.\n" + "\n" + " Use the `with_*` functions to configure the builder, then call\n" + " `sign_with_ecdsa` or `sign_with_rsa` to generate the signed CSR.\n" +). +-spec new() -> builder(). +new() -> + {builder, kryptos@x509:name([]), {extensions, []}}. + +-file("src/kryptos/x509/csr.gleam", 113). +?DOC(" Sets the distinguished name subject for the CSR.\n"). +-spec with_subject(builder(), kryptos@x509:name()) -> builder(). +with_subject(Builder, Subject) -> + {builder, Subject, erlang:element(3, Builder)}. + +-file("src/kryptos/x509/csr.gleam", 122). +?DOC( + " Adds a DNS name to the Subject Alternative Names extension.\n" + "\n" + " SANs allow a certificate to be valid for multiple hostnames. Modern\n" + " browsers require the domain to appear in the SAN extension, not just\n" + " the Common Name. The name must contain only ASCII characters.\n" +). +-spec with_dns_name(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_dns_name(Builder, Name) -> + gleam@bool:guard( + not kryptos@internal@utils:is_ascii(Name), + {error, nil}, + fun() -> + {extensions, Sans} = erlang:element(3, Builder), + {ok, + {builder, + erlang:element(2, Builder), + {extensions, [{dns_name, Name} | Sans]}}} + end + ). + +-file("src/kryptos/x509/csr.gleam", 139). +?DOC( + " Adds an email address to the Subject Alternative Names extension.\n" + "\n" + " Used for S/MIME certificates. The email must contain only ASCII characters.\n" +). +-spec with_email(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_email(Builder, Email) -> + gleam@bool:guard( + not kryptos@internal@utils:is_ascii(Email), + {error, nil}, + fun() -> + {extensions, Sans} = erlang:element(3, Builder), + {ok, + {builder, + erlang:element(2, Builder), + {extensions, [{email, Email} | Sans]}}} + end + ). + +-file("src/kryptos/x509/csr.gleam", 153). +?DOC( + " Adds an IP address to the Subject Alternative Names extension.\n" + "\n" + " Accepts IPv4 (e.g., \"192.168.1.1\") or IPv6 (e.g., \"2001:db8::1\") addresses.\n" +). +-spec with_ip(builder(), binary()) -> {ok, builder()} | {error, nil}. +with_ip(Builder, Ip) -> + gleam@result:'try'( + kryptos@internal@utils:parse_ip(Ip), + fun(Parsed) -> + {extensions, Sans} = erlang:element(3, Builder), + {ok, + {builder, + erlang:element(2, Builder), + {extensions, [{ip_address, Parsed} | Sans]}}} + end + ). + +-file("src/kryptos/x509/csr.gleam", 234). +?DOC(" Exports the CSR as DER-encoded bytes.\n"). +-spec to_der(csr(any())) -> bitstring(). +to_der(Csr) -> + case Csr of + {built_csr, Der} -> + Der; + + {parsed_csr, Der@1, _, _, _, _, _, _, _} -> + Der@1 + end. + +-file("src/kryptos/x509/csr.gleam", 366). +?DOC( + " Returns the version of a parsed CSR.\n" + "\n" + " PKCS#10 v1 CSRs always have version 0.\n" +). +-spec version(csr(parsed())) -> integer(). +version(Csr) -> + Version@1 = case Csr of + {parsed_csr, _, Version, _, _, _, _, _, _} -> Version; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"version"/utf8>>, + line => 367, + value => _assert_fail, + start => 11643, + 'end' => 11683, + pattern_start => 11654, + pattern_end => 11677}) + end, + Version@1. + +-file("src/kryptos/x509/csr.gleam", 372). +?DOC(" Returns the subject (distinguished name) of a parsed CSR.\n"). +-spec subject(csr(parsed())) -> kryptos@x509:name(). +subject(Csr) -> + Subject@1 = case Csr of + {parsed_csr, _, _, Subject, _, _, _, _, _} -> Subject; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"subject"/utf8>>, + line => 373, + value => _assert_fail, + start => 11809, + 'end' => 11849, + pattern_start => 11820, + pattern_end => 11843}) + end, + Subject@1. + +-file("src/kryptos/x509/csr.gleam", 378). +?DOC(" Returns the public key embedded in a parsed CSR.\n"). +-spec public_key(csr(parsed())) -> kryptos@x509:public_key(). +public_key(Csr) -> + Public_key@1 = case Csr of + {parsed_csr, _, _, _, Public_key, _, _, _, _} -> Public_key; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"public_key"/utf8>>, + line => 379, + value => _assert_fail, + start => 11974, + 'end' => 12017, + pattern_start => 11985, + pattern_end => 12011}) + end, + Public_key@1. + +-file("src/kryptos/x509/csr.gleam", 384). +?DOC(" Returns the signature algorithm used to sign the CSR.\n"). +-spec signature_algorithm(csr(parsed())) -> kryptos@x509:signature_algorithm(). +signature_algorithm(Csr) -> + Signature_algorithm@1 = case Csr of + {parsed_csr, _, _, _, _, Signature_algorithm, _, _, _} -> Signature_algorithm; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"signature_algorithm"/utf8>>, + line => 385, + value => _assert_fail, + start => 12168, + 'end' => 12220, + pattern_start => 12179, + pattern_end => 12214}) + end, + Signature_algorithm@1. + +-file("src/kryptos/x509/csr.gleam", 390). +?DOC(" Returns the Subject Alternative Names from the CSR.\n"). +-spec subject_alt_names(csr(parsed())) -> list(kryptos@x509:subject_alt_name()). +subject_alt_names(Csr) -> + Subject_alt_names@1 = case Csr of + {parsed_csr, _, _, _, _, _, Subject_alt_names, _, _} -> Subject_alt_names; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"subject_alt_names"/utf8>>, + line => 391, + value => _assert_fail, + start => 12378, + 'end' => 12428, + pattern_start => 12389, + pattern_end => 12422}) + end, + Subject_alt_names@1. + +-file("src/kryptos/x509/csr.gleam", 400). +?DOC( + " Returns any extensions beyond SANs as raw (OID, critical, value) tuples.\n" + "\n" + " This allows access to extensions that kryptos doesn't have typed\n" + " representations for. The Bool indicates whether the extension was\n" + " marked as critical per RFC 5280.\n" +). +-spec extensions(csr(parsed())) -> list({kryptos@x509:oid(), + boolean(), + bitstring()}). +extensions(Csr) -> + Extensions@1 = case Csr of + {parsed_csr, _, _, _, _, _, _, Extensions, _} -> Extensions; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"extensions"/utf8>>, + line => 401, + value => _assert_fail, + start => 12786, + 'end' => 12829, + pattern_start => 12797, + pattern_end => 12823}) + end, + Extensions@1. + +-file("src/kryptos/x509/csr.gleam", 409). +?DOC( + " Returns any non-extension attributes as raw (OID, value) pairs.\n" + "\n" + " Most CSRs only have the extensionRequest attribute, so this is\n" + " typically empty.\n" +). +-spec attributes(csr(parsed())) -> list({kryptos@x509:oid(), bitstring()}). +attributes(Csr) -> + Attributes@1 = case Csr of + {parsed_csr, _, _, _, _, _, _, _, Attributes} -> Attributes; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"attributes"/utf8>>, + line => 410, + value => _assert_fail, + start => 13077, + 'end' => 13120, + pattern_start => 13088, + pattern_end => 13114}) + end, + Attributes@1. + +-file("src/kryptos/x509/csr.gleam", 421). +-spec parse_version(bitstring()) -> {ok, integer()} | {error, csr_error()}. +parse_version(Bytes) -> + case Bytes of + <<0>> -> + {ok, 0}; + + <> -> + {error, {unsupported_version, V}}; + + _ -> + {error, invalid_structure} + end. + +-file("src/kryptos/x509/csr.gleam", 485). +-spec parse_single_attribute(bitstring()) -> {ok, + {kryptos@x509:oid(), bitstring()}} | + {error, nil}. +parse_single_attribute(Bytes) -> + gleam@result:'try'( + kryptos@internal@der:parse_oid(Bytes), + fun(_use0) -> + {Oid_components, After_oid} = _use0, + gleam@result:'try'( + kryptos@internal@der:parse_set(After_oid), + fun(_use0@1) -> + {Value, Remaining} = _use0@1, + gleam@bool:guard( + Remaining /= <<>>, + {error, nil}, + fun() -> {ok, {{oid, Oid_components}, Value}} end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 506). +-spec parse_extensions( + bitstring(), + list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()}) +) -> {ok, + {list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()})}} | + {error, nil}. +parse_extensions(Bytes, Sans, Exts) -> + case Bytes of + <<>> -> + {ok, {Sans, lists:reverse(Exts)}}; + + _ -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Ext_bytes, Rest} = _use0, + gleam@result:'try'( + kryptos@internal@x509:parse_single_extension(Ext_bytes), + fun(_use0@1) -> + {Oid, Is_critical, Value} = _use0@1, + case Oid of + {oid, [2, 5, 29, 17]} -> + gleam@result:'try'( + kryptos@internal@x509:parse_san_extension( + Value, + false + ), + fun(New_sans) -> + parse_extensions( + Rest, + lists:append(Sans, New_sans), + Exts + ) + end + ); + + _ -> + parse_extensions( + Rest, + Sans, + [{Oid, Is_critical, Value} | Exts] + ) + end + end + ) + end + ) + end. + +-file("src/kryptos/x509/csr.gleam", 492). +-spec parse_extension_request(bitstring()) -> {ok, + {list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()})}} | + {error, nil}. +parse_extension_request(Bytes) -> + gleam@bool:guard( + erlang:byte_size(Bytes) =:= 0, + {ok, {[], []}}, + fun() -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Exts_content, _} = _use0, + parse_extensions(Exts_content, [], []) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 450). +-spec parse_attributes_content( + bitstring(), + list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()}), + list({kryptos@x509:oid(), bitstring()}) +) -> {ok, + {list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()}), + list({kryptos@x509:oid(), bitstring()})}} | + {error, nil}. +parse_attributes_content(Bytes, Sans, Exts, Attrs) -> + case Bytes of + <<>> -> + {ok, {Sans, lists:reverse(Exts), lists:reverse(Attrs)}}; + + _ -> + gleam@result:'try'( + kryptos@internal@der:parse_sequence(Bytes), + fun(_use0) -> + {Attr_bytes, Rest} = _use0, + gleam@result:'try'( + parse_single_attribute(Attr_bytes), + fun(_use0@1) -> + {Oid, Value} = _use0@1, + case Oid of + {oid, [1, 2, 840, 113549, 1, 9, 14]} -> + gleam@result:'try'( + parse_extension_request(Value), + fun(_use0@2) -> + {New_sans, New_exts} = _use0@2, + parse_attributes_content( + Rest, + lists:append(Sans, New_sans), + lists:append(Exts, New_exts), + Attrs + ) + end + ); + + _ -> + parse_attributes_content( + Rest, + Sans, + Exts, + [{Oid, Value} | Attrs] + ) + end + end + ) + end + ) + end. + +-file("src/kryptos/x509/csr.gleam", 429). +-spec parse_attributes(bitstring()) -> {ok, + {list(kryptos@x509:subject_alt_name()), + list({kryptos@x509:oid(), boolean(), bitstring()}), + list({kryptos@x509:oid(), bitstring()})}} | + {error, nil}. +parse_attributes(Bytes) -> + case Bytes of + <<16#a0, _/bitstring>> -> + case kryptos@internal@der:parse_context_tag(Bytes, 0) of + {ok, {Attrs_content, _}} -> + parse_attributes_content(Attrs_content, [], [], []); + + {error, _} -> + {error, nil} + end; + + _ -> + {ok, {[], [], []}} + end. + +-file("src/kryptos/x509/csr.gleam", 279). +?DOC( + " Parse a DER-encoded CSR without verifying the signature.\n" + "\n" + " Useful for debugging malformed or partially valid CSRs.\n" + " The parsed fields may not be trustworthy since the signature\n" + " was not verified.\n" +). +-spec from_der_unverified(bitstring()) -> {ok, csr(parsed())} | + {error, csr_error()}. +from_der_unverified(Der) -> + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_sequence(Der), + gleam@result:replace_error(_pipe, invalid_structure) + end, + fun(_use0) -> + {Csr_content, Remaining} = _use0, + gleam@bool:guard( + erlang:byte_size(Remaining) /= 0, + {error, invalid_structure}, + fun() -> + gleam@result:'try'( + begin + _pipe@1 = kryptos@internal@x509:parse_sequence_with_header( + Csr_content + ), + gleam@result:replace_error( + _pipe@1, + invalid_structure + ) + end, + fun(_use0@1) -> + {Cert_req_info_bytes, After_info} = _use0@1, + gleam@result:'try'( + begin + _pipe@2 = kryptos@internal@der:parse_sequence( + Cert_req_info_bytes + ), + gleam@result:replace_error( + _pipe@2, + invalid_structure + ) + end, + fun(_use0@2) -> + {Cert_req_info_content, _} = _use0@2, + gleam@result:'try'( + begin + _pipe@3 = kryptos@internal@der:parse_integer( + Cert_req_info_content + ), + gleam@result:replace_error( + _pipe@3, + invalid_structure + ) + end, + fun(_use0@3) -> + {Version_bytes, After_version} = _use0@3, + gleam@result:'try'( + parse_version(Version_bytes), + fun(Version) -> + gleam@result:'try'( + begin + _pipe@4 = kryptos@internal@der:parse_sequence( + After_version + ), + gleam@result:replace_error( + _pipe@4, + invalid_structure + ) + end, + fun(_use0@4) -> + {Subject_bytes, + After_subject} = _use0@4, + gleam@result:'try'( + begin + _pipe@5 = kryptos@internal@x509:parse_name( + Subject_bytes + ), + gleam@result:replace_error( + _pipe@5, + invalid_structure + ) + end, + fun(Subject) -> + gleam@result:'try'( + begin + _pipe@6 = kryptos@internal@x509:parse_sequence_with_header( + After_subject + ), + gleam@result:replace_error( + _pipe@6, + invalid_structure + ) + end, + fun( + _use0@5 + ) -> + {Spki_bytes, + After_spki} = _use0@5, + gleam@result:'try'( + begin + _pipe@7 = kryptos@internal@x509:parse_public_key( + Spki_bytes + ), + gleam@result:map_error( + _pipe@7, + fun( + Oid + ) -> + case Oid of + {oid, + []} -> + invalid_structure; + + _ -> + {unsupported_key_type, + Oid} + end + end + ) + end, + fun( + Public_key + ) -> + gleam@result:'try'( + begin + _pipe@8 = parse_attributes( + After_spki + ), + gleam@result:replace_error( + _pipe@8, + invalid_structure + ) + end, + fun( + _use0@6 + ) -> + {Subject_alt_names, + Extensions, + Attributes} = _use0@6, + gleam@result:'try'( + begin + _pipe@9 = kryptos@internal@der:parse_sequence( + After_info + ), + gleam@result:replace_error( + _pipe@9, + invalid_structure + ) + end, + fun( + _use0@7 + ) -> + {Sig_alg_bytes, + After_sig_alg} = _use0@7, + gleam@result:'try'( + begin + _pipe@10 = kryptos@internal@x509:parse_signature_algorithm( + Sig_alg_bytes + ), + gleam@result:map_error( + _pipe@10, + fun(Field@0) -> {unsupported_signature_algorithm, Field@0} end + ) + end, + fun( + Signature_algorithm + ) -> + gleam@result:'try'( + begin + _pipe@11 = kryptos@internal@der:parse_bit_string( + After_sig_alg + ), + gleam@result:replace_error( + _pipe@11, + invalid_structure + ) + end, + fun( + _use0@8 + ) -> + {_, + _} = _use0@8, + {ok, + {parsed_csr, + Der, + Version, + Subject, + Public_key, + Signature_algorithm, + Subject_alt_names, + Extensions, + Attributes}} + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 535). +-spec verify_signature(csr(parsed())) -> {ok, nil} | {error, csr_error()}. +verify_signature(Csr) -> + {Der@1, Public_key@1, Signature_algorithm@1} = case Csr of + {parsed_csr, Der, _, _, Public_key, Signature_algorithm, _, _, _} -> { + Der, + Public_key, + Signature_algorithm}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"kryptos/x509/csr"/utf8>>, + function => <<"verify_signature"/utf8>>, + line => 536, + value => _assert_fail, + start => 16508, + 'end' => 16579, + pattern_start => 16519, + pattern_end => 16573}) + end, + gleam@result:'try'( + begin + _pipe = kryptos@internal@der:parse_sequence(Der@1), + gleam@result:replace_error(_pipe, invalid_structure) + end, + fun(_use0) -> + {Csr_content, _} = _use0, + gleam@result:'try'( + begin + _pipe@1 = kryptos@internal@x509:parse_sequence_with_header( + Csr_content + ), + gleam@result:replace_error(_pipe@1, invalid_structure) + end, + fun(_use0@1) -> + {Cert_req_info_bytes, After_info} = _use0@1, + gleam@result:'try'( + begin + _pipe@2 = kryptos@internal@der:parse_sequence( + After_info + ), + gleam@result:replace_error( + _pipe@2, + invalid_structure + ) + end, + fun(_use0@2) -> + {_, After_sig_alg} = _use0@2, + gleam@result:'try'( + begin + _pipe@3 = kryptos@internal@der:parse_bit_string( + After_sig_alg + ), + gleam@result:replace_error( + _pipe@3, + invalid_structure + ) + end, + fun(_use0@3) -> + {Signature, _} = _use0@3, + Verified = kryptos@internal@x509:verify_signature( + Public_key@1, + Cert_req_info_bytes, + Signature, + Signature_algorithm@1 + ), + case Verified of + true -> + {ok, nil}; + + false -> + {error, + signature_verification_failed} + end + end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 259). +?DOC(" Parse a DER-encoded CSR and verify its signature.\n"). +-spec from_der(bitstring()) -> {ok, csr(parsed())} | {error, csr_error()}. +from_der(Der) -> + gleam@result:'try'( + from_der_unverified(Der), + fun(Parsed) -> + gleam@result:'try'( + verify_signature(Parsed), + fun(_) -> {ok, Parsed} end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 574). +-spec encode_csr(bitstring(), kryptos@internal@x509:sig_alg_info(), bitstring()) -> {ok, + bitstring()} | + {error, nil}. +encode_csr(Cert_request_info, Sig_alg, Signature) -> + gleam@result:'try'( + kryptos@internal@x509:encode_algorithm_identifier(Sig_alg), + fun(Sig_alg_der) -> + gleam@result:'try'( + kryptos@internal@der:encode_bit_string(Signature), + fun(Sig_bits) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Cert_request_info, Sig_alg_der, Sig_bits] + ) + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 621). +-spec encode_extensions(kryptos@x509:extensions()) -> {ok, bitstring()} | + {error, nil}. +encode_extensions(Extensions) -> + {extensions, Sans} = Extensions, + _pipe = kryptos@internal@x509:encode_san_extension(Sans, false), + gleam@result:'try'(_pipe, fun kryptos@internal@der:encode_sequence/1). + +-file("src/kryptos/x509/csr.gleam", 608). +-spec encode_extension_request(kryptos@x509:extensions()) -> {ok, bitstring()} | + {error, nil}. +encode_extension_request(Extensions) -> + {oid, Ext_req_components} = {oid, [1, 2, 840, 113549, 1, 9, 14]}, + gleam@result:'try'( + kryptos@internal@der:encode_oid(Ext_req_components), + fun(Oid_encoded) -> _pipe = Extensions, + _pipe@1 = encode_extensions(_pipe), + _pipe@2 = gleam@result:'try'( + _pipe@1, + fun kryptos@internal@der:encode_set/1 + ), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(Set_encoded) -> + gleam_stdlib:bit_array_concat([Oid_encoded, Set_encoded]) + end + ), + gleam@result:'try'( + _pipe@3, + fun kryptos@internal@der:encode_sequence/1 + ) end + ). + +-file("src/kryptos/x509/csr.gleam", 598). +-spec encode_attributes(kryptos@x509:extensions()) -> {ok, bitstring()} | + {error, nil}. +encode_attributes(Extensions) -> + case gleam@list:is_empty(erlang:element(2, Extensions)) of + true -> + kryptos@internal@der:encode_context_tag(0, <<>>); + + false -> + _pipe = Extensions, + _pipe@1 = encode_extension_request(_pipe), + gleam@result:'try'( + _pipe@1, + fun(_capture) -> + kryptos@internal@der:encode_context_tag(0, _capture) + end + ) + end. + +-file("src/kryptos/x509/csr.gleam", 588). +-spec encode_certification_request_info(builder(), bitstring()) -> {ok, + bitstring()} | + {error, nil}. +encode_certification_request_info(Builder, Spki) -> + gleam@result:'try'( + kryptos@internal@der:encode_integer(<<0>>), + fun(Version) -> + gleam@result:'try'( + kryptos@internal@x509:encode_name(erlang:element(2, Builder)), + fun(Subject) -> + gleam@result:'try'( + encode_attributes(erlang:element(3, Builder)), + fun(Attributes) -> + kryptos@internal@der:encode_sequence( + gleam_stdlib:bit_array_concat( + [Version, Subject, Spki, Attributes] + ) + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 173). +?DOC( + " Signs the CSR with an ECDSA private key.\n" + "\n" + " The public key is derived from the private key and included in the CSR.\n" + " Recommended hash: `Sha256` for P-256, `Sha384` for P-384, `Sha512` for\n" + " P-521. `Sha1` is supported for legacy compatibility but is\n" + " cryptographically weak.\n" +). +-spec sign_with_ecdsa( + builder(), + kryptos@ec:private_key(), + kryptos@hash:hash_algorithm() +) -> {ok, csr(built())} | {error, nil}. +sign_with_ecdsa(Builder, Key, Hash) -> + gleam@result:'try'( + kryptos@internal@x509:ecdsa_sig_alg_info(Hash), + fun(Sig_alg) -> + Public_key = kryptos_ffi:ec_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:ec_export_public_key_der(Public_key), + fun(Spki) -> + gleam@result:'try'( + encode_certification_request_info(Builder, Spki), + fun(Cert_request_info) -> + Signature = kryptos_ffi:ecdsa_sign( + Key, + Cert_request_info, + Hash + ), + gleam@result:'try'( + encode_csr( + Cert_request_info, + Sig_alg, + Signature + ), + fun(Csr_der) -> {ok, {built_csr, Csr_der}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 196). +?DOC( + " Signs the CSR with an RSA private key using PKCS#1 v1.5 padding.\n" + "\n" + " The public key is derived from the private key and included in the CSR.\n" + " Recommended hash: `Sha256` for 2048-bit keys, `Sha384` or `Sha512` for\n" + " 3072-bit or larger keys. `Sha1` is supported for legacy compatibility\n" + " but is cryptographically weak.\n" +). +-spec sign_with_rsa( + builder(), + kryptos@rsa:private_key(), + kryptos@hash:hash_algorithm() +) -> {ok, csr(built())} | {error, nil}. +sign_with_rsa(Builder, Key, Hash) -> + gleam@result:'try'( + kryptos@internal@x509:rsa_sig_alg_info(Hash), + fun(Sig_alg) -> + Public_key = kryptos_ffi:rsa_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:rsa_export_public_key_der(Public_key, spki), + fun(Spki) -> + gleam@result:'try'( + encode_certification_request_info(Builder, Spki), + fun(Cert_request_info) -> + Signature = kryptos_ffi:rsa_sign( + Key, + Cert_request_info, + Hash, + pkcs1v15 + ), + gleam@result:'try'( + encode_csr( + Cert_request_info, + Sig_alg, + Signature + ), + fun(Csr_der) -> {ok, {built_csr, Csr_der}} end + ) + end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 217). +?DOC( + " Signs the CSR with an EdDSA private key (Ed25519 or Ed448).\n" + "\n" + " **Note**: Support for EdDSA is limited with browsers and certificate\n" + " authorities.\n" +). +-spec sign_with_eddsa(builder(), kryptos@eddsa:private_key()) -> {ok, + csr(built())} | + {error, nil}. +sign_with_eddsa(Builder, Key) -> + Sig_alg = kryptos@internal@x509:eddsa_sig_alg_info( + kryptos_ffi:eddsa_private_key_curve(Key) + ), + Public_key = kryptos_ffi:eddsa_public_key_from_private(Key), + gleam@result:'try'( + kryptos_ffi:eddsa_export_public_key_der(Public_key), + fun(Spki) -> + gleam@result:'try'( + encode_certification_request_info(Builder, Spki), + fun(Cert_request_info) -> + Signature = kryptos_ffi:eddsa_sign(Key, Cert_request_info), + gleam@result:'try'( + encode_csr(Cert_request_info, Sig_alg, Signature), + fun(Csr_der) -> {ok, {built_csr, Csr_der}} end + ) + end + ) + end + ). + +-file("src/kryptos/x509/csr.gleam", 245). +?DOC( + " Exports the CSR as a PEM-encoded string.\n" + "\n" + " This is the format typically required when submitting a CSR to a\n" + " Certificate Authority.\n" +). +-spec to_pem(csr(any())) -> binary(). +to_pem(Csr) -> + kryptos@internal@x509:encode_pem( + to_der(Csr), + <<"-----BEGIN CERTIFICATE REQUEST-----"/utf8>>, + <<"-----END CERTIFICATE REQUEST-----"/utf8>> + ). + +-file("src/kryptos/x509/csr.gleam", 414). +-spec decode_csr_pem(binary()) -> {ok, bitstring()} | {error, nil}. +decode_csr_pem(Pem) -> + case kryptos@internal@x509:decode_pem( + Pem, + <<"-----BEGIN CERTIFICATE REQUEST-----"/utf8>>, + <<"-----END CERTIFICATE REQUEST-----"/utf8>> + ) of + {ok, Der} -> + {ok, Der}; + + {error, _} -> + kryptos@internal@x509:decode_pem( + Pem, + <<"-----BEGIN NEW CERTIFICATE REQUEST-----"/utf8>>, + <<"-----END NEW CERTIFICATE REQUEST-----"/utf8>> + ) + end. + +-file("src/kryptos/x509/csr.gleam", 253). +?DOC( + " Parse a PEM-encoded CSR and verify its signature.\n" + "\n" + " Returns an error if the PEM is invalid, the structure is malformed,\n" + " or the signature doesn't verify against the embedded public key.\n" +). +-spec from_pem(binary()) -> {ok, csr(parsed())} | {error, csr_error()}. +from_pem(Pem) -> + gleam@result:'try'( + begin + _pipe = decode_csr_pem(Pem), + gleam@result:replace_error(_pipe, invalid_pem) + end, + fun(Der) -> from_der(Der) end + ). + +-file("src/kryptos/x509/csr.gleam", 269). +?DOC( + " Parse a PEM-encoded CSR without verifying the signature.\n" + "\n" + " Useful for debugging malformed or partially valid CSRs.\n" + " The parsed fields may not be trustworthy.\n" +). +-spec from_pem_unverified(binary()) -> {ok, csr(parsed())} | + {error, csr_error()}. +from_pem_unverified(Pem) -> + gleam@result:'try'( + begin + _pipe = decode_csr_pem(Pem), + gleam@result:replace_error(_pipe, invalid_pem) + end, + fun(Der) -> from_der_unverified(Der) end + ). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos@xdh.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos@xdh.erl new file mode 100644 index 0000000..42c5bb0 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos@xdh.erl @@ -0,0 +1,265 @@ +-module(kryptos@xdh). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/kryptos/xdh.gleam"). +-export([key_size/1, generate_key_pair/1, compute_shared_secret/2, from_bytes/2, to_bytes/1, public_key_from_bytes/2, public_key_to_bytes/1, from_pem/1, from_der/1, to_pem/1, to_der/1, public_key_from_pem/1, public_key_from_der/1, public_key_to_pem/1, public_key_to_der/1, public_key_from_private_key/1, curve/1, public_key_curve/1]). +-export_type([private_key/0, public_key/0, curve/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " X25519 and X448 (XDH) key agreement.\n" + "\n" + " XDH provides Diffie-Hellman key agreement using Montgomery curves X25519\n" + " and X448. These curves are designed specifically for key agreement and\n" + " offer excellent performance with strong security properties.\n" + "\n" + " ## Example\n" + "\n" + " ```gleam\n" + " import kryptos/xdh\n" + "\n" + " // Alice generates a key pair\n" + " let #(alice_private, alice_public) = xdh.generate_key_pair(xdh.X25519)\n" + "\n" + " // Bob generates a key pair\n" + " let #(bob_private, bob_public) = xdh.generate_key_pair(xdh.X25519)\n" + "\n" + " // Both compute the same shared secret\n" + " let assert Ok(alice_shared) = xdh.compute_shared_secret(alice_private, bob_public)\n" + " let assert Ok(bob_shared) = xdh.compute_shared_secret(bob_private, alice_public)\n" + " // alice_shared == bob_shared\n" + " ```\n" +). + +-type private_key() :: any(). + +-type public_key() :: any(). + +-type curve() :: x25519 | x448. + +-file("src/kryptos/xdh.gleam", 42). +?DOC(" Returns the key size in bytes for the given curve.\n"). +-spec key_size(curve()) -> integer(). +key_size(Curve) -> + case Curve of + x25519 -> + 32; + + x448 -> + 56 + end. + +-file("src/kryptos/xdh.gleam", 52). +?DOC(" Generates a new XDH key pair.\n"). +-spec generate_key_pair(curve()) -> {private_key(), public_key()}. +generate_key_pair(Curve) -> + kryptos_ffi:xdh_generate_key_pair(Curve). + +-file("src/kryptos/xdh.gleam", 78). +-spec is_all_zeros(bitstring()) -> boolean(). +is_all_zeros(Bytes) -> + case Bytes of + <<>> -> + true; + + <<0, Rest/binary>> -> + is_all_zeros(Rest); + + _ -> + false + end. + +-file("src/kryptos/xdh.gleam", 64). +?DOC( + " Computes a shared secret using XDH key agreement.\n" + "\n" + " Both parties compute the same shared secret by combining their private key\n" + " with the other party's public key.\n" + "\n" + " Returns `Error(Nil)` if the keys use different curves, the result is an\n" + " all-zero shared secret (low-order point attack), or another error occurs.\n" + "\n" + " The raw shared secret should be passed through a KDF (like HKDF) before\n" + " use as a symmetric key.\n" +). +-spec compute_shared_secret(private_key(), public_key()) -> {ok, bitstring()} | + {error, nil}. +compute_shared_secret(Private_key, Peer_public_key) -> + gleam@result:'try'( + kryptos_ffi:xdh_compute_shared_secret(Private_key, Peer_public_key), + fun(Shared) -> case is_all_zeros(Shared) of + true -> + {error, nil}; + + false -> + {ok, Shared} + end end + ). + +-file("src/kryptos/xdh.gleam", 103). +?DOC( + " Imports a private key from raw bytes.\n" + "\n" + " The bytes should be the raw private key scalar:\n" + " - X25519: 32 bytes\n" + " - X448: 56 bytes\n" + "\n" + " Returns the private key and its corresponding public key, or `Error(Nil)`\n" + " if the bytes are invalid.\n" +). +-spec from_bytes(curve(), bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_bytes(Curve, Private_bytes) -> + kryptos_ffi:xdh_private_key_from_bytes(Curve, Private_bytes). + +-file("src/kryptos/xdh.gleam", 115). +?DOC( + " Exports a private key to raw bytes.\n" + "\n" + " Returns the raw private key scalar:\n" + " - X25519: 32 bytes\n" + " - X448: 56 bytes\n" +). +-spec to_bytes(private_key()) -> bitstring(). +to_bytes(Key) -> + kryptos_ffi:xdh_private_key_to_bytes(Key). + +-file("src/kryptos/xdh.gleam", 126). +?DOC( + " Imports a public key from raw bytes.\n" + "\n" + " The bytes should be the raw public key point:\n" + " - X25519: 32 bytes\n" + " - X448: 56 bytes\n" + "\n" + " Returns the public key or `Error(Nil)` if the bytes are invalid.\n" +). +-spec public_key_from_bytes(curve(), bitstring()) -> {ok, public_key()} | + {error, nil}. +public_key_from_bytes(Curve, Public_bytes) -> + kryptos_ffi:xdh_public_key_from_bytes(Curve, Public_bytes). + +-file("src/kryptos/xdh.gleam", 138). +?DOC( + " Exports a public key to raw bytes.\n" + "\n" + " Returns the raw public key point:\n" + " - X25519: 32 bytes\n" + " - X448: 56 bytes\n" +). +-spec public_key_to_bytes(public_key()) -> bitstring(). +public_key_to_bytes(Key) -> + kryptos_ffi:xdh_public_key_to_bytes(Key). + +-file("src/kryptos/xdh.gleam", 145). +?DOC( + " Imports an XDH private key from PEM-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_pem(binary()) -> {ok, {private_key(), public_key()}} | {error, nil}. +from_pem(Pem) -> + kryptos_ffi:xdh_import_private_key_pem(Pem). + +-file("src/kryptos/xdh.gleam", 152). +?DOC( + " Imports an XDH private key from DER-encoded data.\n" + "\n" + " The key must be in PKCS#8 format.\n" +). +-spec from_der(bitstring()) -> {ok, {private_key(), public_key()}} | + {error, nil}. +from_der(Der) -> + kryptos_ffi:xdh_import_private_key_der(Der). + +-file("src/kryptos/xdh.gleam", 157). +?DOC( + " Exports an XDH private key to PEM format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_pem(private_key()) -> {ok, binary()} | {error, nil}. +to_pem(Key) -> + _pipe = kryptos_ffi:xdh_export_private_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/xdh.gleam", 170). +?DOC( + " Exports an XDH private key to DER format.\n" + "\n" + " The key is exported in PKCS#8 format.\n" +). +-spec to_der(private_key()) -> {ok, bitstring()} | {error, nil}. +to_der(Key) -> + kryptos_ffi:xdh_export_private_key_der(Key). + +-file("src/kryptos/xdh.gleam", 177). +?DOC( + " Imports an XDH public key from PEM-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_pem(binary()) -> {ok, public_key()} | {error, nil}. +public_key_from_pem(Pem) -> + kryptos_ffi:xdh_import_public_key_pem(Pem). + +-file("src/kryptos/xdh.gleam", 184). +?DOC( + " Imports an XDH public key from DER-encoded data.\n" + "\n" + " The key must be in SPKI format.\n" +). +-spec public_key_from_der(bitstring()) -> {ok, public_key()} | {error, nil}. +public_key_from_der(Der) -> + kryptos_ffi:xdh_import_public_key_der(Der). + +-file("src/kryptos/xdh.gleam", 189). +?DOC( + " Exports an XDH public key to PEM format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_pem(public_key()) -> {ok, binary()} | {error, nil}. +public_key_to_pem(Key) -> + _pipe = kryptos_ffi:xdh_export_public_key_pem(Key), + gleam@result:map( + _pipe, + fun(Pem) -> <<(gleam@string:trim_end(Pem))/binary, "\n"/utf8>> end + ). + +-file("src/kryptos/xdh.gleam", 203). +?DOC( + " Exports an XDH public key to DER format.\n" + "\n" + " The key is exported in SPKI format.\n" +). +-spec public_key_to_der(public_key()) -> {ok, bitstring()} | {error, nil}. +public_key_to_der(Key) -> + kryptos_ffi:xdh_export_public_key_der(Key). + +-file("src/kryptos/xdh.gleam", 208). +?DOC(" Derives the public key from an XDH private key.\n"). +-spec public_key_from_private_key(private_key()) -> public_key(). +public_key_from_private_key(Key) -> + kryptos_ffi:xdh_public_key_from_private(Key). + +-file("src/kryptos/xdh.gleam", 213). +?DOC(" Returns the curve for an XDH private key.\n"). +-spec curve(private_key()) -> curve(). +curve(Key) -> + kryptos_ffi:xdh_private_key_curve(Key). + +-file("src/kryptos/xdh.gleam", 218). +?DOC(" Returns the curve for an XDH public key.\n"). +-spec public_key_curve(public_key()) -> curve(). +public_key_curve(Key) -> + kryptos_ffi:xdh_public_key_curve(Key). diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.erl b/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.erl new file mode 100644 index 0000000..0874b96 --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.erl @@ -0,0 +1,1636 @@ +-module(kryptos_ffi). + +-include_lib("kryptos/include/kryptos@aead_Ccm.hrl"). +-include_lib("kryptos/include/kryptos@aead_ChaCha20Poly1305.hrl"). +-include_lib("kryptos/include/kryptos@aead_Gcm.hrl"). +-include_lib("kryptos/include/kryptos@block_Aes.hrl"). +-include_lib("kryptos/include/kryptos@block_Cbc.hrl"). +-include_lib("kryptos/include/kryptos@block_Ctr.hrl"). +-include_lib("kryptos/include/kryptos@block_Ecb.hrl"). +-include_lib("kryptos/include/kryptos@rsa_Oaep.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export([ + aead_open/5, + aead_seal/4, + aes_decrypt_block/2, + aes_encrypt_block/2, + block_cipher_decrypt/2, + block_cipher_encrypt/2, + constant_time_equal/2, + ec_export_private_key_der/1, + ec_export_private_key_pem/1, + ec_export_public_key_der/1, + ec_export_public_key_pem/1, + ec_generate_key_pair/1, + ec_import_private_key_der/1, + ec_import_private_key_pem/1, + ec_import_public_key_der/1, + ec_import_public_key_pem/1, + ec_private_key_from_bytes/2, + ec_private_key_to_bytes/1, + ec_public_key_from_private/1, + ec_public_key_from_raw_point/2, + ec_public_key_to_raw_point/1, + ec_public_key_from_x509/1, + ec_private_key_curve/1, + ec_public_key_curve/1, + ecdh_compute_shared_secret/2, + ecdsa_sign/3, + ecdsa_verify/4, + eddsa_export_private_key_der/1, + eddsa_export_private_key_pem/1, + eddsa_export_public_key_der/1, + eddsa_export_public_key_pem/1, + eddsa_generate_key_pair/1, + eddsa_import_private_key_der/1, + eddsa_import_private_key_pem/1, + eddsa_import_public_key_der/1, + eddsa_import_public_key_pem/1, + eddsa_private_key_from_bytes/2, + eddsa_private_key_to_bytes/1, + eddsa_public_key_from_bytes/2, + eddsa_public_key_from_private/1, + eddsa_public_key_to_bytes/1, + eddsa_private_key_curve/1, + eddsa_public_key_curve/1, + eddsa_sign/2, + eddsa_verify/3, + hash_new/1, + hash_update/2, + hash_final/1, + hmac_new/2, + mod_pow/3, + pbkdf2_derive/5, + random_bytes/1, + rsa_decrypt/3, + rsa_encrypt/3, + rsa_export_private_key_der/2, + rsa_export_private_key_pem/2, + rsa_export_public_key_der/2, + rsa_export_public_key_pem/2, + rsa_generate_key_pair/1, + rsa_import_private_key_der/2, + rsa_import_private_key_pem/2, + rsa_import_public_key_der/2, + rsa_import_public_key_pem/2, + rsa_private_key_from_pkcs8/1, + rsa_public_key_from_private/1, + rsa_public_key_from_x509/1, + rsa_private_key_modulus_bits/1, + rsa_public_key_modulus_bits/1, + rsa_private_key_modulus/1, + rsa_public_key_modulus/1, + rsa_private_key_public_exponent/1, + rsa_public_key_public_exponent/1, + rsa_private_key_public_exponent_bytes/1, + rsa_public_key_exponent_bytes/1, + rsa_private_key_private_exponent_bytes/1, + rsa_private_key_prime1/1, + rsa_private_key_prime2/1, + rsa_private_key_exponent1/1, + rsa_private_key_exponent2/1, + rsa_private_key_coefficient/1, + rsa_public_key_from_components/2, + rsa_private_key_from_full_components/8, + rsa_sign/4, + rsa_verify/5, + xdh_compute_shared_secret/2, + xdh_export_private_key_der/1, + xdh_export_private_key_pem/1, + xdh_export_public_key_der/1, + xdh_export_public_key_pem/1, + xdh_generate_key_pair/1, + xdh_import_private_key_der/1, + xdh_import_private_key_pem/1, + xdh_import_public_key_der/1, + xdh_import_public_key_pem/1, + xdh_private_key_from_bytes/2, + xdh_private_key_to_bytes/1, + xdh_public_key_from_bytes/2, + xdh_public_key_from_private/1, + xdh_public_key_to_bytes/1, + xdh_private_key_curve/1, + xdh_public_key_curve/1 +]). + +%%------------------------------------------------------------------------------ +%% OTP Version Detection +%%------------------------------------------------------------------------------ + +%% OTP 28 changed PrivateKeyInfo from a 5-tuple to a 6-tuple (OneAsymmetricKey) +%% and renamed the algorithm identifier tuple. +otp_version() -> + list_to_integer(erlang:system_info(otp_release)). + +is_otp_28_or_later() -> + otp_version() >= 28. + +%%------------------------------------------------------------------------------ +%% Utilities & Random +%%------------------------------------------------------------------------------ + +random_bytes(Length) when Length < 0 -> + crypto:strong_rand_bytes(0); +random_bytes(Length) -> + crypto:strong_rand_bytes(Length). + +constant_time_equal(A, B) when byte_size(A) =:= byte_size(B) -> + crypto:hash_equals(A, B); +constant_time_equal(_, _) -> + false. + +%%------------------------------------------------------------------------------ +%% Hash Functions +%%------------------------------------------------------------------------------ + +hash_algorithm_name(sha1) -> + sha; +hash_algorithm_name(sha224) -> + sha224; +hash_algorithm_name(sha256) -> + sha256; +hash_algorithm_name(sha384) -> + sha384; +hash_algorithm_name(sha512) -> + sha512; +hash_algorithm_name(sha512x224) -> + sha512_224; +hash_algorithm_name(sha512x256) -> + sha512_256; +hash_algorithm_name(sha3x224) -> + sha3_224; +hash_algorithm_name(sha3x256) -> + sha3_256; +hash_algorithm_name(sha3x384) -> + sha3_384; +hash_algorithm_name(sha3x512) -> + sha3_512; +hash_algorithm_name({shake128, _OutputLength}) -> + shake128; +hash_algorithm_name({shake256, _OutputLength}) -> + shake256; +hash_algorithm_name(Name) -> + Name. + +hash_new({shake128, OutputLength}) -> + try + _ = crypto:hash_xof(shake128, <<>>, 8), + {ok, {xof, shake128, [], OutputLength}} + catch + _:_ -> + {error, nil} + end; +hash_new({shake256, OutputLength}) -> + try + _ = crypto:hash_xof(shake256, <<>>, 8), + {ok, {xof, shake256, [], OutputLength}} + catch + _:_ -> + {error, nil} + end; +hash_new(Algorithm) -> + try + Name = hash_algorithm_name(Algorithm), + {ok, crypto:hash_init(Name)} + catch + _:_ -> + {error, nil} + end. + +hash_update({xof, Algorithm, AccData, OutputLength}, Data) -> + {xof, Algorithm, [Data | AccData], OutputLength}; +hash_update(State, Data) -> + crypto:hash_update(State, Data). + +hash_final({xof, Algorithm, AccData, OutputLength}) -> + AllData = lists:reverse(AccData), + crypto:hash_xof(Algorithm, AllData, OutputLength * 8); +hash_final(State) -> + crypto:hash_final(State). + +%%------------------------------------------------------------------------------ +%% HMAC +%%------------------------------------------------------------------------------ + +hmac_new(Algorithm, Key) -> + try + Name = hash_algorithm_name(Algorithm), + {ok, crypto:mac_init(hmac, Name, Key)} + catch + _:_ -> + {error, nil} + end. + +%%------------------------------------------------------------------------------ +%% Key Derivation Functions (PBKDF2) +%%------------------------------------------------------------------------------ + +pbkdf2_derive(Algorithm, Password, Salt, Iterations, Length) -> + Name = hash_algorithm_name(Algorithm), + try + Key = crypto:pbkdf2_hmac(Name, Password, Salt, Iterations, Length), + {ok, Key} + catch + _:_ -> + {error, nil} + end. + +%%------------------------------------------------------------------------------ +%% AEAD Ciphers (GCM, CCM, ChaCha20-Poly1305) +%%------------------------------------------------------------------------------ + +aead_cipher_name(#gcm{cipher = #aes{key_size = 128}}) -> + aes_128_gcm; +aead_cipher_name(#gcm{cipher = #aes{key_size = 192}}) -> + aes_192_gcm; +aead_cipher_name(#gcm{cipher = #aes{key_size = 256}}) -> + aes_256_gcm; +aead_cipher_name(#ccm{cipher = #aes{key_size = 128}}) -> + aes_128_ccm; +aead_cipher_name(#ccm{cipher = #aes{key_size = 192}}) -> + aes_192_ccm; +aead_cipher_name(#ccm{cipher = #aes{key_size = 256}}) -> + aes_256_ccm; +aead_cipher_name(#cha_cha20_poly1305{}) -> + chacha20_poly1305. + +aead_cipher_key(#gcm{cipher = #aes{key = Key}}) -> + Key; +aead_cipher_key(#ccm{cipher = #aes{key = Key}}) -> + Key; +aead_cipher_key(#cha_cha20_poly1305{key = Key}) -> + Key. + +aead_seal(Mode, Nonce, Plaintext, AdditionalData) -> + Cipher = aead_cipher_name(Mode), + TagSize = kryptos@aead:tag_size(Mode), + Key = aead_cipher_key(Mode), + try + {Ciphertext, Tag} = + crypto:crypto_one_time_aead( + Cipher, + Key, + Nonce, + Plaintext, + AdditionalData, + TagSize, + true + ), + {ok, {Ciphertext, Tag}} + catch + error:_ -> + {error, nil} + end. + +aead_open(Mode, Nonce, Tag, Ciphertext, AdditionalData) -> + Cipher = aead_cipher_name(Mode), + Key = aead_cipher_key(Mode), + try + case + crypto:crypto_one_time_aead( + Cipher, + Key, + Nonce, + Ciphertext, + AdditionalData, + Tag, + false + ) + of + error -> + {error, nil}; + Plaintext -> + {ok, Plaintext} + end + catch + error:_ -> + {error, nil} + end. + +%%------------------------------------------------------------------------------ +%% Block Ciphers (ECB, CBC, CTR) +%%------------------------------------------------------------------------------ + +block_cipher_name(#ecb{cipher = #aes{key_size = 128}}) -> + aes_128_ecb; +block_cipher_name(#ecb{cipher = #aes{key_size = 192}}) -> + aes_192_ecb; +block_cipher_name(#ecb{cipher = #aes{key_size = 256}}) -> + aes_256_ecb; +block_cipher_name(#cbc{cipher = #aes{key_size = 128}}) -> + aes_128_cbc; +block_cipher_name(#cbc{cipher = #aes{key_size = 192}}) -> + aes_192_cbc; +block_cipher_name(#cbc{cipher = #aes{key_size = 256}}) -> + aes_256_cbc; +block_cipher_name(#ctr{cipher = #aes{key_size = 128}}) -> + aes_128_ctr; +block_cipher_name(#ctr{cipher = #aes{key_size = 192}}) -> + aes_192_ctr; +block_cipher_name(#ctr{cipher = #aes{key_size = 256}}) -> + aes_256_ctr. + +block_cipher_padding(#ecb{}) -> + pkcs_padding; +block_cipher_padding(#cbc{}) -> + pkcs_padding; +block_cipher_padding(#ctr{}) -> + none. + +block_cipher_encrypt(Mode, Plaintext) -> + Cipher = block_cipher_name(Mode), + Key = kryptos@block:cipher_key(Mode), + Iv = kryptos@block:cipher_iv(Mode), + Padding = block_cipher_padding(Mode), + try + Ciphertext = + crypto:crypto_one_time( + Cipher, + Key, + Iv, + Plaintext, + [{encrypt, true}, {padding, Padding}] + ), + {ok, Ciphertext} + catch + error:_ -> + {error, nil} + end. + +block_cipher_decrypt(Mode, Ciphertext) -> + Cipher = block_cipher_name(Mode), + Key = kryptos@block:cipher_key(Mode), + Iv = kryptos@block:cipher_iv(Mode), + Padding = block_cipher_padding(Mode), + try + Plaintext = + crypto:crypto_one_time( + Cipher, + Key, + Iv, + Ciphertext, + [{encrypt, false}, {padding, Padding}] + ), + {ok, Plaintext} + catch + error:_ -> + {error, nil} + end. + +%% Raw AES block encryption/decryption (no padding, for key wrap) + +aes_cipher_name(#aes{key_size = 128}) -> + aes_128_ecb; +aes_cipher_name(#aes{key_size = 192}) -> + aes_192_ecb; +aes_cipher_name(#aes{key_size = 256}) -> + aes_256_ecb. + +aes_encrypt_block(Cipher, Block) -> + CipherName = aes_cipher_name(Cipher), + Key = Cipher#aes.key, + crypto:crypto_one_time(CipherName, Key, Block, [{encrypt, true}, {padding, none}]). + +aes_decrypt_block(Cipher, Block) -> + CipherName = aes_cipher_name(Cipher), + Key = Cipher#aes.key, + crypto:crypto_one_time(CipherName, Key, Block, [{encrypt, false}, {padding, none}]). + +%%------------------------------------------------------------------------------ +%% Curve/OID Mapping (shared by EC, EdDSA, XDH) +%%------------------------------------------------------------------------------ + +curve_to_oid(secp256r1) -> + {1, 2, 840, 10045, 3, 1, 7}; +curve_to_oid(secp384r1) -> + {1, 3, 132, 0, 34}; +curve_to_oid(secp521r1) -> + {1, 3, 132, 0, 35}; +curve_to_oid(secp256k1) -> + {1, 3, 132, 0, 10}; +curve_to_oid(ed25519) -> + {1, 3, 101, 112}; +curve_to_oid(ed448) -> + {1, 3, 101, 113}; +curve_to_oid(x25519) -> + {1, 3, 101, 110}; +curve_to_oid(x448) -> + {1, 3, 101, 111}. + +oid_to_curve({1, 2, 840, 10045, 3, 1, 7}) -> + secp256r1; +oid_to_curve({1, 3, 132, 0, 34}) -> + secp384r1; +oid_to_curve({1, 3, 132, 0, 35}) -> + secp521r1; +oid_to_curve({1, 3, 132, 0, 10}) -> + secp256k1; +oid_to_curve({1, 3, 101, 112}) -> + ed25519; +oid_to_curve({1, 3, 101, 113}) -> + ed448; +oid_to_curve({1, 3, 101, 110}) -> + x25519; +oid_to_curve({1, 3, 101, 111}) -> + x448. + +%%------------------------------------------------------------------------------ +%% Elliptic Curve (EC/ECDSA/ECDH) +%%------------------------------------------------------------------------------ + +%% Return the appropriate ECPrivateKey version value for the current OTP version +%% OTP 27: integer 1 +%% OTP 28: atom ecPrivkeyVer1 +ec_private_key_version() -> + case is_otp_28_or_later() of + true -> + ecPrivkeyVer1; + false -> + 1 + end. + +ec_curve_name(p256) -> + secp256r1; +ec_curve_name(p384) -> + secp384r1; +ec_curve_name(p521) -> + secp521r1; +ec_curve_name(secp256k1) -> + secp256k1. + +ec_generate_key_pair(Curve) -> + CurveName = ec_curve_name(Curve), + OID = curve_to_oid(CurveName), + {PubPoint, PrivScalar} = crypto:generate_key(ecdh, CurveName), + PrivKey = make_ec_private_key(PrivScalar, OID, PubPoint), + PubKey = {{'ECPoint', PubPoint}, {namedCurve, CurveName}}, + {PrivKey, PubKey}. + +%% Construct ECPrivateKey tuple compatible with both OTP 27 and OTP 28 +make_ec_private_key(PrivScalar, OID, PubPoint) -> + {'ECPrivateKey', ec_private_key_version(), PrivScalar, {namedCurve, OID}, PubPoint, + asn1_NOVALUE}. + +ec_coordinate_size(Curve) -> + case Curve of + p256 -> + 32; + p384 -> + 48; + p521 -> + 66; + secp256k1 -> + 32 + end. + +ec_private_key_from_bytes(Curve, PrivateScalar) -> + ExpectedSize = ec_coordinate_size(Curve), + case normalize_ec_scalar(PrivateScalar, ExpectedSize) of + {ok, NormalizedScalar} -> + try + CurveName = ec_curve_name(Curve), + OID = curve_to_oid(CurveName), + {PublicPoint, _} = crypto:generate_key(ecdh, CurveName, NormalizedScalar), + PrivKey = make_ec_private_key(NormalizedScalar, OID, PublicPoint), + PubKey = {{'ECPoint', PublicPoint}, {namedCurve, CurveName}}, + {ok, {PrivKey, PubKey}} + catch + _:_ -> + {error, nil} + end; + error -> + {error, nil} + end. + +%% Normalize EC scalar to expected size, handling DER integer encoding +normalize_ec_scalar(Scalar, ExpectedSize) -> + ActualSize = byte_size(Scalar), + if + ActualSize =:= 0 -> + %% Empty scalar is invalid + error; + ActualSize =:= ExpectedSize -> + {ok, Scalar}; + ActualSize =:= ExpectedSize + 1 -> + %% May have leading 0x00 for DER sign byte + case Scalar of + <<0, Rest/binary>> -> + {ok, Rest}; + _ -> + error + end; + ActualSize < ExpectedSize -> + %% Pad with leading zeros + PadSize = ExpectedSize - ActualSize, + {ok, <<0:PadSize/unit:8, Scalar/binary>>}; + true -> + error + end. + +ec_private_key_to_bytes(#'ECPrivateKey'{privateKey = PrivateScalar}) -> + PrivateScalar. + +ec_public_key_from_x509(DerBytes) -> + try + #'SubjectPublicKeyInfo'{algorithm = AlgId, subjectPublicKey = PublicKeyBits} = + public_key:der_decode('SubjectPublicKeyInfo', DerBytes), + #'AlgorithmIdentifier'{parameters = {namedCurve, OID}} = AlgId, + CurveName = oid_to_curve(OID), + case PublicKeyBits of + <> when Prefix == 4; Prefix == 2; Prefix == 3 -> + {ok, {{'ECPoint', PublicKeyBits}, {namedCurve, CurveName}}}; + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +ec_public_key_from_raw_point(Curve, Point) -> + try + CurveName = ec_curve_name(Curve), + CoordSize = kryptos@ec:coordinate_size(Curve), + case Point of + <<16#04, _X:CoordSize/binary, _Y:CoordSize/binary>> -> + validate_ec_point(CurveName, Point); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +validate_ec_point(CurveName, Point) -> + try + {_TempPub, TempPriv} = crypto:generate_key(ecdh, CurveName), + _ = crypto:compute_key(ecdh, Point, TempPriv, CurveName), + {ok, {{'ECPoint', Point}, {namedCurve, CurveName}}} + catch + _:_ -> + {error, nil} + end. + +ec_public_key_to_raw_point({{'ECPoint', <<4, _/binary>> = Point}, {namedCurve, _CurveName}}) -> + Point; +ec_public_key_to_raw_point({{'ECPoint', <> = Point}, {namedCurve, CurveName}}) when + Prefix == 2; Prefix == 3 +-> + ec_decompress_point(Point, CurveName). + +ec_decompress_point(<>, CurveName) -> + {{prime_field, PBin}, {ABin, BBin, _}, _, _, _} = crypto:ec_curve(CurveName), + X = binary:decode_unsigned(XBin), + P = binary:decode_unsigned(PBin), + A = binary:decode_unsigned(ABin), + B = binary:decode_unsigned(BBin), + X3 = binary:decode_unsigned( + crypto:mod_pow(X, 3, P) + ), + % y² = x³ + ax + b (mod p) + Y2 = (X3 + A * X + B) rem P, + % For p ≡ 3 (mod 4), sqrt(a) = a^((p+1)/4) mod p + Y = binary:decode_unsigned( + crypto:mod_pow(Y2, (P + 1) div 4, P) + ), + YFinal = + case (Y rem 2 == 0) == (Prefix == 2) of + true -> + Y; + false -> + P - Y + end, + CoordSize = byte_size(XBin), + YBin = pad_to_size(binary:encode_unsigned(YFinal), CoordSize), + <<4, XBin/binary, YBin/binary>>. + +pad_to_size(Bin, Size) when byte_size(Bin) >= Size -> + Bin; +pad_to_size(Bin, Size) -> + PadSize = Size - byte_size(Bin), + <<0:(PadSize * 8), Bin/binary>>. + +ec_public_key_from_private(#'ECPrivateKey'{ + parameters = {namedCurve, CurveOID}, + publicKey = PublicPoint +}) -> + CurveName = oid_to_curve(CurveOID), + {{'ECPoint', PublicPoint}, {namedCurve, CurveName}}. + +ec_private_key_curve(#'ECPrivateKey'{parameters = {namedCurve, CurveOID}}) -> + erlang_ec_curve_to_gleam(oid_to_curve(CurveOID)). + +ec_public_key_curve({{'ECPoint', _}, {namedCurve, CurveName}}) -> + erlang_ec_curve_to_gleam(CurveName). + +erlang_ec_curve_to_gleam(secp256r1) -> + p256; +erlang_ec_curve_to_gleam(secp384r1) -> + p384; +erlang_ec_curve_to_gleam(secp521r1) -> + p521; +erlang_ec_curve_to_gleam(secp256k1) -> + secp256k1. + +ecdsa_sign(PrivateKey, Message, HashAlgorithm) -> + DigestType = hash_algorithm_name(HashAlgorithm), + public_key:sign(Message, DigestType, PrivateKey). + +ecdsa_verify(PubKey, Message, Signature, HashAlgorithm) -> + try + DigestType = hash_algorithm_name(HashAlgorithm), + public_key:verify(Message, DigestType, Signature, PubKey) + catch + _:_ -> + false + end. + +ecdh_compute_shared_secret(PrivateKey, PeerPublicKey) -> + try + {{'ECPoint', PeerPoint}, {namedCurve, PeerCurveName}} = PeerPublicKey, + #'ECPrivateKey'{privateKey = PrivateScalar, parameters = {namedCurve, PrivateOID}} = + PrivateKey, + ExpectedOID = curve_to_oid(PeerCurveName), + case PrivateOID =:= ExpectedOID of + false -> + {error, nil}; + true -> + SharedSecret = crypto:compute_key(ecdh, PeerPoint, PrivateScalar, PeerCurveName), + {ok, SharedSecret} + end + catch + _:_ -> + {error, nil} + end. + +%% EC Import Functions + +ec_import_private_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'PrivateKeyInfo', DerBytes, not_encrypted}] -> + ec_import_private_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +ec_import_private_key_der(DerBytes) -> + try + ec_import_private_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +ec_import_private_key_from_der(DerBytes) -> + %% der_decode returns ECPrivateKey tuple + %% OTP 27/28: {'ECPrivateKey', Version, PrivScalar, {namedCurve, OID}, PubPoint, asn1_NOVALUE} + case public_key:der_decode('PrivateKeyInfo', DerBytes) of + {'ECPrivateKey', _, PrivateScalar, {namedCurve, CurveOID}, PublicPoint, _} -> + CurveName = oid_to_curve(CurveOID), + PrivKey = make_ec_private_key(PrivateScalar, CurveOID, PublicPoint), + PubKey = {{'ECPoint', PublicPoint}, {namedCurve, CurveName}}, + {ok, {PrivKey, PubKey}}; + _ -> + {error, nil} + end. + +ec_import_public_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'SubjectPublicKeyInfo', DerBytes, not_encrypted}] -> + ec_import_public_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +ec_import_public_key_der(DerBytes) -> + try + ec_import_public_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +ec_import_public_key_from_der(DerBytes) -> + %% SubjectPublicKeyInfo structure: + %% {'SubjectPublicKeyInfo', {'AlgorithmIdentifier', Algorithm, Parameters}, PublicKeyBits} + %% Parameters format differs between OTP versions: + %% OTP 27: raw DER bytes + %% OTP 28: {namedCurve, OID} tuple + case public_key:der_decode('SubjectPublicKeyInfo', DerBytes) of + {'SubjectPublicKeyInfo', {'AlgorithmIdentifier', ?'id-ecPublicKey', Params}, PublicKeyBits} -> + case extract_ec_curve_oid(Params) of + {ok, OID} -> + CurveName = oid_to_curve(OID), + case PublicKeyBits of + <> when Prefix == 4; Prefix == 2; Prefix == 3 -> + {ok, {{'ECPoint', PublicKeyBits}, {namedCurve, CurveName}}}; + _ -> + {error, nil} + end; + error -> + {error, nil} + end; + _ -> + {error, nil} + end. + +%% Extract curve OID from AlgorithmIdentifier parameters +%% OTP 27: raw DER-encoded OID bytes +%% OTP 28: {namedCurve, OID} tuple +extract_ec_curve_oid({namedCurve, OID}) -> + {ok, OID}; +extract_ec_curve_oid(DerBytes) when is_binary(DerBytes) -> + %% OTP 27 returns raw DER bytes, decode the OID + try + OID = public_key:der_decode('EcpkParameters', DerBytes), + case OID of + {namedCurve, ActualOID} -> + {ok, ActualOID}; + _ -> + error + end + catch + _:_ -> + error + end; +extract_ec_curve_oid(_) -> + error. + +%% EC Export Functions + +ec_export_private_key_pem(Key) -> + try + Der = ec_private_key_to_der(Key), + PemEntry = {'PrivateKeyInfo', Der, not_encrypted}, + {ok, public_key:pem_encode([PemEntry])} + catch + _:_ -> + {error, nil} + end. + +ec_export_private_key_der(Key) -> + try + Der = ec_private_key_to_der(Key), + {ok, Der} + catch + _:_ -> + {error, nil} + end. + +ec_private_key_to_der(Key) -> + {'PrivateKeyInfo', Der, not_encrypted} = + public_key:pem_entry_encode('PrivateKeyInfo', Key), + Der. + +ec_export_public_key_pem(Key) -> + try + Der = ec_public_key_to_der(Key), + PemEntry = {'SubjectPublicKeyInfo', Der, not_encrypted}, + {ok, public_key:pem_encode([PemEntry])} + catch + _:_ -> + {error, nil} + end. + +ec_export_public_key_der(Key) -> + try + Der = ec_public_key_to_der(Key), + {ok, Der} + catch + _:_ -> + {error, nil} + end. + +ec_public_key_to_der({{'ECPoint', Point}, {namedCurve, CurveName}}) -> + CurveOID = curve_to_oid(CurveName), + Key = {{'ECPoint', Point}, {namedCurve, CurveOID}}, + {'SubjectPublicKeyInfo', Der, not_encrypted} = + public_key:pem_entry_encode('SubjectPublicKeyInfo', Key), + Der. + +%%------------------------------------------------------------------------------ +%% X25519/X448 Key Exchange (XDH) +%%------------------------------------------------------------------------------ + +xdh_generate_key_pair(Curve) -> + {PubKey, PrivKey} = crypto:generate_key(ecdh, Curve), + {{PrivKey, Curve}, {PubKey, Curve}}. + +xdh_private_key_from_bytes(Curve, PrivateBytes) -> + try + ExpectedSize = kryptos@xdh:key_size(Curve), + case byte_size(PrivateBytes) of + ExpectedSize -> + {PubKey, PrivKey} = crypto:generate_key(ecdh, Curve, PrivateBytes), + {ok, {{PrivKey, Curve}, {PubKey, Curve}}}; + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +xdh_public_key_from_bytes(Curve, PublicBytes) -> + ExpectedSize = kryptos@xdh:key_size(Curve), + case byte_size(PublicBytes) of + ExpectedSize -> + {ok, {PublicBytes, Curve}}; + _ -> + {error, nil} + end. + +xdh_private_key_to_bytes({PrivBytes, _Curve}) -> + PrivBytes. + +xdh_public_key_to_bytes({PubBytes, _Curve}) -> + PubBytes. + +xdh_public_key_from_private({PrivBytes, Curve}) -> + {PubKey, _} = crypto:generate_key(ecdh, Curve, PrivBytes), + {PubKey, Curve}. + +xdh_private_key_curve({_PrivBytes, Curve}) -> + Curve. + +xdh_public_key_curve({_PubBytes, Curve}) -> + Curve. + +xdh_compute_shared_secret({PrivKey, PrivCurve}, {PeerPubKey, PeerCurve}) -> + try + case PrivCurve =:= PeerCurve of + false -> + {error, nil}; + true -> + SharedSecret = crypto:compute_key(ecdh, PeerPubKey, PrivKey, PrivCurve), + {ok, SharedSecret} + end + catch + _:_ -> + {error, nil} + end. + +%% XDH Import Functions + +xdh_import_private_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'PrivateKeyInfo', DerBytes, not_encrypted}] -> + xdh_import_private_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +xdh_import_private_key_der(DerBytes) -> + try + xdh_import_private_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +xdh_import_private_key_from_der(DerBytes) -> + %% der_decode returns tuples, not records + %% OTP 27: 5-tuple with 'PrivateKeyInfo_privateKeyAlgorithm' + %% OTP 28: 6-tuple with 'PrivateKeyAlgorithmIdentifier' + case public_key:der_decode('PrivateKeyInfo', DerBytes) of + {'PrivateKeyInfo', _, {_, OID, _}, WrappedKey, _} -> + %% OTP 27 format (5-tuple) + xdh_import_from_decoded(OID, WrappedKey); + {'PrivateKeyInfo', _, {_, OID, _}, WrappedKey, _, _} -> + %% OTP 28 format (6-tuple) + xdh_import_from_decoded(OID, WrappedKey); + _ -> + {error, nil} + end. + +xdh_import_from_decoded(OID, WrappedKey) -> + Curve = oid_to_curve(OID), + case Curve of + _ when Curve =:= x25519; Curve =:= x448 -> + KeySize = kryptos@xdh:key_size(Curve), + case WrappedKey of + <<4, KeySize, PrivateBytes:KeySize/binary>> -> + {PubKey, PrivKey} = crypto:generate_key(ecdh, Curve, PrivateBytes), + {ok, {{PrivKey, Curve}, {PubKey, Curve}}}; + _ -> + {error, nil} + end; + _ -> + {error, nil} + end. + +xdh_import_public_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'SubjectPublicKeyInfo', DerBytes, not_encrypted}] -> + xdh_import_public_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +xdh_import_public_key_der(DerBytes) -> + try + xdh_import_public_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +xdh_import_public_key_from_der(DerBytes) -> + case public_key:der_decode('SubjectPublicKeyInfo', DerBytes) of + #'SubjectPublicKeyInfo'{ + algorithm = #'AlgorithmIdentifier'{algorithm = OID}, + subjectPublicKey = PublicKeyBits + } -> + Curve = oid_to_curve(OID), + case Curve of + _ when Curve =:= x25519; Curve =:= x448 -> + {ok, {PublicKeyBits, Curve}}; + _ -> + {error, nil} + end; + _ -> + {error, nil} + end. + +%% XDH Export Functions + +xdh_export_private_key_pem({KeyBytes, Curve}) -> + try + Der = xdh_private_key_to_der(KeyBytes, Curve), + {ok, public_key:pem_encode([{'PrivateKeyInfo', Der, not_encrypted}])} + catch + _:_ -> + {error, nil} + end. + +xdh_export_private_key_der({KeyBytes, Curve}) -> + try + {ok, xdh_private_key_to_der(KeyBytes, Curve)} + catch + _:_ -> + {error, nil} + end. + +xdh_private_key_to_der(KeyBytes, Curve) -> + KeySize = kryptos@xdh:key_size(Curve), + WrappedKey = <<4, KeySize, KeyBytes/binary>>, + OID = curve_to_oid(Curve), + AlgId = {'AlgorithmIdentifier', OID, asn1_NOVALUE}, + PrivKeyInfo = + case is_otp_28_or_later() of + true -> + %% OTP 28+: 6-tuple OneAsymmetricKey format + {'OneAsymmetricKey', 0, AlgId, WrappedKey, asn1_NOVALUE, asn1_NOVALUE}; + false -> + %% OTP 27: 5-tuple PrivateKeyInfo format + {'PrivateKeyInfo', v1, AlgId, WrappedKey, asn1_NOVALUE} + end, + public_key:der_encode('PrivateKeyInfo', PrivKeyInfo). + +xdh_export_public_key_pem({PubBytes, Curve}) -> + try + Spki = + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + subjectPublicKey = PubBytes + }, + Der = public_key:der_encode('SubjectPublicKeyInfo', Spki), + {ok, public_key:pem_encode([{'SubjectPublicKeyInfo', Der, not_encrypted}])} + catch + _:_ -> + {error, nil} + end. + +xdh_export_public_key_der({PubBytes, Curve}) -> + try + Spki = + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + subjectPublicKey = PubBytes + }, + {ok, public_key:der_encode('SubjectPublicKeyInfo', Spki)} + catch + _:_ -> + {error, nil} + end. + +%%------------------------------------------------------------------------------ +%% RSA +%%------------------------------------------------------------------------------ + +bin_to_int(Bin) -> + binary:decode_unsigned(Bin). + +rsa_build_private_key([E, N, D, P, Q, Dp, Dq, Qi]) -> + #'RSAPrivateKey'{ + version = 'two-prime', + modulus = bin_to_int(N), + publicExponent = bin_to_int(E), + privateExponent = bin_to_int(D), + prime1 = bin_to_int(P), + prime2 = bin_to_int(Q), + exponent1 = bin_to_int(Dp), + exponent2 = bin_to_int(Dq), + coefficient = bin_to_int(Qi) + }; +rsa_build_private_key([E, N, D]) -> + #'RSAPrivateKey'{ + version = 'two-prime', + modulus = bin_to_int(N), + publicExponent = bin_to_int(E), + privateExponent = bin_to_int(D) + }. + +rsa_build_public_key([E, N | _]) -> + #'RSAPublicKey'{modulus = bin_to_int(N), publicExponent = bin_to_int(E)}. + +rsa_generate_key_pair(Bits) -> + {PubKey, PrivKey} = crypto:generate_key(rsa, {Bits, 65537}), + {rsa_build_private_key(PrivKey), rsa_build_public_key(PubKey)}. + +rsa_public_key_from_private(PrivKey) -> + #'RSAPublicKey'{ + modulus = PrivKey#'RSAPrivateKey'.modulus, + publicExponent = PrivKey#'RSAPrivateKey'.publicExponent + }. + +rsa_private_key_modulus_bits(#'RSAPrivateKey'{modulus = N}) -> + bit_size(binary:encode_unsigned(N)). + +rsa_public_key_modulus_bits(#'RSAPublicKey'{modulus = N}) -> + bit_size(binary:encode_unsigned(N)). + +rsa_private_key_public_exponent(#'RSAPrivateKey'{publicExponent = E}) -> + E. + +rsa_public_key_public_exponent(#'RSAPublicKey'{publicExponent = E}) -> + E. + +rsa_private_key_modulus(#'RSAPrivateKey'{modulus = N}) -> + binary:encode_unsigned(N). + +rsa_public_key_modulus(#'RSAPublicKey'{modulus = N}) -> + binary:encode_unsigned(N). + +rsa_private_key_public_exponent_bytes(#'RSAPrivateKey'{publicExponent = E}) -> + binary:encode_unsigned(E). + +rsa_public_key_exponent_bytes(#'RSAPublicKey'{publicExponent = E}) -> + binary:encode_unsigned(E). + +rsa_private_key_private_exponent_bytes(#'RSAPrivateKey'{privateExponent = D}) -> + binary:encode_unsigned(D). + +rsa_private_key_prime1(#'RSAPrivateKey'{prime1 = P}) -> + binary:encode_unsigned(P). + +rsa_private_key_prime2(#'RSAPrivateKey'{prime2 = Q}) -> + binary:encode_unsigned(Q). + +rsa_private_key_exponent1(#'RSAPrivateKey'{exponent1 = DP}) -> + binary:encode_unsigned(DP). + +rsa_private_key_exponent2(#'RSAPrivateKey'{exponent2 = DQ}) -> + binary:encode_unsigned(DQ). + +rsa_private_key_coefficient(#'RSAPrivateKey'{coefficient = QI}) -> + binary:encode_unsigned(QI). + +rsa_public_key_from_components(N, E) -> + try + NInt = bin_to_int(N), + EInt = bin_to_int(E), + case NInt > 1 andalso EInt > 1 andalso EInt < NInt of + true -> + {ok, #'RSAPublicKey'{modulus = NInt, publicExponent = EInt}}; + false -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +mod_pow(Base, Exp, Mod) -> + crypto:mod_pow(bin_to_int(Base), bin_to_int(Exp), bin_to_int(Mod)). + +rsa_private_key_from_full_components(N, E, D, P, Q, DP, DQ, QI) -> + try + NInt = bin_to_int(N), + EInt = bin_to_int(E), + DInt = bin_to_int(D), + PInt = bin_to_int(P), + QInt = bin_to_int(Q), + DPInt = bin_to_int(DP), + DQInt = bin_to_int(DQ), + QIInt = bin_to_int(QI), + case NInt > 1 andalso EInt > 1 andalso EInt < NInt andalso PInt * QInt =:= NInt of + true -> + PrivKey = + #'RSAPrivateKey'{ + version = 'two-prime', + modulus = NInt, + publicExponent = EInt, + privateExponent = DInt, + prime1 = PInt, + prime2 = QInt, + exponent1 = DPInt, + exponent2 = DQInt, + coefficient = QIInt + }, + PubKey = #'RSAPublicKey'{modulus = NInt, publicExponent = EInt}, + {ok, {PrivKey, PubKey}}; + false -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +%% RSA Signing + +rsa_pss_salt_length(salt_length_hash_len) -> + -1; +rsa_pss_salt_length(salt_length_max) -> + -2; +rsa_pss_salt_length({salt_length_explicit, Length}) -> + Length. + +rsa_sign_padding_opts(pkcs1v15, _Hash) -> + []; +rsa_sign_padding_opts({pss, SaltLength}, Hash) -> + DigestType = hash_algorithm_name(Hash), + Salt = rsa_pss_salt_length(SaltLength), + [ + {rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, Salt}, + {rsa_mgf1_md, DigestType} + ]. + +rsa_sign(PrivateKey, Message, Hash, Padding) -> + DigestType = hash_algorithm_name(Hash), + Opts = rsa_sign_padding_opts(Padding, Hash), + public_key:sign(Message, DigestType, PrivateKey, Opts). + +rsa_verify(PublicKey, Message, Signature, Hash, Padding) -> + try + DigestType = hash_algorithm_name(Hash), + Opts = rsa_sign_padding_opts(Padding, Hash), + public_key:verify(Message, DigestType, Signature, PublicKey, Opts) + catch + _:_ -> + false + end. + +%% RSA Encryption + +rsa_encrypt_padding_opts(encrypt_pkcs1v15) -> + [{rsa_padding, rsa_pkcs1_padding}]; +rsa_encrypt_padding_opts(#oaep{hash = Hash, label = Label}) -> + DigestType = hash_algorithm_name(Hash), + Opts = + [ + {rsa_padding, rsa_pkcs1_oaep_padding}, + {rsa_oaep_md, DigestType}, + {rsa_mgf1_md, DigestType} + ], + case Label of + <<>> -> + Opts; + _ -> + [{rsa_oaep_label, Label} | Opts] + end. + +rsa_encrypt(PublicKey, Plaintext, Padding) -> + try + Opts = rsa_encrypt_padding_opts(Padding), + Ciphertext = public_key:encrypt_public(Plaintext, PublicKey, Opts), + {ok, Ciphertext} + catch + _:_ -> + {error, nil} + end. + +rsa_decrypt(PrivateKey, Ciphertext, Padding) -> + try + Opts = rsa_encrypt_padding_opts(Padding), + Plaintext = public_key:decrypt_private(Ciphertext, PrivateKey, Opts), + {ok, Plaintext} + catch + _:_ -> + {error, nil} + end. + +%% RSA Import Functions + +rsa_private_format_types(pkcs8) -> + {'PrivateKeyInfo', 'PrivateKeyInfo'}; +rsa_private_format_types(pkcs1) -> + {'RSAPrivateKey', 'RSAPrivateKey'}. + +rsa_public_format_types(spki) -> + {'SubjectPublicKeyInfo', spki}; +rsa_public_format_types(rsa_public_key) -> + {'RSAPublicKey', rsa_public_key}. + +rsa_private_key_from_pkcs8(DerBytes) -> + try + RSAPrivKey = public_key:der_decode('PrivateKeyInfo', DerBytes), + PubKey = + #'RSAPublicKey'{ + modulus = RSAPrivKey#'RSAPrivateKey'.modulus, + publicExponent = RSAPrivKey#'RSAPrivateKey'.publicExponent + }, + {ok, {RSAPrivKey, PubKey}} + catch + _:_ -> + {error, nil} + end. + +rsa_public_key_from_x509(DerBytes) -> + try + #'SubjectPublicKeyInfo'{ + algorithm = #'AlgorithmIdentifier'{algorithm = ?rsaEncryption}, + subjectPublicKey = PublicKeyDer + } = + public_key:der_decode('SubjectPublicKeyInfo', DerBytes), + RSAPubKey = public_key:der_decode('RSAPublicKey', PublicKeyDer), + {ok, RSAPubKey} + catch + _:_ -> + {error, nil} + end. + +rsa_import_private_key_pem(PemData, Format) -> + try + {PemType, DerType} = rsa_private_format_types(Format), + case public_key:pem_decode(PemData) of + [{PemType, DerBytes, not_encrypted}] -> + rsa_import_private_key_from_der(DerBytes, DerType); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +rsa_import_private_key_der(DerBytes, Format) -> + try + {_, DerType} = rsa_private_format_types(Format), + rsa_import_private_key_from_der(DerBytes, DerType) + catch + _:_ -> + {error, nil} + end. + +rsa_import_private_key_from_der(DerBytes, DerType) -> + RSAPrivKey = public_key:der_decode(DerType, DerBytes), + RSAPubKey = + #'RSAPublicKey'{ + modulus = RSAPrivKey#'RSAPrivateKey'.modulus, + publicExponent = RSAPrivKey#'RSAPrivateKey'.publicExponent + }, + {ok, {RSAPrivKey, RSAPubKey}}. + +rsa_import_public_key_pem(PemData, Format) -> + try + {PemType, _} = rsa_public_format_types(Format), + case public_key:pem_decode(PemData) of + [{PemType, DerBytes, not_encrypted}] -> + rsa_import_public_key_from_der(DerBytes, Format); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +rsa_import_public_key_der(DerBytes, Format) -> + try + rsa_import_public_key_from_der(DerBytes, Format) + catch + _:_ -> + {error, nil} + end. + +rsa_import_public_key_from_der(DerBytes, spki) -> + #'SubjectPublicKeyInfo'{ + algorithm = #'AlgorithmIdentifier'{algorithm = ?rsaEncryption}, + subjectPublicKey = PublicKeyDer + } = + public_key:der_decode('SubjectPublicKeyInfo', DerBytes), + RSAPubKey = public_key:der_decode('RSAPublicKey', PublicKeyDer), + {ok, RSAPubKey}; +rsa_import_public_key_from_der(DerBytes, rsa_public_key) -> + RSAPubKey = public_key:der_decode('RSAPublicKey', DerBytes), + {ok, RSAPubKey}. + +%% RSA Export Functions + +rsa_private_pem_type(pkcs8) -> + 'PrivateKeyInfo'; +rsa_private_pem_type(pkcs1) -> + 'RSAPrivateKey'. + +rsa_public_pem_type(spki) -> + 'SubjectPublicKeyInfo'; +rsa_public_pem_type(rsa_public_key) -> + 'RSAPublicKey'. + +rsa_private_key_to_der(Key, pkcs8) -> + {'PrivateKeyInfo', Der, not_encrypted} = + public_key:pem_entry_encode('PrivateKeyInfo', Key), + Der; +rsa_private_key_to_der(Key, pkcs1) -> + public_key:der_encode('RSAPrivateKey', Key). + +rsa_public_key_to_der(Key, spki) -> + {'SubjectPublicKeyInfo', Der, not_encrypted} = + public_key:pem_entry_encode('SubjectPublicKeyInfo', Key), + Der; +rsa_public_key_to_der(Key, rsa_public_key) -> + public_key:der_encode('RSAPublicKey', Key). + +rsa_export_private_key_pem(Key, Format) -> + try + Der = rsa_private_key_to_der(Key, Format), + PemType = rsa_private_pem_type(Format), + PemEntry = {PemType, Der, not_encrypted}, + {ok, public_key:pem_encode([PemEntry])} + catch + _:_ -> + {error, nil} + end. + +rsa_export_private_key_der(Key, Format) -> + try + Der = rsa_private_key_to_der(Key, Format), + {ok, Der} + catch + _:_ -> + {error, nil} + end. + +rsa_export_public_key_pem(Key, Format) -> + try + Der = rsa_public_key_to_der(Key, Format), + PemType = rsa_public_pem_type(Format), + PemEntry = {PemType, Der, not_encrypted}, + {ok, public_key:pem_encode([PemEntry])} + catch + _:_ -> + {error, nil} + end. + +rsa_export_public_key_der(Key, Format) -> + try + Der = rsa_public_key_to_der(Key, Format), + {ok, Der} + catch + _:_ -> + {error, nil} + end. + +%%------------------------------------------------------------------------------ +%% EdDSA (Ed25519/Ed448) +%%------------------------------------------------------------------------------ + +eddsa_generate_key_pair(Curve) -> + {PubKey, PrivKey} = crypto:generate_key(eddsa, Curve), + {{PrivKey, Curve}, {PubKey, Curve}}. + +eddsa_private_key_from_bytes(Curve, PrivateBytes) -> + try + ExpectedSize = kryptos@eddsa:key_size(Curve), + case byte_size(PrivateBytes) of + ExpectedSize -> + {PubKey, PrivKey} = crypto:generate_key(eddsa, Curve, PrivateBytes), + {ok, {{PrivKey, Curve}, {PubKey, Curve}}}; + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +eddsa_public_key_from_bytes(Curve, PublicBytes) -> + ExpectedSize = kryptos@eddsa:key_size(Curve), + case byte_size(PublicBytes) of + ExpectedSize -> + {ok, {PublicBytes, Curve}}; + _ -> + {error, nil} + end. + +eddsa_private_key_to_bytes({PrivBytes, _Curve}) -> + PrivBytes. + +eddsa_public_key_to_bytes({PubBytes, _Curve}) -> + PubBytes. + +eddsa_public_key_from_private({PrivBytes, Curve}) -> + {PubKey, _} = crypto:generate_key(eddsa, Curve, PrivBytes), + {PubKey, Curve}. + +eddsa_private_key_curve({_PrivBytes, Curve}) -> + Curve. + +eddsa_public_key_curve({_PubBytes, Curve}) -> + Curve. + +eddsa_sign({PrivKey, Curve}, Message) -> + crypto:sign(eddsa, none, Message, [PrivKey, Curve]). + +eddsa_verify({PubKey, Curve}, Message, Signature) -> + try + crypto:verify(eddsa, none, Message, Signature, [PubKey, Curve]) + catch + _:_ -> + false + end. + +%% EdDSA Import Functions + +eddsa_import_private_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'PrivateKeyInfo', DerBytes, not_encrypted}] -> + eddsa_import_private_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +eddsa_import_private_key_der(DerBytes) -> + try + eddsa_import_private_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +eddsa_import_private_key_from_der(DerBytes) -> + case public_key:der_decode('PrivateKeyInfo', DerBytes) of + {'PrivateKeyInfo', _, {_, OID, _}, WrappedKey, _} -> + eddsa_import_from_decoded(OID, WrappedKey); + {'PrivateKeyInfo', _, {_, OID, _}, WrappedKey, _, _} -> + eddsa_import_from_decoded(OID, WrappedKey); + #'ECPrivateKey'{privateKey = PrivateBytes, parameters = {namedCurve, OID}} -> + Curve = oid_to_curve(OID), + case Curve of + _ when Curve =:= ed25519; Curve =:= ed448 -> + {PubKey, PrivKey} = crypto:generate_key(eddsa, Curve, PrivateBytes), + {ok, {{PrivKey, Curve}, {PubKey, Curve}}}; + _ -> + {error, nil} + end; + _ -> + {error, nil} + end. + +eddsa_import_from_decoded(OID, WrappedKey) -> + Curve = oid_to_curve(OID), + case Curve of + _ when Curve =:= ed25519; Curve =:= ed448 -> + KeySize = kryptos@eddsa:key_size(Curve), + case WrappedKey of + <<4, KeySize, PrivateBytes:KeySize/binary>> -> + {PubKey, PrivKey} = crypto:generate_key(eddsa, Curve, PrivateBytes), + {ok, {{PrivKey, Curve}, {PubKey, Curve}}}; + _ -> + {error, nil} + end; + _ -> + {error, nil} + end. + +eddsa_import_public_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'SubjectPublicKeyInfo', DerBytes, not_encrypted}] -> + eddsa_import_public_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +eddsa_import_public_key_der(DerBytes) -> + try + eddsa_import_public_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +eddsa_import_public_key_from_der(DerBytes) -> + case public_key:der_decode('SubjectPublicKeyInfo', DerBytes) of + #'SubjectPublicKeyInfo'{ + algorithm = #'AlgorithmIdentifier'{algorithm = OID}, + subjectPublicKey = PublicKeyBits + } -> + Curve = oid_to_curve(OID), + case Curve of + _ when Curve =:= ed25519; Curve =:= ed448 -> + {ok, {PublicKeyBits, Curve}}; + _ -> + {error, nil} + end; + _ -> + {error, nil} + end. + +%% EdDSA Export Functions + +eddsa_export_private_key_pem({KeyBytes, Curve}) -> + try + Der = eddsa_private_key_to_der(KeyBytes, Curve), + {ok, public_key:pem_encode([{'PrivateKeyInfo', Der, not_encrypted}])} + catch + _:_ -> + {error, nil} + end. + +eddsa_export_private_key_der({KeyBytes, Curve}) -> + try + {ok, eddsa_private_key_to_der(KeyBytes, Curve)} + catch + _:_ -> + {error, nil} + end. + +eddsa_private_key_to_der(KeyBytes, Curve) -> + KeySize = kryptos@eddsa:key_size(Curve), + WrappedKey = <<4, KeySize, KeyBytes/binary>>, + OID = curve_to_oid(Curve), + AlgId = {'AlgorithmIdentifier', OID, asn1_NOVALUE}, + PrivKeyInfo = + case is_otp_28_or_later() of + true -> + {'OneAsymmetricKey', 0, AlgId, WrappedKey, asn1_NOVALUE, asn1_NOVALUE}; + false -> + {'PrivateKeyInfo', v1, AlgId, WrappedKey, asn1_NOVALUE} + end, + public_key:der_encode('PrivateKeyInfo', PrivKeyInfo). + +eddsa_export_public_key_pem({PubBytes, Curve}) -> + try + Spki = + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + subjectPublicKey = PubBytes + }, + Der = public_key:der_encode('SubjectPublicKeyInfo', Spki), + {ok, public_key:pem_encode([{'SubjectPublicKeyInfo', Der, not_encrypted}])} + catch + _:_ -> + {error, nil} + end. + +eddsa_export_public_key_der({PubBytes, Curve}) -> + try + Spki = + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + subjectPublicKey = PubBytes + }, + {ok, public_key:der_encode('SubjectPublicKeyInfo', Spki)} + catch + _:_ -> + {error, nil} + end. diff --git a/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.mjs b/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.mjs new file mode 100644 index 0000000..2e7c60a --- /dev/null +++ b/examples/http_interactions/build/packages/kryptos/src/kryptos_ffi.mjs @@ -0,0 +1,1557 @@ +import crypto from "node:crypto"; + +import { BitArray$BitArray, Result$Error, Result$Ok } from "./gleam.mjs"; +import { + cipher_key as aeadCipherKey, + cipher_name as aeadCipherName, + is_ccm as aeadIsCcm, + tag_size as aeadTagSize, +} from "./kryptos/aead.mjs"; +import { + aes_key as blockAesKey, + cipher_iv as blockCipherIv, + cipher_key as blockCipherKey, + cipher_name as blockCipherName, + is_ctr as blockIsCtr, + key_size as blockKeySize, +} from "./kryptos/block.mjs"; +import { + Curve$P256, + Curve$P384, + Curve$P521, + Curve$Secp256k1, + Curve$isP256, + Curve$isP384, + Curve$isP521, + Curve$isSecp256k1, + coordinate_size as ecCoordinateSize, +} from "./kryptos/ec.mjs"; +import { + Curve$isEd25519, + Curve$isEd448, + Curve$Ed25519, + Curve$Ed448, + key_size as eddsaKeySize, +} from "./kryptos/eddsa.mjs"; +import { + HashAlgorithm$isShake128, + HashAlgorithm$isShake256, + HashAlgorithm$Shake128$0, + HashAlgorithm$Shake256$0, + algorithm_name as hashAlgorithmName, +} from "./kryptos/hash.mjs"; +import { + EncryptPadding$isEncryptPkcs1v15, + EncryptPadding$isOaep, + PrivateKeyFormat$isPkcs1, + PrivateKeyFormat$isPkcs8, + PssSaltLength$SaltLengthExplicit$0, + PssSaltLength$isSaltLengthExplicit, + PssSaltLength$isSaltLengthHashLen, + PssSaltLength$isSaltLengthMax, + PublicKeyFormat$isRsaPublicKey, + PublicKeyFormat$isSpki, + SignPadding$Pss$0, + SignPadding$isPkcs1v15, + SignPadding$isPss, +} from "./kryptos/rsa.mjs"; +import { + Curve$isX25519, + Curve$isX448, + Curve$X25519, + Curve$X448, + key_size as xdhKeySize, +} from "./kryptos/xdh.mjs"; + +// ============================================================================= +// Utilities & Random +// ============================================================================= + +export function randomBytes(length) { + const safeLength = Math.max(0, length); + const buffer = crypto.randomBytes(safeLength); + return BitArray$BitArray(buffer); +} + +export function randomUuid() { + return crypto.randomUUID(); +} + +export function constantTimeEqual(a, b) { + if (a.byteSize !== b.byteSize) { + return false; + } + + return crypto.timingSafeEqual(a.rawBuffer, b.rawBuffer); +} + +// ============================================================================= +// Hash Functions +// ============================================================================= + +function getXofOutputLength(algorithm) { + if (HashAlgorithm$isShake128(algorithm)) { + return HashAlgorithm$Shake128$0(algorithm); + } + if (HashAlgorithm$isShake256(algorithm)) { + return HashAlgorithm$Shake256$0(algorithm); + } + return null; +} + +export function hashNew(algorithm) { + try { + const name = hashAlgorithmName(algorithm); + const outputLength = getXofOutputLength(algorithm); + const options = outputLength !== null ? { outputLength } : undefined; + return Result$Ok(crypto.createHash(name, options)); + } catch { + return Result$Error(undefined); + } +} + +export function hashUpdate(hasher, input) { + hasher.update(input.rawBuffer); + return hasher; +} + +export function hashFinal(hasher) { + const digest = hasher.digest(); + return BitArray$BitArray(digest); +} + +// ============================================================================= +// HMAC +// ============================================================================= + +export function hmacNew(algorithm, key) { + try { + const algorithmName = hashAlgorithmName(algorithm); + return Result$Ok(crypto.createHmac(algorithmName, key.rawBuffer)); + } catch { + return Result$Error(undefined); + } +} + +export function hmacUpdate(hmac, data) { + hmac.update(data.rawBuffer); + return hmac; +} + +export function hmacFinal(hmac) { + const digest = hmac.digest(); + return BitArray$BitArray(digest); +} + +// ============================================================================= +// Key Derivation Functions (HKDF, PBKDF2) +// ============================================================================= + +export function hkdfDerive(algorithm, ikm, salt, info, length) { + try { + const name = hashAlgorithmName(algorithm); + const result = crypto.hkdfSync( + name, + ikm.rawBuffer, + salt.rawBuffer, + info.rawBuffer, + length, + ); + const buffer = new Uint8Array(result); + return Result$Ok(BitArray$BitArray(buffer)); + } catch { + return Result$Error(undefined); + } +} + +export function pbkdf2Derive(algorithm, password, salt, iterations, length) { + try { + const name = hashAlgorithmName(algorithm); + const result = crypto.pbkdf2Sync( + password.rawBuffer, + salt.rawBuffer, + iterations, + length, + name, + ); + return Result$Ok(BitArray$BitArray(result)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// AEAD Ciphers (GCM, CCM, ChaCha20-Poly1305) +// ============================================================================= + +export function aeadSeal(ctx, nonce, plaintext, aad) { + try { + const name = aeadCipherName(ctx); + const key = aeadCipherKey(ctx); + const authTagLength = aeadTagSize(ctx); + + const aadOptions = aeadIsCcm(ctx) + ? { plaintextLength: plaintext.byteSize } + : undefined; + + const cipher = crypto.createCipheriv(name, key.rawBuffer, nonce.rawBuffer, { + authTagLength, + }); + cipher.setAAD(aad.rawBuffer, aadOptions); + + const updateOutput = cipher.update(plaintext.rawBuffer); + const finalOutput = cipher.final(); + const ciphertext = Buffer.concat([updateOutput, finalOutput]); + const tag = cipher.getAuthTag(); + return Result$Ok([BitArray$BitArray(ciphertext), BitArray$BitArray(tag)]); + } catch { + return Result$Error(undefined); + } +} + +export function aeadOpen(ctx, nonce, tag, ciphertext, aad) { + try { + const name = aeadCipherName(ctx); + const key = aeadCipherKey(ctx); + const authTagLength = aeadTagSize(ctx); + + const aadOptions = aeadIsCcm(ctx) + ? { plaintextLength: ciphertext.byteSize } + : undefined; + + const decipher = crypto.createDecipheriv( + name, + key.rawBuffer, + nonce.rawBuffer, + { authTagLength }, + ); + decipher.setAAD(aad.rawBuffer, aadOptions); + decipher.setAuthTag(tag.rawBuffer); + + const updateOutput = decipher.update(ciphertext.rawBuffer); + const finalOutput = decipher.final(); + const plaintext = Buffer.concat([updateOutput, finalOutput]); + return Result$Ok(BitArray$BitArray(plaintext)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// Block Ciphers (ECB, CBC, CTR) +// ============================================================================= + +function blockCipherNeedsPadding(ctx) { + return !blockIsCtr(ctx); +} + +export function blockCipherEncrypt(mode, plaintext) { + try { + const name = blockCipherName(mode); + const key = blockCipherKey(mode); + const iv = blockCipherIv(mode); + + const ivBuffer = iv.byteSize === 0 ? null : iv.rawBuffer; + + const cipher = crypto.createCipheriv(name, key.rawBuffer, ivBuffer); + cipher.setAutoPadding(blockCipherNeedsPadding(mode)); + + const updateOutput = cipher.update(plaintext.rawBuffer); + const finalOutput = cipher.final(); + const ciphertext = Buffer.concat([updateOutput, finalOutput]); + + return Result$Ok(BitArray$BitArray(ciphertext)); + } catch { + return Result$Error(undefined); + } +} + +export function blockCipherDecrypt(mode, ciphertext) { + try { + const name = blockCipherName(mode); + const key = blockCipherKey(mode); + const iv = blockCipherIv(mode); + + const ivBuffer = iv.byteSize === 0 ? null : iv.rawBuffer; + + const decipher = crypto.createDecipheriv(name, key.rawBuffer, ivBuffer); + decipher.setAutoPadding(blockCipherNeedsPadding(mode)); + + const updateOutput = decipher.update(ciphertext.rawBuffer); + const finalOutput = decipher.final(); + const plaintext = Buffer.concat([updateOutput, finalOutput]); + + return Result$Ok(BitArray$BitArray(plaintext)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// Key Wrapping (AES-KW, RFC 3394) +// ============================================================================= + +// RFC 3394 default IV +const KEYWRAP_DEFAULT_IV = Buffer.from([ + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, +]); + +function keywrapCipherName(cipher) { + const keySize = blockKeySize(cipher); + switch (keySize) { + case 128: + return "aes128-wrap"; + case 192: + return "aes192-wrap"; + case 256: + return "aes256-wrap"; + default: + throw new Error(`Unknown key size: ${keySize}`); + } +} + +export function blockCipherWrap(cipher, plaintext) { + try { + const algorithm = keywrapCipherName(cipher); + const key = blockAesKey(cipher).rawBuffer; + + const cipherObj = crypto.createCipheriv(algorithm, key, KEYWRAP_DEFAULT_IV); + const updateOutput = cipherObj.update(plaintext.rawBuffer); + const finalOutput = cipherObj.final(); + const ciphertext = Buffer.concat([updateOutput, finalOutput]); + + return Result$Ok(BitArray$BitArray(ciphertext)); + } catch { + return Result$Error(undefined); + } +} + +export function blockCipherUnwrap(cipher, ciphertext) { + try { + const algorithm = keywrapCipherName(cipher); + const key = blockAesKey(cipher).rawBuffer; + + const decipherObj = crypto.createDecipheriv( + algorithm, + key, + KEYWRAP_DEFAULT_IV, + ); + const updateOutput = decipherObj.update(ciphertext.rawBuffer); + const finalOutput = decipherObj.final(); + const plaintext = Buffer.concat([updateOutput, finalOutput]); + + return Result$Ok(BitArray$BitArray(plaintext)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// Curve Helpers (shared by EC, EdDSA, XDH) +// ============================================================================= + +function ecCurveToOpensslName(curve) { + if (Curve$isP256(curve)) return "prime256v1"; + if (Curve$isP384(curve)) return "secp384r1"; + if (Curve$isP521(curve)) return "secp521r1"; + if (Curve$isSecp256k1(curve)) return "secp256k1"; + throw new Error(`Unsupported curve: ${curve.constructor.name}`); +} + +function ecCurveToJwkCrv(curve) { + if (Curve$isP256(curve)) return "P-256"; + if (Curve$isP384(curve)) return "P-384"; + if (Curve$isP521(curve)) return "P-521"; + if (Curve$isSecp256k1(curve)) return "secp256k1"; + throw new Error(`Unsupported curve: ${curve.constructor.name}`); +} + +function jwkCrvToCurve(crv) { + switch (crv) { + case "P-256": + return Curve$P256(); + case "P-384": + return Curve$P384(); + case "P-521": + return Curve$P521(); + case "secp256k1": + return Curve$Secp256k1(); + default: + throw new Error(`Unsupported JWK crv: ${crv}`); + } +} + +function padStart(buffer, length) { + if (buffer.length >= length) return buffer; + const padding = Buffer.alloc(length - buffer.length, 0); + return Buffer.concat([padding, buffer]); +} + +export function ecPublicKeyFromPrivate(privateKey) { + if (privateKey.asymmetricKeyType !== "ec") { + throw new Error( + `Expected EC private key, got ${privateKey.asymmetricKeyType}`, + ); + } + return crypto.createPublicKey(privateKey); +} + +export function ecPrivateKeyCurve(key) { + const jwk = key.export({ format: "jwk" }); + return jwkCrvToCurve(jwk.crv); +} + +export function ecPublicKeyCurve(key) { + const jwk = key.export({ format: "jwk" }); + return jwkCrvToCurve(jwk.crv); +} + +export function eddsaPublicKeyFromPrivate(privateKey) { + const keyType = privateKey.asymmetricKeyType; + if (keyType !== "ed25519" && keyType !== "ed448") { + throw new Error(`Expected EdDSA private key, got ${keyType}`); + } + return crypto.createPublicKey(privateKey); +} + +function keyTypeToEddsaCurve(keyType) { + switch (keyType) { + case "ed25519": + return Curve$Ed25519(); + case "ed448": + return Curve$Ed448(); + default: + throw new Error(`Unsupported EdDSA key type: ${keyType}`); + } +} + +export function eddsaPrivateKeyCurve(key) { + return keyTypeToEddsaCurve(key.asymmetricKeyType); +} + +export function eddsaPublicKeyCurve(key) { + return keyTypeToEddsaCurve(key.asymmetricKeyType); +} + +export function rsaPublicKeyFromPrivate(privateKey) { + if (privateKey.asymmetricKeyType !== "rsa") { + throw new Error( + `Expected RSA private key, got ${privateKey.asymmetricKeyType}`, + ); + } + return crypto.createPublicKey(privateKey); +} + +export function rsaPrivateKeyModulusBits(key) { + return key.asymmetricKeyDetails.modulusLength; +} + +export function rsaPublicKeyModulusBits(key) { + return key.asymmetricKeyDetails.modulusLength; +} + +function base64UrlToInt(base64url) { + const buffer = Buffer.from(base64url, "base64url"); + let result = 0n; + for (const byte of buffer) { + result = (result << 8n) | BigInt(byte); + } + return Number(result); +} + +export function rsaPrivateKeyPublicExponent(key) { + const jwk = key.export({ format: "jwk" }); + return base64UrlToInt(jwk.e); +} + +export function rsaPublicKeyPublicExponent(key) { + const jwk = key.export({ format: "jwk" }); + return base64UrlToInt(jwk.e); +} + +export function rsaPrivateKeyModulus(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.n, "base64url")); +} + +export function rsaPublicKeyModulus(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.n, "base64url")); +} + +export function rsaPrivateKeyPublicExponentBytes(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.e, "base64url")); +} + +export function rsaPublicKeyExponentBytes(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.e, "base64url")); +} + +export function rsaPrivateKeyPrivateExponentBytes(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.d, "base64url")); +} + +export function rsaPrivateKeyPrime1(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.p, "base64url")); +} + +export function rsaPrivateKeyPrime2(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.q, "base64url")); +} + +export function rsaPrivateKeyExponent1(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.dp, "base64url")); +} + +export function rsaPrivateKeyExponent2(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.dq, "base64url")); +} + +export function rsaPrivateKeyCoefficient(key) { + const jwk = key.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.qi, "base64url")); +} + +export function rsaPublicKeyFromComponents(n, e) { + try { + const jwk = { + kty: "RSA", + n: Buffer.from(n.rawBuffer).toString("base64url"), + e: Buffer.from(e.rawBuffer).toString("base64url"), + }; + const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function modPow(baseBits, expBits, modBits) { + const base = BigInt( + "0x" + (Buffer.from(baseBits.rawBuffer).toString("hex") || "0"), + ); + const exp = BigInt( + "0x" + (Buffer.from(expBits.rawBuffer).toString("hex") || "0"), + ); + const mod = BigInt( + "0x" + (Buffer.from(modBits.rawBuffer).toString("hex") || "0"), + ); + + let result = 1n; + let b = base % mod; + let e = exp; + while (e > 0n) { + if (e % 2n === 1n) result = (result * b) % mod; + e /= 2n; + b = (b * b) % mod; + } + + let hex = result.toString(16); + if (hex.length % 2 !== 0) hex = "0" + hex; + return BitArray$BitArray(Buffer.from(hex, "hex")); +} + +export function rsaPrivateKeyFromFullComponents(n, e, d, p, q, dp, dq, qi) { + try { + const jwk = { + kty: "RSA", + n: Buffer.from(n.rawBuffer).toString("base64url"), + e: Buffer.from(e.rawBuffer).toString("base64url"), + d: Buffer.from(d.rawBuffer).toString("base64url"), + p: Buffer.from(p.rawBuffer).toString("base64url"), + q: Buffer.from(q.rawBuffer).toString("base64url"), + dp: Buffer.from(dp.rawBuffer).toString("base64url"), + dq: Buffer.from(dq.rawBuffer).toString("base64url"), + qi: Buffer.from(qi.rawBuffer).toString("base64url"), + }; + const privateKey = crypto.createPrivateKey({ key: jwk, format: "jwk" }); + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function xdhPublicKeyFromPrivate(privateKey) { + const keyType = privateKey.asymmetricKeyType; + if (keyType !== "x25519" && keyType !== "x448") { + throw new Error(`Expected XDH private key, got ${keyType}`); + } + return crypto.createPublicKey(privateKey); +} + +function keyTypeToXdhCurve(keyType) { + switch (keyType) { + case "x25519": + return Curve$X25519(); + case "x448": + return Curve$X448(); + default: + throw new Error(`Unsupported XDH key type: ${keyType}`); + } +} + +export function xdhPrivateKeyCurve(key) { + return keyTypeToXdhCurve(key.asymmetricKeyType); +} + +export function xdhPublicKeyCurve(key) { + return keyTypeToXdhCurve(key.asymmetricKeyType); +} + +// Extracts DER bytes from a PEM-encoded string. +function pemToDer(pem) { + const lines = pem.split("\n"); + const base64Lines = lines.filter( + (line) => !line.startsWith("-----") && line.trim() !== "", + ); + const base64 = base64Lines.join(""); + return Buffer.from(base64, "base64"); +} + +// OID 1.2.840.10045.2.1 (id-ecPublicKey) +const EC_PUBLIC_KEY_OID = Buffer.from([ + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, +]); + +// Validates that an EC SPKI DER uses a named curve OID (not explicit parameters). +// This mirrors Erlang's behavior which only accepts {namedCurve, OID} format. +function validateSpkiUsesNamedCurve(derBytes) { + const buf = Buffer.from(derBytes); + const oidIndex = buf.indexOf(EC_PUBLIC_KEY_OID); + if (oidIndex === -1) { + return false; + } + + const paramTagIndex = oidIndex + EC_PUBLIC_KEY_OID.length; + if (paramTagIndex >= buf.length) { + return false; + } + + const paramTag = buf[paramTagIndex]; + return paramTag === 0x06; +} + +// ============================================================================= +// Elliptic Curve (EC/ECDSA/ECDH) +// ============================================================================= + +export function ecGenerateKeyPair(curve) { + const namedCurve = ecCurveToOpensslName(curve); + const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", { + namedCurve, + }); + + return [privateKey, publicKey]; +} + +export function ecPrivateKeyFromBytes(curve, privateScalar) { + try { + const curveName = ecCurveToOpensslName(curve); + const coordSize = ecCoordinateSize(curve); + let privBuffer = Buffer.from(privateScalar.rawBuffer); + + // Handle DER integer encoding: may have leading 0x00 for sign or be shorter + if (privBuffer.length === 0 || privBuffer.length > coordSize + 1) { + return Result$Error(undefined); + } + if (privBuffer.length === coordSize + 1) { + if (privBuffer[0] !== 0x00) { + return Result$Error(undefined); + } + privBuffer = privBuffer.subarray(1); + } else if (privBuffer.length < coordSize) { + // Pad with leading zeros + const padded = Buffer.alloc(coordSize); + privBuffer.copy(padded, coordSize - privBuffer.length); + privBuffer = padded; + } + + const ecdh = crypto.createECDH(curveName); + ecdh.setPrivateKey(privBuffer); + const publicPoint = ecdh.getPublicKey(); + + const x = publicPoint.subarray(1, 1 + coordSize); + const y = publicPoint.subarray(1 + coordSize); + + const jwk = { + kty: "EC", + crv: ecCurveToJwkCrv(curve), + x: x.toString("base64url"), + y: y.toString("base64url"), + d: privBuffer.toString("base64url"), + }; + + const privateKey = crypto.createPrivateKey({ key: jwk, format: "jwk" }); + const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); + + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function ecPrivateKeyToBytes(privateKey) { + const jwk = privateKey.export({ format: "jwk" }); + return BitArray$BitArray(Buffer.from(jwk.d, "base64url")); +} + +export function ecPublicKeyFromDer(derBytes) { + try { + if (!validateSpkiUsesNamedCurve(derBytes.rawBuffer)) { + return Result$Error(undefined); + } + + const publicKey = crypto.createPublicKey({ + key: derBytes.rawBuffer, + format: "der", + type: "spki", + }); + + if (publicKey.asymmetricKeyType !== "ec") { + return Result$Error(undefined); + } + + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function ecPublicKeyFromRawPoint(curve, point) { + try { + const coordSize = ecCoordinateSize(curve); + const expectedSize = 1 + 2 * coordSize; + + if (point.byteSize !== expectedSize) { + return Result$Error(undefined); + } + + const pointBuffer = Buffer.from(point.rawBuffer); + + if (pointBuffer[0] !== 0x04) { + return Result$Error(undefined); + } + + const x = pointBuffer.subarray(1, 1 + coordSize); + const y = pointBuffer.subarray(1 + coordSize); + + const jwk = { + kty: "EC", + crv: ecCurveToJwkCrv(curve), + x: x.toString("base64url"), + y: y.toString("base64url"), + }; + + const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); + + // Validate point is on the curve by attempting ECDH computation. + // This mirrors Erlang's validate_ec_point which uses crypto:compute_key. + const curveName = ecCurveToOpensslName(curve); + const ecdh = crypto.createECDH(curveName); + ecdh.generateKeys(); + ecdh.computeSecret(pointBuffer); + + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function ecPublicKeyToRawPoint(key) { + const jwk = key.export({ format: "jwk" }); + const x = Buffer.from(jwk.x, "base64url"); + const y = Buffer.from(jwk.y, "base64url"); + + const coordSize = ecCoordinateSize(jwkCrvToCurve(jwk.crv)); + const xPadded = padStart(x, coordSize); + const yPadded = padStart(y, coordSize); + + return BitArray$BitArray( + Buffer.concat([Buffer.from([0x04]), xPadded, yPadded]), + ); +} + +export function ecdsaSign(privateKey, message, hashAlgorithm) { + const algorithmName = hashAlgorithmName(hashAlgorithm); + const signature = crypto.sign(algorithmName, message.rawBuffer, { + key: privateKey, + dsaEncoding: "der", + }); + return BitArray$BitArray(signature); +} + +export function ecdsaVerify(publicKey, message, signature, hashAlgorithm) { + try { + const algorithmName = hashAlgorithmName(hashAlgorithm); + return crypto.verify( + algorithmName, + message.rawBuffer, + { + key: publicKey, + dsaEncoding: "der", + }, + signature.rawBuffer, + ); + } catch { + return false; + } +} + +export function ecdhComputeSharedSecret(privateKey, peerPublicKey) { + try { + const sharedSecret = crypto.diffieHellman({ + privateKey, + publicKey: peerPublicKey, + }); + return Result$Ok(BitArray$BitArray(sharedSecret)); + } catch { + return Result$Error(undefined); + } +} + +// EC Import/Export Helpers + +function importPrivateKeyPem(pem, type, allowedTypes) { + try { + const privateKey = crypto.createPrivateKey({ + key: pem, + format: "pem", + type, + }); + + if (!allowedTypes.includes(privateKey.asymmetricKeyType)) { + return Result$Error(undefined); + } + + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +function importPrivateKeyDer(der, type, allowedTypes) { + try { + const privateKey = crypto.createPrivateKey({ + key: der.rawBuffer, + format: "der", + type, + }); + + if (!allowedTypes.includes(privateKey.asymmetricKeyType)) { + return Result$Error(undefined); + } + + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +function importPublicKeyPem(pem, type, allowedTypes) { + try { + const publicKey = crypto.createPublicKey({ + key: pem, + format: "pem", + type, + }); + + if (!allowedTypes.includes(publicKey.asymmetricKeyType)) { + return Result$Error(undefined); + } + + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +function importPublicKeyDer(der, type, allowedTypes, preValidate) { + try { + if (preValidate && !preValidate(der.rawBuffer)) { + return Result$Error(undefined); + } + + const publicKey = crypto.createPublicKey({ + key: der.rawBuffer, + format: "der", + type, + }); + + if (!allowedTypes.includes(publicKey.asymmetricKeyType)) { + return Result$Error(undefined); + } + + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +// EC Import Functions + +export function ecImportPrivateKeyPem(pem) { + return importPrivateKeyPem(pem, "pkcs8", ["ec"]); +} + +export function ecImportPrivateKeyDer(der) { + return importPrivateKeyDer(der, "pkcs8", ["ec"]); +} + +export function ecImportPublicKeyPem(pem) { + try { + // Extract DER from PEM and validate it uses named curves + const derBytes = pemToDer(pem); + if (!validateSpkiUsesNamedCurve(derBytes)) { + return Result$Error(undefined); + } + + return importPublicKeyPem(pem, "spki", ["ec"]); + } catch { + return Result$Error(undefined); + } +} + +export function ecImportPublicKeyDer(der) { + return importPublicKeyDer(der, "spki", ["ec"], validateSpkiUsesNamedCurve); +} + +// EC Export Functions + +export function ecExportPrivateKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "pkcs8" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function ecExportPrivateKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "pkcs8" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +export function ecExportPublicKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "spki" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function ecExportPublicKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "spki" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// X25519/X448 Key Exchange (XDH) +// ============================================================================= + +// DER prefixes for wrapping raw key bytes into PKCS#8/SPKI format (RFC 8410). +// These are stable, standardized encodings that prepend ASN.1 structure to raw keys. +// +// PKCS#8 PrivateKeyInfo structure (RFC 5958): +// SEQUENCE { +// INTEGER 0 (version) +// SEQUENCE { OBJECT IDENTIFIER (algorithm OID) } +// OCTET STRING { OCTET STRING (raw private key) } +// } +// +// X25519 OID: 1.3.101.110 (06 03 2b 65 6e) +// X448 OID: 1.3.101.111 (06 03 2b 65 6f) +const XDH_PRIVATE_DER_PREFIX = { + // 30 2e SEQUENCE (46 bytes total) + // 02 01 00 INTEGER 0 (version) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 6e OID 1.3.101.110 (X25519) + // 04 22 OCTET STRING (34 bytes) + // 04 20 OCTET STRING (32 bytes) - the raw key follows + x25519: Buffer.from("302e020100300506032b656e04220420", "hex"), + // 30 46 SEQUENCE (70 bytes total) + // 02 01 00 INTEGER 0 (version) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 6f OID 1.3.101.111 (X448) + // 04 3a OCTET STRING (58 bytes) + // 04 38 OCTET STRING (56 bytes) - the raw key follows + x448: Buffer.from("3046020100300506032b656f043a0438", "hex"), +}; + +// SPKI SubjectPublicKeyInfo structure (RFC 5280): +// SEQUENCE { +// SEQUENCE { OBJECT IDENTIFIER (algorithm OID) } +// BIT STRING (raw public key) +// } +const XDH_PUBLIC_DER_PREFIX = { + // 30 2a SEQUENCE (42 bytes total) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 6e OID 1.3.101.110 (X25519) + // 03 21 00 BIT STRING (33 bytes, 0 unused bits) - 32-byte key follows + x25519: Buffer.from("302a300506032b656e032100", "hex"), + // 30 42 SEQUENCE (66 bytes total) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 6f OID 1.3.101.111 (X448) + // 03 39 00 BIT STRING (57 bytes, 0 unused bits) - 56-byte key follows + x448: Buffer.from("3042300506032b656f033900", "hex"), +}; + +function xdhCurveName(curve) { + if (Curve$isX25519(curve)) return "x25519"; + if (Curve$isX448(curve)) return "x448"; + throw new Error(`Unsupported XDH curve: ${curve.constructor.name}`); +} + +export function xdhGenerateKeyPair(curve) { + const curveName = xdhCurveName(curve); + const { privateKey, publicKey } = crypto.generateKeyPairSync(curveName); + return [privateKey, publicKey]; +} + +export function xdhPrivateKeyFromBytes(curve, privateBytes) { + try { + const curveName = xdhCurveName(curve); + const expectedSize = xdhKeySize(curve); + if (privateBytes.byteSize !== expectedSize) { + return Result$Error(undefined); + } + const prefix = XDH_PRIVATE_DER_PREFIX[curveName]; + const der = Buffer.concat([prefix, Buffer.from(privateBytes.rawBuffer)]); + const privateKey = crypto.createPrivateKey({ + key: der, + format: "der", + type: "pkcs8", + }); + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function xdhPublicKeyFromBytes(curve, publicBytes) { + try { + const curveName = xdhCurveName(curve); + const expectedSize = xdhKeySize(curve); + if (publicBytes.byteSize !== expectedSize) { + return Result$Error(undefined); + } + const prefix = XDH_PUBLIC_DER_PREFIX[curveName]; + const der = Buffer.concat([prefix, Buffer.from(publicBytes.rawBuffer)]); + const publicKey = crypto.createPublicKey({ + key: der, + format: "der", + type: "spki", + }); + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function xdhPrivateKeyToBytes(privateKey) { + const der = privateKey.export({ format: "der", type: "pkcs8" }); + const curveName = privateKey.asymmetricKeyType; + const prefixLen = XDH_PRIVATE_DER_PREFIX[curveName].length; + return BitArray$BitArray(der.subarray(prefixLen)); +} + +export function xdhPublicKeyToBytes(publicKey) { + const der = publicKey.export({ format: "der", type: "spki" }); + const curveName = publicKey.asymmetricKeyType; + const prefixLen = XDH_PUBLIC_DER_PREFIX[curveName].length; + return BitArray$BitArray(der.subarray(prefixLen)); +} + +export function xdhComputeSharedSecret(privateKey, peerPublicKey) { + try { + const sharedSecret = crypto.diffieHellman({ + privateKey, + publicKey: peerPublicKey, + }); + return Result$Ok(BitArray$BitArray(sharedSecret)); + } catch { + return Result$Error(undefined); + } +} + +// XDH Import Functions + +export function xdhImportPrivateKeyPem(pem) { + return importPrivateKeyPem(pem, "pkcs8", ["x25519", "x448"]); +} + +export function xdhImportPrivateKeyDer(der) { + return importPrivateKeyDer(der, "pkcs8", ["x25519", "x448"]); +} + +export function xdhImportPublicKeyPem(pem) { + return importPublicKeyPem(pem, "spki", ["x25519", "x448"]); +} + +export function xdhImportPublicKeyDer(der) { + return importPublicKeyDer(der, "spki", ["x25519", "x448"]); +} + +// XDH Export Functions + +export function xdhExportPrivateKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "pkcs8" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function xdhExportPrivateKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "pkcs8" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +export function xdhExportPublicKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "spki" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function xdhExportPublicKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "spki" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// RSA +// ============================================================================= + +const RSA_PUBLIC_EXPONENT = 65537; + +export function rsaGenerateKeyPair(modulusLength) { + const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { + modulusLength, + publicExponent: RSA_PUBLIC_EXPONENT, + }); + return [privateKey, publicKey]; +} + +// RSA Signing + +function rsaPssSaltLength(saltLength) { + if (PssSaltLength$isSaltLengthHashLen(saltLength)) { + return crypto.constants.RSA_PSS_SALTLEN_DIGEST; + } + if (PssSaltLength$isSaltLengthMax(saltLength)) { + return crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN; + } + if (PssSaltLength$isSaltLengthExplicit(saltLength)) { + return PssSaltLength$SaltLengthExplicit$0(saltLength); + } + throw new Error(`Unknown salt length: ${saltLength.constructor.name}`); +} + +function rsaSignPaddingOpts(padding) { + if (SignPadding$isPkcs1v15(padding)) { + return { padding: crypto.constants.RSA_PKCS1_PADDING }; + } + if (SignPadding$isPss(padding)) { + const saltLength = rsaPssSaltLength(SignPadding$Pss$0(padding)); + return { + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength, + }; + } + throw new Error(`Unknown sign padding: ${padding.constructor.name}`); +} + +export function rsaSign(privateKey, message, hash, padding) { + const algorithmName = hashAlgorithmName(hash); + const signature = crypto.sign(algorithmName, message.rawBuffer, { + key: privateKey, + ...rsaSignPaddingOpts(padding), + }); + return BitArray$BitArray(signature); +} + +export function rsaVerify(publicKey, message, signature, hash, padding) { + try { + const algorithmName = hashAlgorithmName(hash); + const opts = rsaSignPaddingOpts(padding); + // Node.js requires RSA_PSS_SALTLEN_AUTO for verification because + // RSA_PSS_SALTLEN_MAX_SIGN (-2) is only valid during signing. + // AUTO allows verification to detect the salt length from the signature. + if (opts.saltLength === crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN) { + opts.saltLength = crypto.constants.RSA_PSS_SALTLEN_AUTO; + } + return crypto.verify( + algorithmName, + message.rawBuffer, + { key: publicKey, ...opts }, + signature.rawBuffer, + ); + } catch { + return false; + } +} + +// RSA Encryption + +function rsaEncryptPaddingOpts(padding) { + if (EncryptPadding$isEncryptPkcs1v15(padding)) { + return { padding: crypto.constants.RSA_PKCS1_PADDING }; + } + if (EncryptPadding$isOaep(padding)) { + const opts = { + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: hashAlgorithmName(padding.hash), + }; + if (padding.label?.byteSize > 0) { + opts.oaepLabel = padding.label.rawBuffer; + } + return opts; + } + throw new Error(`Unknown encrypt padding: ${padding.constructor.name}`); +} + +export function rsaEncrypt(publicKey, plaintext, padding) { + try { + const opts = rsaEncryptPaddingOpts(padding); + const ciphertext = crypto.publicEncrypt( + { key: publicKey, ...opts }, + plaintext.rawBuffer, + ); + return Result$Ok(BitArray$BitArray(ciphertext)); + } catch { + return Result$Error(undefined); + } +} + +export function rsaDecrypt(privateKey, ciphertext, padding) { + try { + const opts = rsaEncryptPaddingOpts(padding); + const plaintext = crypto.privateDecrypt( + { key: privateKey, ...opts }, + ciphertext.rawBuffer, + ); + return Result$Ok(BitArray$BitArray(plaintext)); + } catch { + return Result$Error(undefined); + } +} + +// RSA Import Functions + +function rsaFormatToType(format, isPrivate) { + if (isPrivate) { + if (PrivateKeyFormat$isPkcs1(format)) return "pkcs1"; + if (PrivateKeyFormat$isPkcs8(format)) return "pkcs8"; + throw new Error(`Unknown private key format: ${format.constructor.name}`); + } else { + if (PublicKeyFormat$isRsaPublicKey(format)) return "pkcs1"; + if (PublicKeyFormat$isSpki(format)) return "spki"; + throw new Error(`Unknown public key format: ${format.constructor.name}`); + } +} + +export function rsaPrivateKeyFromPkcs8(derBytes) { + try { + const privateKey = crypto.createPrivateKey({ + key: derBytes.rawBuffer, + format: "der", + type: "pkcs8", + }); + if (privateKey.asymmetricKeyType !== "rsa") { + return Result$Error(undefined); + } + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function rsaPublicKeyFromX509(derBytes) { + try { + const publicKey = crypto.createPublicKey({ + key: derBytes.rawBuffer, + format: "der", + type: "spki", + }); + if (publicKey.asymmetricKeyType !== "rsa") { + return Result$Error(undefined); + } + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function rsaImportPrivateKeyPem(pem, format) { + const type = rsaFormatToType(format, true); + return importPrivateKeyPem(pem, type, ["rsa"]); +} + +export function rsaImportPrivateKeyDer(der, format) { + const type = rsaFormatToType(format, true); + return importPrivateKeyDer(der, type, ["rsa"]); +} + +export function rsaImportPublicKeyPem(pem, format) { + const type = rsaFormatToType(format, false); + return importPublicKeyPem(pem, type, ["rsa"]); +} + +export function rsaImportPublicKeyDer(der, format) { + const type = rsaFormatToType(format, false); + return importPublicKeyDer(der, type, ["rsa"]); +} + +// RSA Export Functions + +export function rsaExportPrivateKeyPem(key, format) { + try { + const type = rsaFormatToType(format, true); + const exported = key.export({ format: "pem", type }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function rsaExportPrivateKeyDer(key, format) { + try { + const type = rsaFormatToType(format, true); + const exported = key.export({ format: "der", type }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +export function rsaExportPublicKeyPem(key, format) { + try { + const type = rsaFormatToType(format, false); + const exported = key.export({ format: "pem", type }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function rsaExportPublicKeyDer(key, format) { + try { + const type = rsaFormatToType(format, false); + const exported = key.export({ format: "der", type }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +// ============================================================================= +// EdDSA (Ed25519/Ed448) +// ============================================================================= + +// DER prefixes for wrapping raw key bytes into PKCS#8/SPKI format (RFC 8410). +// These are stable, standardized encodings that prepend ASN.1 structure to raw keys. +// +// Ed25519 OID: 1.3.101.112 (06 03 2b 65 70) +// Ed448 OID: 1.3.101.113 (06 03 2b 65 71) +const EDDSA_PRIVATE_DER_PREFIX = { + // 30 2e SEQUENCE (46 bytes total) + // 02 01 00 INTEGER 0 (version) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 70 OID 1.3.101.112 (Ed25519) + // 04 22 OCTET STRING (34 bytes) + // 04 20 OCTET STRING (32 bytes) - the raw key follows + ed25519: Buffer.from("302e020100300506032b657004220420", "hex"), + // 30 47 SEQUENCE (71 bytes total) + // 02 01 00 INTEGER 0 (version) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 71 OID 1.3.101.113 (Ed448) + // 04 3b OCTET STRING (59 bytes) + // 04 39 OCTET STRING (57 bytes) - the raw key follows + ed448: Buffer.from("3047020100300506032b6571043b0439", "hex"), +}; + +const EDDSA_PUBLIC_DER_PREFIX = { + // 30 2a SEQUENCE (42 bytes total) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 70 OID 1.3.101.112 (Ed25519) + // 03 21 00 BIT STRING (33 bytes, 0 unused bits) - 32-byte key follows + ed25519: Buffer.from("302a300506032b6570032100", "hex"), + // 30 43 SEQUENCE (67 bytes total) + // 30 05 SEQUENCE (5 bytes) + // 06 03 2b 65 71 OID 1.3.101.113 (Ed448) + // 03 3a 00 BIT STRING (58 bytes, 0 unused bits) - 57-byte key follows + ed448: Buffer.from("3043300506032b6571033a00", "hex"), +}; + +function eddsaCurveName(curve) { + if (Curve$isEd25519(curve)) return "ed25519"; + if (Curve$isEd448(curve)) return "ed448"; + throw new Error(`Unsupported EdDSA curve: ${curve.constructor.name}`); +} + +export function eddsaGenerateKeyPair(curve) { + const curveName = eddsaCurveName(curve); + const { privateKey, publicKey } = crypto.generateKeyPairSync(curveName); + return [privateKey, publicKey]; +} + +export function eddsaPrivateKeyFromBytes(curve, privateBytes) { + try { + const curveName = eddsaCurveName(curve); + const expectedSize = eddsaKeySize(curve); + if (privateBytes.byteSize !== expectedSize) { + return Result$Error(undefined); + } + const prefix = EDDSA_PRIVATE_DER_PREFIX[curveName]; + const der = Buffer.concat([prefix, Buffer.from(privateBytes.rawBuffer)]); + const privateKey = crypto.createPrivateKey({ + key: der, + format: "der", + type: "pkcs8", + }); + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function eddsaPublicKeyFromBytes(curve, publicBytes) { + try { + const curveName = eddsaCurveName(curve); + const expectedSize = eddsaKeySize(curve); + if (publicBytes.byteSize !== expectedSize) { + return Result$Error(undefined); + } + const prefix = EDDSA_PUBLIC_DER_PREFIX[curveName]; + const der = Buffer.concat([prefix, Buffer.from(publicBytes.rawBuffer)]); + const publicKey = crypto.createPublicKey({ + key: der, + format: "der", + type: "spki", + }); + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function eddsaPrivateKeyToBytes(privateKey) { + const der = privateKey.export({ format: "der", type: "pkcs8" }); + const curveName = privateKey.asymmetricKeyType; + const prefixLen = EDDSA_PRIVATE_DER_PREFIX[curveName].length; + return BitArray$BitArray(der.subarray(prefixLen)); +} + +export function eddsaPublicKeyToBytes(publicKey) { + const der = publicKey.export({ format: "der", type: "spki" }); + const curveName = publicKey.asymmetricKeyType; + const prefixLen = EDDSA_PUBLIC_DER_PREFIX[curveName].length; + return BitArray$BitArray(der.subarray(prefixLen)); +} + +export function eddsaSign(privateKey, message) { + const signature = crypto.sign(null, message.rawBuffer, privateKey); + return BitArray$BitArray(signature); +} + +export function eddsaVerify(publicKey, message, signature) { + try { + return crypto.verify( + null, + message.rawBuffer, + publicKey, + signature.rawBuffer, + ); + } catch { + return false; + } +} + +// EdDSA Import Functions + +export function eddsaImportPrivateKeyPem(pem) { + return importPrivateKeyPem(pem, "pkcs8", ["ed25519", "ed448"]); +} + +export function eddsaImportPrivateKeyDer(der) { + return importPrivateKeyDer(der, "pkcs8", ["ed25519", "ed448"]); +} + +export function eddsaImportPublicKeyPem(pem) { + return importPublicKeyPem(pem, "spki", ["ed25519", "ed448"]); +} + +export function eddsaImportPublicKeyDer(der) { + return importPublicKeyDer(der, "spki", ["ed25519", "ed448"]); +} + +// EdDSA Export Functions + +export function eddsaExportPrivateKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "pkcs8" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function eddsaExportPrivateKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "pkcs8" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} + +export function eddsaExportPublicKeyPem(key) { + try { + const exported = key.export({ format: "pem", type: "spki" }); + return Result$Ok(exported); + } catch { + return Result$Error(undefined); + } +} + +export function eddsaExportPublicKeyDer(key) { + try { + const exported = key.export({ format: "der", type: "spki" }); + return Result$Ok(BitArray$BitArray(exported)); + } catch { + return Result$Error(undefined); + } +} diff --git a/examples/http_interactions/build/packages/logging/README.md b/examples/http_interactions/build/packages/logging/README.md new file mode 100644 index 0000000..0623ff3 --- /dev/null +++ b/examples/http_interactions/build/packages/logging/README.md @@ -0,0 +1,31 @@ +# logging + +Configuration for the Erlang logger. + +[![Package Version](https://img.shields.io/hexpm/v/logging)](https://hex.pm/packages/logging) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/logging/) + +```sh +gleam add logging@1 +``` + +```gleam +import logging.{Info} + +pub fn main() { + // Run this once at the start of your program + logging.configure() + + // And get logging! + logging.log(Info, "Hello, Joe!") +} +``` + +## Disabling the colored output + +When using some logger services, colored output can be superfluous, because +they're not processed at all, and appears as real characters. You can set +the `NO_COLOUR` or `NO_COLOR` environment variable to any string that _is not_ +`"false"` or the empty string to disable the colored output from the logger. + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/logging/gleam.toml b/examples/http_interactions/build/packages/logging/gleam.toml new file mode 100644 index 0000000..5b3b7e4 --- /dev/null +++ b/examples/http_interactions/build/packages/logging/gleam.toml @@ -0,0 +1,15 @@ +name = "logging" +version = "1.5.0" +description = "Configuration for the Erlang logger" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "logging" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = "~> 0.34 or ~> 1.0" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/examples/http_interactions/build/packages/logging/src/logging.app.src b/examples/http_interactions/build/packages/logging/src/logging.app.src new file mode 100644 index 0000000..02e2b5f --- /dev/null +++ b/examples/http_interactions/build/packages/logging/src/logging.app.src @@ -0,0 +1,9 @@ +{application, logging, [ + {vsn, "1.5.0"}, + {applications, [gleam_stdlib]}, + {description, "Configuration for the Erlang logger"}, + {modules, [logging, + logging@@main, + logging_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/logging/src/logging.erl b/examples/http_interactions/build/packages/logging/src/logging.erl new file mode 100644 index 0000000..4de3967 --- /dev/null +++ b/examples/http_interactions/build/packages/logging/src/logging.erl @@ -0,0 +1,56 @@ +-module(logging). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/logging.gleam"). +-export([configure/0, log/2, set_level/1]). +-export_type([log_level/0, do_not_leak/0, key/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type log_level() :: emergency | + alert | + critical | + error | + warning | + notice | + info | + debug. + +-type do_not_leak() :: any(). + +-type key() :: level. + +-file("src/logging.gleam", 25). +?DOC( + " Configure the default Erlang logger handler with a pretty Gleam output\n" + " format, and sets the logging level to `Info`.\n" + "\n" + " ## Interaction with Elixir\n" + "\n" + " Elixir's built-in `logger` application removes Erlang's default logger\n" + " handler and replaces it with its own code, so if you have an Elixir package\n" + " in your project then this code will not be able to configure the logger as\n" + " it could normally.\n" +). +-spec configure() -> nil. +configure() -> + logging_ffi:configure(). + +-file("src/logging.gleam", 29). +?DOC(" Log a message to the Erlang logger at the given log level.\n"). +-spec log(log_level(), binary()) -> nil. +log(Level, Message) -> + logger:log(Level, Message), + nil. + +-file("src/logging.gleam", 42). +?DOC(" Change the log visibility level to be output.\n"). +-spec set_level(log_level()) -> nil. +set_level(Level) -> + logger:set_primary_config(level, Level), + nil. diff --git a/examples/http_interactions/build/packages/logging/src/logging.gleam b/examples/http_interactions/build/packages/logging/src/logging.gleam new file mode 100644 index 0000000..b9e6a58 --- /dev/null +++ b/examples/http_interactions/build/packages/logging/src/logging.gleam @@ -0,0 +1,49 @@ +pub type LogLevel { + Emergency + Alert + Critical + Error + Warning + Notice + Info + Debug +} + +type DoNotLeak + +/// Configure the default Erlang logger handler with a pretty Gleam output +/// format, and sets the logging level to `Info`. +/// +/// ## Interaction with Elixir +/// +/// Elixir's built-in `logger` application removes Erlang's default logger +/// handler and replaces it with its own code, so if you have an Elixir package +/// in your project then this code will not be able to configure the logger as +/// it could normally. +/// +@external(erlang, "logging_ffi", "configure") +pub fn configure() -> Nil + +/// Log a message to the Erlang logger at the given log level. +/// +pub fn log(level: LogLevel, message: String) -> Nil { + erlang_log(level, message) + Nil +} + +@external(erlang, "logger", "log") +fn erlang_log(level: LogLevel, message: String) -> DoNotLeak + +@external(erlang, "logger", "set_primary_config") +fn set_primary_config_level(key: Key, level: LogLevel) -> DoNotLeak + +/// Change the log visibility level to be output. +/// +pub fn set_level(level: LogLevel) -> Nil { + set_primary_config_level(Level, level) + Nil +} + +type Key { + Level +} diff --git a/examples/http_interactions/build/packages/logging/src/logging_ffi.erl b/examples/http_interactions/build/packages/logging/src/logging_ffi.erl new file mode 100644 index 0000000..eff2251 --- /dev/null +++ b/examples/http_interactions/build/packages/logging/src/logging_ffi.erl @@ -0,0 +1,87 @@ +-module(logging_ffi). +-export([configure/0, format/2]). + +configure() -> + logger:update_primary_config(#{ + level => info, + filter_default => log, + filters => [ + {domain,{fun logger_filters:domain/2, {stop, sub, [sasl]}}}, + {domain,{fun logger_filters:domain/2, {stop, sub, [supervisor_report]}}}, + {progress, {fun logger_filters:progress/2, stop}} + ], + metadata => #{} + }), + logger:update_handler_config(default, #{ + formatter => {logging_ffi, #{colored => is_colored_output()}} + }), + nil. + +is_colored_output() -> + not ( + envvar_enabled("NO_COLOUR", false) + orelse envvar_enabled("NO_COLOR", false) + ). + +envvar_enabled(Name, Default) -> + case os:getenv(Name) of + % Not set, use default + false -> Default; + % Set to a false value + "" -> false; + "false" -> false; + % Otherwise it is enabled + _ -> true + end. + +format(#{level := Level, msg := Msg, meta := _Meta}, Config) -> + [format_level(Level, Config), format_msg(Msg), $\n]. + +format_level(Level, #{colored := Colored}) -> + case Level of + emergency when Colored -> "\x1b[1;41mEMRG\x1b[0m"; + alert when Colored -> "\x1b[1;41mALRT\x1b[0m"; + critical when Colored -> "\x1b[1;41mCRIT\x1b[0m"; + error when Colored -> "\x1b[1;31mEROR\x1b[0m"; + warning when Colored -> "\x1b[1;33mWARN\x1b[0m"; + notice when Colored -> "\x1b[1;32mNTCE\x1b[0m"; + info when Colored -> "\x1b[1;34mINFO\x1b[0m"; + debug when Colored -> "\x1b[1;36mDEBG\x1b[0m"; + emergency -> "EMRG"; + alert -> "ALRT"; + critical -> "CRIT"; + error -> "EROR"; + warning -> "WARN"; + notice -> "NTCE"; + info -> "INFO"; + debug -> "DEBG" + end. + +format_msg(Report0) -> + case Report0 of + {string, Msg} -> + [$\s, Msg]; + + {report, Report1} when is_map(Report1) -> + format_kv(maps:to_list(Report1)); + + {report, Report1} when is_list(Report1) -> + format_kv(Report1); + + _ -> + [$\s, gleam@string:inspect(Report0)] + end. + +format_kv(Pairs) -> + case Pairs of + [] -> []; + [{K, V} | Rest] when is_atom(K) -> [ + $\s, erlang:atom_to_binary(K), $=, gleam@string:inspect(V) + | format_kv(Rest) + ]; + [{K, V} | Rest] -> [ + $\s, gleam@string:inspect(K), $=, gleam@string:inspect(V) + | format_kv(Rest) + ]; + Other -> gleam@string:inspect(Other) + end. diff --git a/examples/http_interactions/build/packages/marceau/README.md b/examples/http_interactions/build/packages/marceau/README.md new file mode 100644 index 0000000..df8b948 --- /dev/null +++ b/examples/http_interactions/build/packages/marceau/README.md @@ -0,0 +1,24 @@ +# marceau + +[![Package Version](https://img.shields.io/hexpm/v/marceau)](https://hex.pm/packages/marceau) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/marceau/) + +A MIME types library for Gleam. + +```sh +gleam add marceau +``` +```gleam +import marceau + +pub fn main() { + marceau.extension_to_mime_type("json") + // -> "application/json" + + marceau.mime_type_to_extensions("application/json") + // -> ["json"] +} +``` + +This library is based off of the [mimerl](https://hex.pm/packages/mimerl) Erlang +library but ported to pure Gleam so it can be used on any target and platform. diff --git a/examples/http_interactions/build/packages/marceau/gleam.toml b/examples/http_interactions/build/packages/marceau/gleam.toml new file mode 100644 index 0000000..d54d4b6 --- /dev/null +++ b/examples/http_interactions/build/packages/marceau/gleam.toml @@ -0,0 +1,14 @@ +name = "marceau" +version = "1.3.0" +description = "A MIME types library for Gleam" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "marceau" } +links = [ + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] + +[dev-dependencies] +gleeunit = "~> 1.0" +gleam_stdlib = "~> 0.31" diff --git a/examples/http_interactions/build/packages/marceau/src/marceau.app.src b/examples/http_interactions/build/packages/marceau/src/marceau.app.src new file mode 100644 index 0000000..2229759 --- /dev/null +++ b/examples/http_interactions/build/packages/marceau/src/marceau.app.src @@ -0,0 +1,8 @@ +{application, marceau, [ + {vsn, "1.3.0"}, + {applications, [gleam_stdlib, + gleeunit]}, + {description, "A MIME types library for Gleam"}, + {modules, [marceau]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/marceau/src/marceau.erl b/examples/http_interactions/build/packages/marceau/src/marceau.erl new file mode 100644 index 0000000..09cf8b4 --- /dev/null +++ b/examples/http_interactions/build/packages/marceau/src/marceau.erl @@ -0,0 +1,5393 @@ +-module(marceau). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([extension_to_mime_type/1, mime_type_to_extensions/1]). + +-file("/Users/louis/src/gleam/marceau/src/marceau.gleam", 1). +-spec extension_to_mime_type(binary()) -> binary(). +extension_to_mime_type(Extension) -> + case Extension of + <<"123"/utf8>> -> + <<"application/vnd.lotus-1-2-3"/utf8>>; + + <<"3dml"/utf8>> -> + <<"text/vnd.in3d.3dml"/utf8>>; + + <<"3ds"/utf8>> -> + <<"image/x-3ds"/utf8>>; + + <<"3g2"/utf8>> -> + <<"video/3gpp2"/utf8>>; + + <<"3gp"/utf8>> -> + <<"video/3gpp"/utf8>>; + + <<"7z"/utf8>> -> + <<"application/x-7z-compressed"/utf8>>; + + <<"aab"/utf8>> -> + <<"application/x-authorware-bin"/utf8>>; + + <<"aac"/utf8>> -> + <<"audio/x-aac"/utf8>>; + + <<"aam"/utf8>> -> + <<"application/x-authorware-map"/utf8>>; + + <<"aas"/utf8>> -> + <<"application/x-authorware-seg"/utf8>>; + + <<"abw"/utf8>> -> + <<"application/x-abiword"/utf8>>; + + <<"ac"/utf8>> -> + <<"application/pkix-attr-cert"/utf8>>; + + <<"acc"/utf8>> -> + <<"application/vnd.americandynamics.acc"/utf8>>; + + <<"ace"/utf8>> -> + <<"application/x-ace-compressed"/utf8>>; + + <<"acu"/utf8>> -> + <<"application/vnd.acucobol"/utf8>>; + + <<"acutc"/utf8>> -> + <<"application/vnd.acucorp"/utf8>>; + + <<"adp"/utf8>> -> + <<"audio/adpcm"/utf8>>; + + <<"aep"/utf8>> -> + <<"application/vnd.audiograph"/utf8>>; + + <<"afm"/utf8>> -> + <<"application/x-font-type1"/utf8>>; + + <<"afp"/utf8>> -> + <<"application/vnd.ibm.modcap"/utf8>>; + + <<"ahead"/utf8>> -> + <<"application/vnd.ahead.space"/utf8>>; + + <<"ai"/utf8>> -> + <<"application/postscript"/utf8>>; + + <<"aif"/utf8>> -> + <<"audio/x-aiff"/utf8>>; + + <<"aifc"/utf8>> -> + <<"audio/x-aiff"/utf8>>; + + <<"aiff"/utf8>> -> + <<"audio/x-aiff"/utf8>>; + + <<"air"/utf8>> -> + <<"application/vnd.adobe.air-application-installer-package+zip"/utf8>>; + + <<"ait"/utf8>> -> + <<"application/vnd.dvb.ait"/utf8>>; + + <<"ami"/utf8>> -> + <<"application/vnd.amiga.ami"/utf8>>; + + <<"apk"/utf8>> -> + <<"application/vnd.android.package-archive"/utf8>>; + + <<"appcache"/utf8>> -> + <<"text/cache-manifest"/utf8>>; + + <<"application"/utf8>> -> + <<"application/x-ms-application"/utf8>>; + + <<"apr"/utf8>> -> + <<"application/vnd.lotus-approach"/utf8>>; + + <<"arc"/utf8>> -> + <<"application/x-freearc"/utf8>>; + + <<"asc"/utf8>> -> + <<"application/pgp-signature"/utf8>>; + + <<"asf"/utf8>> -> + <<"video/x-ms-asf"/utf8>>; + + <<"asm"/utf8>> -> + <<"text/x-asm"/utf8>>; + + <<"aso"/utf8>> -> + <<"application/vnd.accpac.simply.aso"/utf8>>; + + <<"asx"/utf8>> -> + <<"video/x-ms-asf"/utf8>>; + + <<"atc"/utf8>> -> + <<"application/vnd.acucorp"/utf8>>; + + <<"atom"/utf8>> -> + <<"application/atom+xml"/utf8>>; + + <<"atomcat"/utf8>> -> + <<"application/atomcat+xml"/utf8>>; + + <<"atomsvc"/utf8>> -> + <<"application/atomsvc+xml"/utf8>>; + + <<"atx"/utf8>> -> + <<"application/vnd.antix.game-component"/utf8>>; + + <<"au"/utf8>> -> + <<"audio/basic"/utf8>>; + + <<"avi"/utf8>> -> + <<"video/x-msvideo"/utf8>>; + + <<"avif"/utf8>> -> + <<"image/avif"/utf8>>; + + <<"aw"/utf8>> -> + <<"application/applixware"/utf8>>; + + <<"azf"/utf8>> -> + <<"application/vnd.airzip.filesecure.azf"/utf8>>; + + <<"azs"/utf8>> -> + <<"application/vnd.airzip.filesecure.azs"/utf8>>; + + <<"azw"/utf8>> -> + <<"application/vnd.amazon.ebook"/utf8>>; + + <<"bat"/utf8>> -> + <<"application/x-msdownload"/utf8>>; + + <<"bcpio"/utf8>> -> + <<"application/x-bcpio"/utf8>>; + + <<"bdf"/utf8>> -> + <<"application/x-font-bdf"/utf8>>; + + <<"bdm"/utf8>> -> + <<"application/vnd.syncml.dm+wbxml"/utf8>>; + + <<"bed"/utf8>> -> + <<"application/vnd.realvnc.bed"/utf8>>; + + <<"bh2"/utf8>> -> + <<"application/vnd.fujitsu.oasysprs"/utf8>>; + + <<"bin"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"blb"/utf8>> -> + <<"application/x-blorb"/utf8>>; + + <<"blorb"/utf8>> -> + <<"application/x-blorb"/utf8>>; + + <<"bmi"/utf8>> -> + <<"application/vnd.bmi"/utf8>>; + + <<"bmp"/utf8>> -> + <<"image/bmp"/utf8>>; + + <<"book"/utf8>> -> + <<"application/vnd.framemaker"/utf8>>; + + <<"box"/utf8>> -> + <<"application/vnd.previewsystems.box"/utf8>>; + + <<"boz"/utf8>> -> + <<"application/x-bzip2"/utf8>>; + + <<"bpk"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"btif"/utf8>> -> + <<"image/prs.btif"/utf8>>; + + <<"bz"/utf8>> -> + <<"application/x-bzip"/utf8>>; + + <<"bz2"/utf8>> -> + <<"application/x-bzip2"/utf8>>; + + <<"c"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"c11amc"/utf8>> -> + <<"application/vnd.cluetrust.cartomobile-config"/utf8>>; + + <<"c11amz"/utf8>> -> + <<"application/vnd.cluetrust.cartomobile-config-pkg"/utf8>>; + + <<"c4d"/utf8>> -> + <<"application/vnd.clonk.c4group"/utf8>>; + + <<"c4f"/utf8>> -> + <<"application/vnd.clonk.c4group"/utf8>>; + + <<"c4g"/utf8>> -> + <<"application/vnd.clonk.c4group"/utf8>>; + + <<"c4p"/utf8>> -> + <<"application/vnd.clonk.c4group"/utf8>>; + + <<"c4u"/utf8>> -> + <<"application/vnd.clonk.c4group"/utf8>>; + + <<"cab"/utf8>> -> + <<"application/vnd.ms-cab-compressed"/utf8>>; + + <<"caf"/utf8>> -> + <<"audio/x-caf"/utf8>>; + + <<"cap"/utf8>> -> + <<"application/vnd.tcpdump.pcap"/utf8>>; + + <<"car"/utf8>> -> + <<"application/vnd.curl.car"/utf8>>; + + <<"cat"/utf8>> -> + <<"application/vnd.ms-pki.seccat"/utf8>>; + + <<"cb7"/utf8>> -> + <<"application/x-cbr"/utf8>>; + + <<"cba"/utf8>> -> + <<"application/x-cbr"/utf8>>; + + <<"cbr"/utf8>> -> + <<"application/x-cbr"/utf8>>; + + <<"cbt"/utf8>> -> + <<"application/x-cbr"/utf8>>; + + <<"cbz"/utf8>> -> + <<"application/x-cbr"/utf8>>; + + <<"cc"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"cct"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"ccxml"/utf8>> -> + <<"application/ccxml+xml"/utf8>>; + + <<"cdbcmsg"/utf8>> -> + <<"application/vnd.contact.cmsg"/utf8>>; + + <<"cdf"/utf8>> -> + <<"application/x-netcdf"/utf8>>; + + <<"cdkey"/utf8>> -> + <<"application/vnd.mediastation.cdkey"/utf8>>; + + <<"cdmia"/utf8>> -> + <<"application/cdmi-capability"/utf8>>; + + <<"cdmic"/utf8>> -> + <<"application/cdmi-container"/utf8>>; + + <<"cdmid"/utf8>> -> + <<"application/cdmi-domain"/utf8>>; + + <<"cdmio"/utf8>> -> + <<"application/cdmi-object"/utf8>>; + + <<"cdmiq"/utf8>> -> + <<"application/cdmi-queue"/utf8>>; + + <<"cdx"/utf8>> -> + <<"chemical/x-cdx"/utf8>>; + + <<"cdxml"/utf8>> -> + <<"application/vnd.chemdraw+xml"/utf8>>; + + <<"cdy"/utf8>> -> + <<"application/vnd.cinderella"/utf8>>; + + <<"cer"/utf8>> -> + <<"application/pkix-cert"/utf8>>; + + <<"cfs"/utf8>> -> + <<"application/x-cfs-compressed"/utf8>>; + + <<"cgm"/utf8>> -> + <<"image/cgm"/utf8>>; + + <<"chat"/utf8>> -> + <<"application/x-chat"/utf8>>; + + <<"chm"/utf8>> -> + <<"application/vnd.ms-htmlhelp"/utf8>>; + + <<"chrt"/utf8>> -> + <<"application/vnd.kde.kchart"/utf8>>; + + <<"cif"/utf8>> -> + <<"chemical/x-cif"/utf8>>; + + <<"cii"/utf8>> -> + <<"application/vnd.anser-web-certificate-issue-initiation"/utf8>>; + + <<"cil"/utf8>> -> + <<"application/vnd.ms-artgalry"/utf8>>; + + <<"cla"/utf8>> -> + <<"application/vnd.claymore"/utf8>>; + + <<"class"/utf8>> -> + <<"application/java-vm"/utf8>>; + + <<"clkk"/utf8>> -> + <<"application/vnd.crick.clicker.keyboard"/utf8>>; + + <<"clkp"/utf8>> -> + <<"application/vnd.crick.clicker.palette"/utf8>>; + + <<"clkt"/utf8>> -> + <<"application/vnd.crick.clicker.template"/utf8>>; + + <<"clkw"/utf8>> -> + <<"application/vnd.crick.clicker.wordbank"/utf8>>; + + <<"clkx"/utf8>> -> + <<"application/vnd.crick.clicker"/utf8>>; + + <<"clp"/utf8>> -> + <<"application/x-msclip"/utf8>>; + + <<"cmc"/utf8>> -> + <<"application/vnd.cosmocaller"/utf8>>; + + <<"cmdf"/utf8>> -> + <<"chemical/x-cmdf"/utf8>>; + + <<"cml"/utf8>> -> + <<"chemical/x-cml"/utf8>>; + + <<"cmp"/utf8>> -> + <<"application/vnd.yellowriver-custom-menu"/utf8>>; + + <<"cmx"/utf8>> -> + <<"image/x-cmx"/utf8>>; + + <<"cod"/utf8>> -> + <<"application/vnd.rim.cod"/utf8>>; + + <<"com"/utf8>> -> + <<"application/x-msdownload"/utf8>>; + + <<"conf"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"cpio"/utf8>> -> + <<"application/x-cpio"/utf8>>; + + <<"cpp"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"cpt"/utf8>> -> + <<"application/mac-compactpro"/utf8>>; + + <<"crd"/utf8>> -> + <<"application/x-mscardfile"/utf8>>; + + <<"crl"/utf8>> -> + <<"application/pkix-crl"/utf8>>; + + <<"crt"/utf8>> -> + <<"application/x-x509-ca-cert"/utf8>>; + + <<"cryptonote"/utf8>> -> + <<"application/vnd.rig.cryptonote"/utf8>>; + + <<"csh"/utf8>> -> + <<"application/x-csh"/utf8>>; + + <<"csml"/utf8>> -> + <<"chemical/x-csml"/utf8>>; + + <<"csp"/utf8>> -> + <<"application/vnd.commonspace"/utf8>>; + + <<"css"/utf8>> -> + <<"text/css"/utf8>>; + + <<"cst"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"csv"/utf8>> -> + <<"text/csv"/utf8>>; + + <<"cu"/utf8>> -> + <<"application/cu-seeme"/utf8>>; + + <<"curl"/utf8>> -> + <<"text/vnd.curl"/utf8>>; + + <<"cww"/utf8>> -> + <<"application/prs.cww"/utf8>>; + + <<"cxt"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"cxx"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"dae"/utf8>> -> + <<"model/vnd.collada+xml"/utf8>>; + + <<"daf"/utf8>> -> + <<"application/vnd.mobius.daf"/utf8>>; + + <<"dart"/utf8>> -> + <<"application/vnd.dart"/utf8>>; + + <<"dataless"/utf8>> -> + <<"application/vnd.fdsn.seed"/utf8>>; + + <<"davmount"/utf8>> -> + <<"application/davmount+xml"/utf8>>; + + <<"dbk"/utf8>> -> + <<"application/docbook+xml"/utf8>>; + + <<"dcr"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"dcurl"/utf8>> -> + <<"text/vnd.curl.dcurl"/utf8>>; + + <<"dd2"/utf8>> -> + <<"application/vnd.oma.dd2+xml"/utf8>>; + + <<"ddd"/utf8>> -> + <<"application/vnd.fujixerox.ddd"/utf8>>; + + <<"deb"/utf8>> -> + <<"application/x-debian-package"/utf8>>; + + <<"def"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"deploy"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"der"/utf8>> -> + <<"application/x-x509-ca-cert"/utf8>>; + + <<"dfac"/utf8>> -> + <<"application/vnd.dreamfactory"/utf8>>; + + <<"dgc"/utf8>> -> + <<"application/x-dgc-compressed"/utf8>>; + + <<"dic"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"dir"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"dis"/utf8>> -> + <<"application/vnd.mobius.dis"/utf8>>; + + <<"dist"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"distz"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"djv"/utf8>> -> + <<"image/vnd.djvu"/utf8>>; + + <<"djvu"/utf8>> -> + <<"image/vnd.djvu"/utf8>>; + + <<"dll"/utf8>> -> + <<"application/x-msdownload"/utf8>>; + + <<"dmg"/utf8>> -> + <<"application/x-apple-diskimage"/utf8>>; + + <<"dmp"/utf8>> -> + <<"application/vnd.tcpdump.pcap"/utf8>>; + + <<"dms"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"dna"/utf8>> -> + <<"application/vnd.dna"/utf8>>; + + <<"doc"/utf8>> -> + <<"application/msword"/utf8>>; + + <<"docm"/utf8>> -> + <<"application/vnd.ms-word.document.macroenabled.12"/utf8>>; + + <<"docx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.wordprocessingml.document"/utf8>>; + + <<"dot"/utf8>> -> + <<"application/msword"/utf8>>; + + <<"dotm"/utf8>> -> + <<"application/vnd.ms-word.template.macroenabled.12"/utf8>>; + + <<"dotx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.wordprocessingml.template"/utf8>>; + + <<"dp"/utf8>> -> + <<"application/vnd.osgi.dp"/utf8>>; + + <<"dpg"/utf8>> -> + <<"application/vnd.dpgraph"/utf8>>; + + <<"dra"/utf8>> -> + <<"audio/vnd.dra"/utf8>>; + + <<"dsc"/utf8>> -> + <<"text/prs.lines.tag"/utf8>>; + + <<"dssc"/utf8>> -> + <<"application/dssc+der"/utf8>>; + + <<"dtb"/utf8>> -> + <<"application/x-dtbook+xml"/utf8>>; + + <<"dtd"/utf8>> -> + <<"application/xml-dtd"/utf8>>; + + <<"dts"/utf8>> -> + <<"audio/vnd.dts"/utf8>>; + + <<"dtshd"/utf8>> -> + <<"audio/vnd.dts.hd"/utf8>>; + + <<"dump"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"dvb"/utf8>> -> + <<"video/vnd.dvb.file"/utf8>>; + + <<"dvi"/utf8>> -> + <<"application/x-dvi"/utf8>>; + + <<"dwf"/utf8>> -> + <<"model/vnd.dwf"/utf8>>; + + <<"dwg"/utf8>> -> + <<"image/vnd.dwg"/utf8>>; + + <<"dxf"/utf8>> -> + <<"image/vnd.dxf"/utf8>>; + + <<"dxp"/utf8>> -> + <<"application/vnd.spotfire.dxp"/utf8>>; + + <<"dxr"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"ecelp4800"/utf8>> -> + <<"audio/vnd.nuera.ecelp4800"/utf8>>; + + <<"ecelp7470"/utf8>> -> + <<"audio/vnd.nuera.ecelp7470"/utf8>>; + + <<"ecelp9600"/utf8>> -> + <<"audio/vnd.nuera.ecelp9600"/utf8>>; + + <<"ecma"/utf8>> -> + <<"application/ecmascript"/utf8>>; + + <<"edm"/utf8>> -> + <<"application/vnd.novadigm.edm"/utf8>>; + + <<"edx"/utf8>> -> + <<"application/vnd.novadigm.edx"/utf8>>; + + <<"efif"/utf8>> -> + <<"application/vnd.picsel"/utf8>>; + + <<"ei6"/utf8>> -> + <<"application/vnd.pg.osasli"/utf8>>; + + <<"elc"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"emf"/utf8>> -> + <<"application/x-msmetafile"/utf8>>; + + <<"eml"/utf8>> -> + <<"message/rfc822"/utf8>>; + + <<"emma"/utf8>> -> + <<"application/emma+xml"/utf8>>; + + <<"emz"/utf8>> -> + <<"application/x-msmetafile"/utf8>>; + + <<"eol"/utf8>> -> + <<"audio/vnd.digital-winds"/utf8>>; + + <<"eot"/utf8>> -> + <<"application/vnd.ms-fontobject"/utf8>>; + + <<"eps"/utf8>> -> + <<"application/postscript"/utf8>>; + + <<"epub"/utf8>> -> + <<"application/epub+zip"/utf8>>; + + <<"es3"/utf8>> -> + <<"application/vnd.eszigno3+xml"/utf8>>; + + <<"esa"/utf8>> -> + <<"application/vnd.osgi.subsystem"/utf8>>; + + <<"esf"/utf8>> -> + <<"application/vnd.epson.esf"/utf8>>; + + <<"et3"/utf8>> -> + <<"application/vnd.eszigno3+xml"/utf8>>; + + <<"etx"/utf8>> -> + <<"text/x-setext"/utf8>>; + + <<"eva"/utf8>> -> + <<"application/x-eva"/utf8>>; + + <<"evy"/utf8>> -> + <<"application/x-envoy"/utf8>>; + + <<"exe"/utf8>> -> + <<"application/x-msdownload"/utf8>>; + + <<"exi"/utf8>> -> + <<"application/exi"/utf8>>; + + <<"ext"/utf8>> -> + <<"application/vnd.novadigm.ext"/utf8>>; + + <<"ez"/utf8>> -> + <<"application/andrew-inset"/utf8>>; + + <<"ez2"/utf8>> -> + <<"application/vnd.ezpix-album"/utf8>>; + + <<"ez3"/utf8>> -> + <<"application/vnd.ezpix-package"/utf8>>; + + <<"f"/utf8>> -> + <<"text/x-fortran"/utf8>>; + + <<"f4v"/utf8>> -> + <<"video/x-f4v"/utf8>>; + + <<"f77"/utf8>> -> + <<"text/x-fortran"/utf8>>; + + <<"f90"/utf8>> -> + <<"text/x-fortran"/utf8>>; + + <<"fbs"/utf8>> -> + <<"image/vnd.fastbidsheet"/utf8>>; + + <<"fcdt"/utf8>> -> + <<"application/vnd.adobe.formscentral.fcdt"/utf8>>; + + <<"fcs"/utf8>> -> + <<"application/vnd.isac.fcs"/utf8>>; + + <<"fdf"/utf8>> -> + <<"application/vnd.fdf"/utf8>>; + + <<"fe_launch"/utf8>> -> + <<"application/vnd.denovo.fcselayout-link"/utf8>>; + + <<"fg5"/utf8>> -> + <<"application/vnd.fujitsu.oasysgp"/utf8>>; + + <<"fgd"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"fh"/utf8>> -> + <<"image/x-freehand"/utf8>>; + + <<"fh4"/utf8>> -> + <<"image/x-freehand"/utf8>>; + + <<"fh5"/utf8>> -> + <<"image/x-freehand"/utf8>>; + + <<"fh7"/utf8>> -> + <<"image/x-freehand"/utf8>>; + + <<"fhc"/utf8>> -> + <<"image/x-freehand"/utf8>>; + + <<"fig"/utf8>> -> + <<"application/x-xfig"/utf8>>; + + <<"flac"/utf8>> -> + <<"audio/x-flac"/utf8>>; + + <<"fli"/utf8>> -> + <<"video/x-fli"/utf8>>; + + <<"flo"/utf8>> -> + <<"application/vnd.micrografx.flo"/utf8>>; + + <<"flv"/utf8>> -> + <<"video/x-flv"/utf8>>; + + <<"flw"/utf8>> -> + <<"application/vnd.kde.kivio"/utf8>>; + + <<"flx"/utf8>> -> + <<"text/vnd.fmi.flexstor"/utf8>>; + + <<"fly"/utf8>> -> + <<"text/vnd.fly"/utf8>>; + + <<"fm"/utf8>> -> + <<"application/vnd.framemaker"/utf8>>; + + <<"fnc"/utf8>> -> + <<"application/vnd.frogans.fnc"/utf8>>; + + <<"for"/utf8>> -> + <<"text/x-fortran"/utf8>>; + + <<"fpx"/utf8>> -> + <<"image/vnd.fpx"/utf8>>; + + <<"frame"/utf8>> -> + <<"application/vnd.framemaker"/utf8>>; + + <<"fsc"/utf8>> -> + <<"application/vnd.fsc.weblaunch"/utf8>>; + + <<"fst"/utf8>> -> + <<"image/vnd.fst"/utf8>>; + + <<"ftc"/utf8>> -> + <<"application/vnd.fluxtime.clip"/utf8>>; + + <<"fti"/utf8>> -> + <<"application/vnd.anser-web-funds-transfer-initiation"/utf8>>; + + <<"fvt"/utf8>> -> + <<"video/vnd.fvt"/utf8>>; + + <<"fxp"/utf8>> -> + <<"application/vnd.adobe.fxp"/utf8>>; + + <<"fxpl"/utf8>> -> + <<"application/vnd.adobe.fxp"/utf8>>; + + <<"fzs"/utf8>> -> + <<"application/vnd.fuzzysheet"/utf8>>; + + <<"g2w"/utf8>> -> + <<"application/vnd.geoplan"/utf8>>; + + <<"g3"/utf8>> -> + <<"image/g3fax"/utf8>>; + + <<"g3w"/utf8>> -> + <<"application/vnd.geospace"/utf8>>; + + <<"gac"/utf8>> -> + <<"application/vnd.groove-account"/utf8>>; + + <<"gam"/utf8>> -> + <<"application/x-tads"/utf8>>; + + <<"gbr"/utf8>> -> + <<"application/rpki-ghostbusters"/utf8>>; + + <<"gca"/utf8>> -> + <<"application/x-gca-compressed"/utf8>>; + + <<"gdl"/utf8>> -> + <<"model/vnd.gdl"/utf8>>; + + <<"geo"/utf8>> -> + <<"application/vnd.dynageo"/utf8>>; + + <<"gex"/utf8>> -> + <<"application/vnd.geometry-explorer"/utf8>>; + + <<"ggb"/utf8>> -> + <<"application/vnd.geogebra.file"/utf8>>; + + <<"ggs"/utf8>> -> + <<"application/vnd.geogebra.slides"/utf8>>; + + <<"ggt"/utf8>> -> + <<"application/vnd.geogebra.tool"/utf8>>; + + <<"ghf"/utf8>> -> + <<"application/vnd.groove-help"/utf8>>; + + <<"gif"/utf8>> -> + <<"image/gif"/utf8>>; + + <<"gim"/utf8>> -> + <<"application/vnd.groove-identity-message"/utf8>>; + + <<"gml"/utf8>> -> + <<"application/gml+xml"/utf8>>; + + <<"gmx"/utf8>> -> + <<"application/vnd.gmx"/utf8>>; + + <<"gnumeric"/utf8>> -> + <<"application/x-gnumeric"/utf8>>; + + <<"gph"/utf8>> -> + <<"application/vnd.flographit"/utf8>>; + + <<"gpx"/utf8>> -> + <<"application/gpx+xml"/utf8>>; + + <<"gqf"/utf8>> -> + <<"application/vnd.grafeq"/utf8>>; + + <<"gqs"/utf8>> -> + <<"application/vnd.grafeq"/utf8>>; + + <<"gram"/utf8>> -> + <<"application/srgs"/utf8>>; + + <<"gramps"/utf8>> -> + <<"application/x-gramps-xml"/utf8>>; + + <<"gre"/utf8>> -> + <<"application/vnd.geometry-explorer"/utf8>>; + + <<"grv"/utf8>> -> + <<"application/vnd.groove-injector"/utf8>>; + + <<"grxml"/utf8>> -> + <<"application/srgs+xml"/utf8>>; + + <<"gsf"/utf8>> -> + <<"application/x-font-ghostscript"/utf8>>; + + <<"gtar"/utf8>> -> + <<"application/x-gtar"/utf8>>; + + <<"gtm"/utf8>> -> + <<"application/vnd.groove-tool-message"/utf8>>; + + <<"gtw"/utf8>> -> + <<"model/vnd.gtw"/utf8>>; + + <<"gv"/utf8>> -> + <<"text/vnd.graphviz"/utf8>>; + + <<"gxf"/utf8>> -> + <<"application/gxf"/utf8>>; + + <<"gxt"/utf8>> -> + <<"application/vnd.geonext"/utf8>>; + + <<"h"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"h261"/utf8>> -> + <<"video/h261"/utf8>>; + + <<"h263"/utf8>> -> + <<"video/h263"/utf8>>; + + <<"h264"/utf8>> -> + <<"video/h264"/utf8>>; + + <<"hal"/utf8>> -> + <<"application/vnd.hal+xml"/utf8>>; + + <<"hbci"/utf8>> -> + <<"application/vnd.hbci"/utf8>>; + + <<"hdf"/utf8>> -> + <<"application/x-hdf"/utf8>>; + + <<"hh"/utf8>> -> + <<"text/x-c"/utf8>>; + + <<"hlp"/utf8>> -> + <<"application/winhlp"/utf8>>; + + <<"hpgl"/utf8>> -> + <<"application/vnd.hp-hpgl"/utf8>>; + + <<"hpid"/utf8>> -> + <<"application/vnd.hp-hpid"/utf8>>; + + <<"hps"/utf8>> -> + <<"application/vnd.hp-hps"/utf8>>; + + <<"hqx"/utf8>> -> + <<"application/mac-binhex40"/utf8>>; + + <<"htke"/utf8>> -> + <<"application/vnd.kenameaapp"/utf8>>; + + <<"htm"/utf8>> -> + <<"text/html"/utf8>>; + + <<"html"/utf8>> -> + <<"text/html"/utf8>>; + + <<"hvd"/utf8>> -> + <<"application/vnd.yamaha.hv-dic"/utf8>>; + + <<"hvp"/utf8>> -> + <<"application/vnd.yamaha.hv-voice"/utf8>>; + + <<"hvs"/utf8>> -> + <<"application/vnd.yamaha.hv-script"/utf8>>; + + <<"i2g"/utf8>> -> + <<"application/vnd.intergeo"/utf8>>; + + <<"icc"/utf8>> -> + <<"application/vnd.iccprofile"/utf8>>; + + <<"ice"/utf8>> -> + <<"x-conference/x-cooltalk"/utf8>>; + + <<"icm"/utf8>> -> + <<"application/vnd.iccprofile"/utf8>>; + + <<"ico"/utf8>> -> + <<"image/x-icon"/utf8>>; + + <<"ics"/utf8>> -> + <<"text/calendar"/utf8>>; + + <<"ief"/utf8>> -> + <<"image/ief"/utf8>>; + + <<"ifb"/utf8>> -> + <<"text/calendar"/utf8>>; + + <<"ifm"/utf8>> -> + <<"application/vnd.shana.informed.formdata"/utf8>>; + + <<"iges"/utf8>> -> + <<"model/iges"/utf8>>; + + <<"igl"/utf8>> -> + <<"application/vnd.igloader"/utf8>>; + + <<"igm"/utf8>> -> + <<"application/vnd.insors.igm"/utf8>>; + + <<"igs"/utf8>> -> + <<"model/iges"/utf8>>; + + <<"igx"/utf8>> -> + <<"application/vnd.micrografx.igx"/utf8>>; + + <<"iif"/utf8>> -> + <<"application/vnd.shana.informed.interchange"/utf8>>; + + <<"imp"/utf8>> -> + <<"application/vnd.accpac.simply.imp"/utf8>>; + + <<"ims"/utf8>> -> + <<"application/vnd.ms-ims"/utf8>>; + + <<"in"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"ink"/utf8>> -> + <<"application/inkml+xml"/utf8>>; + + <<"inkml"/utf8>> -> + <<"application/inkml+xml"/utf8>>; + + <<"install"/utf8>> -> + <<"application/x-install-instructions"/utf8>>; + + <<"iota"/utf8>> -> + <<"application/vnd.astraea-software.iota"/utf8>>; + + <<"ipfix"/utf8>> -> + <<"application/ipfix"/utf8>>; + + <<"ipk"/utf8>> -> + <<"application/vnd.shana.informed.package"/utf8>>; + + <<"irm"/utf8>> -> + <<"application/vnd.ibm.rights-management"/utf8>>; + + <<"irp"/utf8>> -> + <<"application/vnd.irepository.package+xml"/utf8>>; + + <<"iso"/utf8>> -> + <<"application/x-iso9660-image"/utf8>>; + + <<"itp"/utf8>> -> + <<"application/vnd.shana.informed.formtemplate"/utf8>>; + + <<"ivp"/utf8>> -> + <<"application/vnd.immervision-ivp"/utf8>>; + + <<"ivu"/utf8>> -> + <<"application/vnd.immervision-ivu"/utf8>>; + + <<"jad"/utf8>> -> + <<"text/vnd.sun.j2me.app-descriptor"/utf8>>; + + <<"jam"/utf8>> -> + <<"application/vnd.jam"/utf8>>; + + <<"jar"/utf8>> -> + <<"application/java-archive"/utf8>>; + + <<"java"/utf8>> -> + <<"text/x-java-source"/utf8>>; + + <<"jisp"/utf8>> -> + <<"application/vnd.jisp"/utf8>>; + + <<"jlt"/utf8>> -> + <<"application/vnd.hp-jlyt"/utf8>>; + + <<"jnlp"/utf8>> -> + <<"application/x-java-jnlp-file"/utf8>>; + + <<"joda"/utf8>> -> + <<"application/vnd.joost.joda-archive"/utf8>>; + + <<"jpe"/utf8>> -> + <<"image/jpeg"/utf8>>; + + <<"jpeg"/utf8>> -> + <<"image/jpeg"/utf8>>; + + <<"jpg"/utf8>> -> + <<"image/jpeg"/utf8>>; + + <<"jpgm"/utf8>> -> + <<"video/jpm"/utf8>>; + + <<"jpgv"/utf8>> -> + <<"video/jpeg"/utf8>>; + + <<"jpm"/utf8>> -> + <<"video/jpm"/utf8>>; + + <<"js"/utf8>> -> + <<"text/javascript"/utf8>>; + + <<"json"/utf8>> -> + <<"application/json"/utf8>>; + + <<"jsonml"/utf8>> -> + <<"application/jsonml+json"/utf8>>; + + <<"jxl"/utf8>> -> + <<"image/jxl"/utf8>>; + + <<"kar"/utf8>> -> + <<"audio/midi"/utf8>>; + + <<"karbon"/utf8>> -> + <<"application/vnd.kde.karbon"/utf8>>; + + <<"kfo"/utf8>> -> + <<"application/vnd.kde.kformula"/utf8>>; + + <<"kia"/utf8>> -> + <<"application/vnd.kidspiration"/utf8>>; + + <<"kml"/utf8>> -> + <<"application/vnd.google-earth.kml+xml"/utf8>>; + + <<"kmz"/utf8>> -> + <<"application/vnd.google-earth.kmz"/utf8>>; + + <<"kne"/utf8>> -> + <<"application/vnd.kinar"/utf8>>; + + <<"knp"/utf8>> -> + <<"application/vnd.kinar"/utf8>>; + + <<"kon"/utf8>> -> + <<"application/vnd.kde.kontour"/utf8>>; + + <<"kpr"/utf8>> -> + <<"application/vnd.kde.kpresenter"/utf8>>; + + <<"kpt"/utf8>> -> + <<"application/vnd.kde.kpresenter"/utf8>>; + + <<"kpxx"/utf8>> -> + <<"application/vnd.ds-keypoint"/utf8>>; + + <<"ksp"/utf8>> -> + <<"application/vnd.kde.kspread"/utf8>>; + + <<"ktr"/utf8>> -> + <<"application/vnd.kahootz"/utf8>>; + + <<"ktx"/utf8>> -> + <<"image/ktx"/utf8>>; + + <<"ktz"/utf8>> -> + <<"application/vnd.kahootz"/utf8>>; + + <<"kwd"/utf8>> -> + <<"application/vnd.kde.kword"/utf8>>; + + <<"kwt"/utf8>> -> + <<"application/vnd.kde.kword"/utf8>>; + + <<"lasxml"/utf8>> -> + <<"application/vnd.las.las+xml"/utf8>>; + + <<"latex"/utf8>> -> + <<"application/x-latex"/utf8>>; + + <<"lbd"/utf8>> -> + <<"application/vnd.llamagraphics.life-balance.desktop"/utf8>>; + + <<"lbe"/utf8>> -> + <<"application/vnd.llamagraphics.life-balance.exchange+xml"/utf8>>; + + <<"les"/utf8>> -> + <<"application/vnd.hhe.lesson-player"/utf8>>; + + <<"lha"/utf8>> -> + <<"application/x-lzh-compressed"/utf8>>; + + <<"link66"/utf8>> -> + <<"application/vnd.route66.link66+xml"/utf8>>; + + <<"list"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"list3820"/utf8>> -> + <<"application/vnd.ibm.modcap"/utf8>>; + + <<"listafp"/utf8>> -> + <<"application/vnd.ibm.modcap"/utf8>>; + + <<"lnk"/utf8>> -> + <<"application/x-ms-shortcut"/utf8>>; + + <<"log"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"lostxml"/utf8>> -> + <<"application/lost+xml"/utf8>>; + + <<"lrf"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"lrm"/utf8>> -> + <<"application/vnd.ms-lrm"/utf8>>; + + <<"ltf"/utf8>> -> + <<"application/vnd.frogans.ltf"/utf8>>; + + <<"lvp"/utf8>> -> + <<"audio/vnd.lucent.voice"/utf8>>; + + <<"lwp"/utf8>> -> + <<"application/vnd.lotus-wordpro"/utf8>>; + + <<"lzh"/utf8>> -> + <<"application/x-lzh-compressed"/utf8>>; + + <<"m13"/utf8>> -> + <<"application/x-msmediaview"/utf8>>; + + <<"m14"/utf8>> -> + <<"application/x-msmediaview"/utf8>>; + + <<"m1v"/utf8>> -> + <<"video/mpeg"/utf8>>; + + <<"m21"/utf8>> -> + <<"application/mp21"/utf8>>; + + <<"m2a"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"m2t"/utf8>> -> + <<"video/mp2t"/utf8>>; + + <<"m2ts"/utf8>> -> + <<"video/mp2t"/utf8>>; + + <<"m2v"/utf8>> -> + <<"video/mpeg"/utf8>>; + + <<"m3a"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"m3u"/utf8>> -> + <<"audio/x-mpegurl"/utf8>>; + + <<"m3u8"/utf8>> -> + <<"application/vnd.apple.mpegurl"/utf8>>; + + <<"m4a"/utf8>> -> + <<"audio/mp4"/utf8>>; + + <<"m4u"/utf8>> -> + <<"video/vnd.mpegurl"/utf8>>; + + <<"m4v"/utf8>> -> + <<"video/x-m4v"/utf8>>; + + <<"ma"/utf8>> -> + <<"application/mathematica"/utf8>>; + + <<"mads"/utf8>> -> + <<"application/mads+xml"/utf8>>; + + <<"mag"/utf8>> -> + <<"application/vnd.ecowin.chart"/utf8>>; + + <<"maker"/utf8>> -> + <<"application/vnd.framemaker"/utf8>>; + + <<"man"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"mar"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"mathml"/utf8>> -> + <<"application/mathml+xml"/utf8>>; + + <<"mb"/utf8>> -> + <<"application/mathematica"/utf8>>; + + <<"mbk"/utf8>> -> + <<"application/vnd.mobius.mbk"/utf8>>; + + <<"mbox"/utf8>> -> + <<"application/mbox"/utf8>>; + + <<"mc1"/utf8>> -> + <<"application/vnd.medcalcdata"/utf8>>; + + <<"mcd"/utf8>> -> + <<"application/vnd.mcd"/utf8>>; + + <<"mcurl"/utf8>> -> + <<"text/vnd.curl.mcurl"/utf8>>; + + <<"mdb"/utf8>> -> + <<"application/x-msaccess"/utf8>>; + + <<"mdi"/utf8>> -> + <<"image/vnd.ms-modi"/utf8>>; + + <<"me"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"mesh"/utf8>> -> + <<"model/mesh"/utf8>>; + + <<"meta4"/utf8>> -> + <<"application/metalink4+xml"/utf8>>; + + <<"metalink"/utf8>> -> + <<"application/metalink+xml"/utf8>>; + + <<"mets"/utf8>> -> + <<"application/mets+xml"/utf8>>; + + <<"mfm"/utf8>> -> + <<"application/vnd.mfmp"/utf8>>; + + <<"mft"/utf8>> -> + <<"application/rpki-manifest"/utf8>>; + + <<"mgp"/utf8>> -> + <<"application/vnd.osgeo.mapguide.package"/utf8>>; + + <<"mgz"/utf8>> -> + <<"application/vnd.proteus.magazine"/utf8>>; + + <<"mid"/utf8>> -> + <<"audio/midi"/utf8>>; + + <<"midi"/utf8>> -> + <<"audio/midi"/utf8>>; + + <<"mie"/utf8>> -> + <<"application/x-mie"/utf8>>; + + <<"mif"/utf8>> -> + <<"application/vnd.mif"/utf8>>; + + <<"mime"/utf8>> -> + <<"message/rfc822"/utf8>>; + + <<"mj2"/utf8>> -> + <<"video/mj2"/utf8>>; + + <<"mjp2"/utf8>> -> + <<"video/mj2"/utf8>>; + + <<"mjs"/utf8>> -> + <<"text/javascript"/utf8>>; + + <<"mk3d"/utf8>> -> + <<"video/x-matroska"/utf8>>; + + <<"mka"/utf8>> -> + <<"audio/x-matroska"/utf8>>; + + <<"mks"/utf8>> -> + <<"video/x-matroska"/utf8>>; + + <<"mkv"/utf8>> -> + <<"video/x-matroska"/utf8>>; + + <<"mlp"/utf8>> -> + <<"application/vnd.dolby.mlp"/utf8>>; + + <<"mmd"/utf8>> -> + <<"application/vnd.chipnuts.karaoke-mmd"/utf8>>; + + <<"mmf"/utf8>> -> + <<"application/vnd.smaf"/utf8>>; + + <<"mmr"/utf8>> -> + <<"image/vnd.fujixerox.edmics-mmr"/utf8>>; + + <<"mng"/utf8>> -> + <<"video/x-mng"/utf8>>; + + <<"mny"/utf8>> -> + <<"application/x-msmoney"/utf8>>; + + <<"mobi"/utf8>> -> + <<"application/x-mobipocket-ebook"/utf8>>; + + <<"mods"/utf8>> -> + <<"application/mods+xml"/utf8>>; + + <<"mov"/utf8>> -> + <<"video/quicktime"/utf8>>; + + <<"movie"/utf8>> -> + <<"video/x-sgi-movie"/utf8>>; + + <<"mp2"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"mp21"/utf8>> -> + <<"application/mp21"/utf8>>; + + <<"mp2a"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"mp3"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"mp4"/utf8>> -> + <<"video/mp4"/utf8>>; + + <<"mp4a"/utf8>> -> + <<"audio/mp4"/utf8>>; + + <<"mp4s"/utf8>> -> + <<"application/mp4"/utf8>>; + + <<"mp4v"/utf8>> -> + <<"video/mp4"/utf8>>; + + <<"mpc"/utf8>> -> + <<"application/vnd.mophun.certificate"/utf8>>; + + <<"mpe"/utf8>> -> + <<"video/mpeg"/utf8>>; + + <<"mpeg"/utf8>> -> + <<"video/mpeg"/utf8>>; + + <<"mpg"/utf8>> -> + <<"video/mpeg"/utf8>>; + + <<"mpg4"/utf8>> -> + <<"video/mp4"/utf8>>; + + <<"mpga"/utf8>> -> + <<"audio/mpeg"/utf8>>; + + <<"mpkg"/utf8>> -> + <<"application/vnd.apple.installer+xml"/utf8>>; + + <<"mpm"/utf8>> -> + <<"application/vnd.blueice.multipass"/utf8>>; + + <<"mpn"/utf8>> -> + <<"application/vnd.mophun.application"/utf8>>; + + <<"mpp"/utf8>> -> + <<"application/vnd.ms-project"/utf8>>; + + <<"mpt"/utf8>> -> + <<"application/vnd.ms-project"/utf8>>; + + <<"mpy"/utf8>> -> + <<"application/vnd.ibm.minipay"/utf8>>; + + <<"mqy"/utf8>> -> + <<"application/vnd.mobius.mqy"/utf8>>; + + <<"mrc"/utf8>> -> + <<"application/marc"/utf8>>; + + <<"mrcx"/utf8>> -> + <<"application/marcxml+xml"/utf8>>; + + <<"ms"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"mscml"/utf8>> -> + <<"application/mediaservercontrol+xml"/utf8>>; + + <<"mseed"/utf8>> -> + <<"application/vnd.fdsn.mseed"/utf8>>; + + <<"mseq"/utf8>> -> + <<"application/vnd.mseq"/utf8>>; + + <<"msf"/utf8>> -> + <<"application/vnd.epson.msf"/utf8>>; + + <<"msh"/utf8>> -> + <<"model/mesh"/utf8>>; + + <<"msi"/utf8>> -> + <<"application/x-msdownload"/utf8>>; + + <<"msl"/utf8>> -> + <<"application/vnd.mobius.msl"/utf8>>; + + <<"msty"/utf8>> -> + <<"application/vnd.muvee.style"/utf8>>; + + <<"mts"/utf8>> -> + <<"video/mp2t"/utf8>>; + + <<"mus"/utf8>> -> + <<"application/vnd.musician"/utf8>>; + + <<"musicxml"/utf8>> -> + <<"application/vnd.recordare.musicxml+xml"/utf8>>; + + <<"mvb"/utf8>> -> + <<"application/x-msmediaview"/utf8>>; + + <<"mwf"/utf8>> -> + <<"application/vnd.mfer"/utf8>>; + + <<"mxf"/utf8>> -> + <<"application/mxf"/utf8>>; + + <<"mxl"/utf8>> -> + <<"application/vnd.recordare.musicxml"/utf8>>; + + <<"mxml"/utf8>> -> + <<"application/xv+xml"/utf8>>; + + <<"mxs"/utf8>> -> + <<"application/vnd.triscape.mxs"/utf8>>; + + <<"mxu"/utf8>> -> + <<"video/vnd.mpegurl"/utf8>>; + + <<"n-gage"/utf8>> -> + <<"application/vnd.nokia.n-gage.symbian.install"/utf8>>; + + <<"n3"/utf8>> -> + <<"text/n3"/utf8>>; + + <<"nb"/utf8>> -> + <<"application/mathematica"/utf8>>; + + <<"nbp"/utf8>> -> + <<"application/vnd.wolfram.player"/utf8>>; + + <<"nc"/utf8>> -> + <<"application/x-netcdf"/utf8>>; + + <<"ncx"/utf8>> -> + <<"application/x-dtbncx+xml"/utf8>>; + + <<"nfo"/utf8>> -> + <<"text/x-nfo"/utf8>>; + + <<"ngdat"/utf8>> -> + <<"application/vnd.nokia.n-gage.data"/utf8>>; + + <<"nitf"/utf8>> -> + <<"application/vnd.nitf"/utf8>>; + + <<"nlu"/utf8>> -> + <<"application/vnd.neurolanguage.nlu"/utf8>>; + + <<"nml"/utf8>> -> + <<"application/vnd.enliven"/utf8>>; + + <<"nnd"/utf8>> -> + <<"application/vnd.noblenet-directory"/utf8>>; + + <<"nns"/utf8>> -> + <<"application/vnd.noblenet-sealer"/utf8>>; + + <<"nnw"/utf8>> -> + <<"application/vnd.noblenet-web"/utf8>>; + + <<"npx"/utf8>> -> + <<"image/vnd.net-fpx"/utf8>>; + + <<"nsc"/utf8>> -> + <<"application/x-conference"/utf8>>; + + <<"nsf"/utf8>> -> + <<"application/vnd.lotus-notes"/utf8>>; + + <<"ntf"/utf8>> -> + <<"application/vnd.nitf"/utf8>>; + + <<"nzb"/utf8>> -> + <<"application/x-nzb"/utf8>>; + + <<"oa2"/utf8>> -> + <<"application/vnd.fujitsu.oasys2"/utf8>>; + + <<"oa3"/utf8>> -> + <<"application/vnd.fujitsu.oasys3"/utf8>>; + + <<"oas"/utf8>> -> + <<"application/vnd.fujitsu.oasys"/utf8>>; + + <<"obd"/utf8>> -> + <<"application/x-msbinder"/utf8>>; + + <<"obj"/utf8>> -> + <<"application/x-tgif"/utf8>>; + + <<"oda"/utf8>> -> + <<"application/oda"/utf8>>; + + <<"odb"/utf8>> -> + <<"application/vnd.oasis.opendocument.database"/utf8>>; + + <<"odc"/utf8>> -> + <<"application/vnd.oasis.opendocument.chart"/utf8>>; + + <<"odf"/utf8>> -> + <<"application/vnd.oasis.opendocument.formula"/utf8>>; + + <<"odft"/utf8>> -> + <<"application/vnd.oasis.opendocument.formula-template"/utf8>>; + + <<"odg"/utf8>> -> + <<"application/vnd.oasis.opendocument.graphics"/utf8>>; + + <<"odi"/utf8>> -> + <<"application/vnd.oasis.opendocument.image"/utf8>>; + + <<"odm"/utf8>> -> + <<"application/vnd.oasis.opendocument.text-master"/utf8>>; + + <<"odp"/utf8>> -> + <<"application/vnd.oasis.opendocument.presentation"/utf8>>; + + <<"ods"/utf8>> -> + <<"application/vnd.oasis.opendocument.spreadsheet"/utf8>>; + + <<"odt"/utf8>> -> + <<"application/vnd.oasis.opendocument.text"/utf8>>; + + <<"oga"/utf8>> -> + <<"audio/ogg"/utf8>>; + + <<"ogg"/utf8>> -> + <<"audio/ogg"/utf8>>; + + <<"ogv"/utf8>> -> + <<"video/ogg"/utf8>>; + + <<"ogx"/utf8>> -> + <<"application/ogg"/utf8>>; + + <<"omdoc"/utf8>> -> + <<"application/omdoc+xml"/utf8>>; + + <<"onepkg"/utf8>> -> + <<"application/onenote"/utf8>>; + + <<"onetmp"/utf8>> -> + <<"application/onenote"/utf8>>; + + <<"onetoc"/utf8>> -> + <<"application/onenote"/utf8>>; + + <<"onetoc2"/utf8>> -> + <<"application/onenote"/utf8>>; + + <<"opf"/utf8>> -> + <<"application/oebps-package+xml"/utf8>>; + + <<"opml"/utf8>> -> + <<"text/x-opml"/utf8>>; + + <<"oprc"/utf8>> -> + <<"application/vnd.palm"/utf8>>; + + <<"opus"/utf8>> -> + <<"audio/ogg"/utf8>>; + + <<"org"/utf8>> -> + <<"application/vnd.lotus-organizer"/utf8>>; + + <<"osf"/utf8>> -> + <<"application/vnd.yamaha.openscoreformat"/utf8>>; + + <<"osfpvg"/utf8>> -> + <<"application/vnd.yamaha.openscoreformat.osfpvg+xml"/utf8>>; + + <<"otc"/utf8>> -> + <<"application/vnd.oasis.opendocument.chart-template"/utf8>>; + + <<"otf"/utf8>> -> + <<"font/otf"/utf8>>; + + <<"otg"/utf8>> -> + <<"application/vnd.oasis.opendocument.graphics-template"/utf8>>; + + <<"oth"/utf8>> -> + <<"application/vnd.oasis.opendocument.text-web"/utf8>>; + + <<"oti"/utf8>> -> + <<"application/vnd.oasis.opendocument.image-template"/utf8>>; + + <<"otp"/utf8>> -> + <<"application/vnd.oasis.opendocument.presentation-template"/utf8>>; + + <<"ots"/utf8>> -> + <<"application/vnd.oasis.opendocument.spreadsheet-template"/utf8>>; + + <<"ott"/utf8>> -> + <<"application/vnd.oasis.opendocument.text-template"/utf8>>; + + <<"oxps"/utf8>> -> + <<"application/oxps"/utf8>>; + + <<"oxt"/utf8>> -> + <<"application/vnd.openofficeorg.extension"/utf8>>; + + <<"p"/utf8>> -> + <<"text/x-pascal"/utf8>>; + + <<"p10"/utf8>> -> + <<"application/pkcs10"/utf8>>; + + <<"p12"/utf8>> -> + <<"application/x-pkcs12"/utf8>>; + + <<"p7b"/utf8>> -> + <<"application/x-pkcs7-certificates"/utf8>>; + + <<"p7c"/utf8>> -> + <<"application/pkcs7-mime"/utf8>>; + + <<"p7m"/utf8>> -> + <<"application/pkcs7-mime"/utf8>>; + + <<"p7r"/utf8>> -> + <<"application/x-pkcs7-certreqresp"/utf8>>; + + <<"p7s"/utf8>> -> + <<"application/pkcs7-signature"/utf8>>; + + <<"p8"/utf8>> -> + <<"application/pkcs8"/utf8>>; + + <<"pas"/utf8>> -> + <<"text/x-pascal"/utf8>>; + + <<"paw"/utf8>> -> + <<"application/vnd.pawaafile"/utf8>>; + + <<"pbd"/utf8>> -> + <<"application/vnd.powerbuilder6"/utf8>>; + + <<"pbm"/utf8>> -> + <<"image/x-portable-bitmap"/utf8>>; + + <<"pcap"/utf8>> -> + <<"application/vnd.tcpdump.pcap"/utf8>>; + + <<"pcf"/utf8>> -> + <<"application/x-font-pcf"/utf8>>; + + <<"pcl"/utf8>> -> + <<"application/vnd.hp-pcl"/utf8>>; + + <<"pclxl"/utf8>> -> + <<"application/vnd.hp-pclxl"/utf8>>; + + <<"pct"/utf8>> -> + <<"image/x-pict"/utf8>>; + + <<"pcurl"/utf8>> -> + <<"application/vnd.curl.pcurl"/utf8>>; + + <<"pcx"/utf8>> -> + <<"image/x-pcx"/utf8>>; + + <<"pdb"/utf8>> -> + <<"application/vnd.palm"/utf8>>; + + <<"pdf"/utf8>> -> + <<"application/pdf"/utf8>>; + + <<"pfa"/utf8>> -> + <<"application/x-font-type1"/utf8>>; + + <<"pfb"/utf8>> -> + <<"application/x-font-type1"/utf8>>; + + <<"pfm"/utf8>> -> + <<"application/x-font-type1"/utf8>>; + + <<"pfr"/utf8>> -> + <<"application/font-tdpfr"/utf8>>; + + <<"pfx"/utf8>> -> + <<"application/x-pkcs12"/utf8>>; + + <<"pgm"/utf8>> -> + <<"image/x-portable-graymap"/utf8>>; + + <<"pgn"/utf8>> -> + <<"application/x-chess-pgn"/utf8>>; + + <<"pgp"/utf8>> -> + <<"application/pgp-encrypted"/utf8>>; + + <<"pic"/utf8>> -> + <<"image/x-pict"/utf8>>; + + <<"pkg"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"pki"/utf8>> -> + <<"application/pkixcmp"/utf8>>; + + <<"pkipath"/utf8>> -> + <<"application/pkix-pkipath"/utf8>>; + + <<"plb"/utf8>> -> + <<"application/vnd.3gpp.pic-bw-large"/utf8>>; + + <<"plc"/utf8>> -> + <<"application/vnd.mobius.plc"/utf8>>; + + <<"plf"/utf8>> -> + <<"application/vnd.pocketlearn"/utf8>>; + + <<"pls"/utf8>> -> + <<"application/pls+xml"/utf8>>; + + <<"pml"/utf8>> -> + <<"application/vnd.ctc-posml"/utf8>>; + + <<"png"/utf8>> -> + <<"image/png"/utf8>>; + + <<"pnm"/utf8>> -> + <<"image/x-portable-anymap"/utf8>>; + + <<"portpkg"/utf8>> -> + <<"application/vnd.macports.portpkg"/utf8>>; + + <<"pot"/utf8>> -> + <<"application/vnd.ms-powerpoint"/utf8>>; + + <<"potm"/utf8>> -> + <<"application/vnd.ms-powerpoint.template.macroenabled.12"/utf8>>; + + <<"potx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.presentationml.template"/utf8>>; + + <<"ppam"/utf8>> -> + <<"application/vnd.ms-powerpoint.addin.macroenabled.12"/utf8>>; + + <<"ppd"/utf8>> -> + <<"application/vnd.cups-ppd"/utf8>>; + + <<"ppm"/utf8>> -> + <<"image/x-portable-pixmap"/utf8>>; + + <<"pps"/utf8>> -> + <<"application/vnd.ms-powerpoint"/utf8>>; + + <<"ppsm"/utf8>> -> + <<"application/vnd.ms-powerpoint.slideshow.macroenabled.12"/utf8>>; + + <<"ppsx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.presentationml.slideshow"/utf8>>; + + <<"ppt"/utf8>> -> + <<"application/vnd.ms-powerpoint"/utf8>>; + + <<"pptm"/utf8>> -> + <<"application/vnd.ms-powerpoint.presentation.macroenabled.12"/utf8>>; + + <<"pptx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.presentationml.presentation"/utf8>>; + + <<"pqa"/utf8>> -> + <<"application/vnd.palm"/utf8>>; + + <<"prc"/utf8>> -> + <<"application/x-mobipocket-ebook"/utf8>>; + + <<"pre"/utf8>> -> + <<"application/vnd.lotus-freelance"/utf8>>; + + <<"prf"/utf8>> -> + <<"application/pics-rules"/utf8>>; + + <<"ps"/utf8>> -> + <<"application/postscript"/utf8>>; + + <<"psb"/utf8>> -> + <<"application/vnd.3gpp.pic-bw-small"/utf8>>; + + <<"psd"/utf8>> -> + <<"image/vnd.adobe.photoshop"/utf8>>; + + <<"psf"/utf8>> -> + <<"application/x-font-linux-psf"/utf8>>; + + <<"pskcxml"/utf8>> -> + <<"application/pskc+xml"/utf8>>; + + <<"ptid"/utf8>> -> + <<"application/vnd.pvi.ptid1"/utf8>>; + + <<"pub"/utf8>> -> + <<"application/x-mspublisher"/utf8>>; + + <<"pvb"/utf8>> -> + <<"application/vnd.3gpp.pic-bw-var"/utf8>>; + + <<"pwn"/utf8>> -> + <<"application/vnd.3m.post-it-notes"/utf8>>; + + <<"pya"/utf8>> -> + <<"audio/vnd.ms-playready.media.pya"/utf8>>; + + <<"pyv"/utf8>> -> + <<"video/vnd.ms-playready.media.pyv"/utf8>>; + + <<"qam"/utf8>> -> + <<"application/vnd.epson.quickanime"/utf8>>; + + <<"qbo"/utf8>> -> + <<"application/vnd.intu.qbo"/utf8>>; + + <<"qfx"/utf8>> -> + <<"application/vnd.intu.qfx"/utf8>>; + + <<"qps"/utf8>> -> + <<"application/vnd.publishare-delta-tree"/utf8>>; + + <<"qt"/utf8>> -> + <<"video/quicktime"/utf8>>; + + <<"qwd"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"qwt"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"qxb"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"qxd"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"qxl"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"qxt"/utf8>> -> + <<"application/vnd.quark.quarkxpress"/utf8>>; + + <<"ra"/utf8>> -> + <<"audio/x-pn-realaudio"/utf8>>; + + <<"ram"/utf8>> -> + <<"audio/x-pn-realaudio"/utf8>>; + + <<"rar"/utf8>> -> + <<"application/x-rar-compressed"/utf8>>; + + <<"ras"/utf8>> -> + <<"image/x-cmu-raster"/utf8>>; + + <<"rcprofile"/utf8>> -> + <<"application/vnd.ipunplugged.rcprofile"/utf8>>; + + <<"rdf"/utf8>> -> + <<"application/rdf+xml"/utf8>>; + + <<"rdz"/utf8>> -> + <<"application/vnd.data-vision.rdz"/utf8>>; + + <<"rep"/utf8>> -> + <<"application/vnd.businessobjects"/utf8>>; + + <<"res"/utf8>> -> + <<"application/x-dtbresource+xml"/utf8>>; + + <<"rgb"/utf8>> -> + <<"image/x-rgb"/utf8>>; + + <<"rif"/utf8>> -> + <<"application/reginfo+xml"/utf8>>; + + <<"rip"/utf8>> -> + <<"audio/vnd.rip"/utf8>>; + + <<"ris"/utf8>> -> + <<"application/x-research-info-systems"/utf8>>; + + <<"rl"/utf8>> -> + <<"application/resource-lists+xml"/utf8>>; + + <<"rlc"/utf8>> -> + <<"image/vnd.fujixerox.edmics-rlc"/utf8>>; + + <<"rld"/utf8>> -> + <<"application/resource-lists-diff+xml"/utf8>>; + + <<"rm"/utf8>> -> + <<"application/vnd.rn-realmedia"/utf8>>; + + <<"rmi"/utf8>> -> + <<"audio/midi"/utf8>>; + + <<"rmp"/utf8>> -> + <<"audio/x-pn-realaudio-plugin"/utf8>>; + + <<"rms"/utf8>> -> + <<"application/vnd.jcp.javame.midlet-rms"/utf8>>; + + <<"rmvb"/utf8>> -> + <<"application/vnd.rn-realmedia-vbr"/utf8>>; + + <<"rnc"/utf8>> -> + <<"application/relax-ng-compact-syntax"/utf8>>; + + <<"roa"/utf8>> -> + <<"application/rpki-roa"/utf8>>; + + <<"roff"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"rp9"/utf8>> -> + <<"application/vnd.cloanto.rp9"/utf8>>; + + <<"rpss"/utf8>> -> + <<"application/vnd.nokia.radio-presets"/utf8>>; + + <<"rpst"/utf8>> -> + <<"application/vnd.nokia.radio-preset"/utf8>>; + + <<"rq"/utf8>> -> + <<"application/sparql-query"/utf8>>; + + <<"rs"/utf8>> -> + <<"application/rls-services+xml"/utf8>>; + + <<"rsd"/utf8>> -> + <<"application/rsd+xml"/utf8>>; + + <<"rss"/utf8>> -> + <<"application/rss+xml"/utf8>>; + + <<"rtf"/utf8>> -> + <<"application/rtf"/utf8>>; + + <<"rtx"/utf8>> -> + <<"text/richtext"/utf8>>; + + <<"s"/utf8>> -> + <<"text/x-asm"/utf8>>; + + <<"s3m"/utf8>> -> + <<"audio/s3m"/utf8>>; + + <<"saf"/utf8>> -> + <<"application/vnd.yamaha.smaf-audio"/utf8>>; + + <<"sbml"/utf8>> -> + <<"application/sbml+xml"/utf8>>; + + <<"sc"/utf8>> -> + <<"application/vnd.ibm.secure-container"/utf8>>; + + <<"scd"/utf8>> -> + <<"application/x-msschedule"/utf8>>; + + <<"scm"/utf8>> -> + <<"application/vnd.lotus-screencam"/utf8>>; + + <<"scq"/utf8>> -> + <<"application/scvp-cv-request"/utf8>>; + + <<"scs"/utf8>> -> + <<"application/scvp-cv-response"/utf8>>; + + <<"scurl"/utf8>> -> + <<"text/vnd.curl.scurl"/utf8>>; + + <<"sda"/utf8>> -> + <<"application/vnd.stardivision.draw"/utf8>>; + + <<"sdc"/utf8>> -> + <<"application/vnd.stardivision.calc"/utf8>>; + + <<"sdd"/utf8>> -> + <<"application/vnd.stardivision.impress"/utf8>>; + + <<"sdkd"/utf8>> -> + <<"application/vnd.solent.sdkm+xml"/utf8>>; + + <<"sdkm"/utf8>> -> + <<"application/vnd.solent.sdkm+xml"/utf8>>; + + <<"sdp"/utf8>> -> + <<"application/sdp"/utf8>>; + + <<"sdw"/utf8>> -> + <<"application/vnd.stardivision.writer"/utf8>>; + + <<"see"/utf8>> -> + <<"application/vnd.seemail"/utf8>>; + + <<"seed"/utf8>> -> + <<"application/vnd.fdsn.seed"/utf8>>; + + <<"sema"/utf8>> -> + <<"application/vnd.sema"/utf8>>; + + <<"semd"/utf8>> -> + <<"application/vnd.semd"/utf8>>; + + <<"semf"/utf8>> -> + <<"application/vnd.semf"/utf8>>; + + <<"ser"/utf8>> -> + <<"application/java-serialized-object"/utf8>>; + + <<"setpay"/utf8>> -> + <<"application/set-payment-initiation"/utf8>>; + + <<"setreg"/utf8>> -> + <<"application/set-registration-initiation"/utf8>>; + + <<"sfd-hdstx"/utf8>> -> + <<"application/vnd.hydrostatix.sof-data"/utf8>>; + + <<"sfs"/utf8>> -> + <<"application/vnd.spotfire.sfs"/utf8>>; + + <<"sfv"/utf8>> -> + <<"text/x-sfv"/utf8>>; + + <<"sgi"/utf8>> -> + <<"image/sgi"/utf8>>; + + <<"sgl"/utf8>> -> + <<"application/vnd.stardivision.writer-global"/utf8>>; + + <<"sgm"/utf8>> -> + <<"text/sgml"/utf8>>; + + <<"sgml"/utf8>> -> + <<"text/sgml"/utf8>>; + + <<"sh"/utf8>> -> + <<"application/x-sh"/utf8>>; + + <<"shar"/utf8>> -> + <<"application/x-shar"/utf8>>; + + <<"shf"/utf8>> -> + <<"application/shf+xml"/utf8>>; + + <<"sid"/utf8>> -> + <<"image/x-mrsid-image"/utf8>>; + + <<"sig"/utf8>> -> + <<"application/pgp-signature"/utf8>>; + + <<"sil"/utf8>> -> + <<"audio/silk"/utf8>>; + + <<"silo"/utf8>> -> + <<"model/mesh"/utf8>>; + + <<"sis"/utf8>> -> + <<"application/vnd.symbian.install"/utf8>>; + + <<"sisx"/utf8>> -> + <<"application/vnd.symbian.install"/utf8>>; + + <<"sit"/utf8>> -> + <<"application/x-stuffit"/utf8>>; + + <<"sitx"/utf8>> -> + <<"application/x-stuffitx"/utf8>>; + + <<"skd"/utf8>> -> + <<"application/vnd.koan"/utf8>>; + + <<"skm"/utf8>> -> + <<"application/vnd.koan"/utf8>>; + + <<"skp"/utf8>> -> + <<"application/vnd.koan"/utf8>>; + + <<"skt"/utf8>> -> + <<"application/vnd.koan"/utf8>>; + + <<"sldm"/utf8>> -> + <<"application/vnd.ms-powerpoint.slide.macroenabled.12"/utf8>>; + + <<"sldx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.presentationml.slide"/utf8>>; + + <<"slt"/utf8>> -> + <<"application/vnd.epson.salt"/utf8>>; + + <<"sm"/utf8>> -> + <<"application/vnd.stepmania.stepchart"/utf8>>; + + <<"smf"/utf8>> -> + <<"application/vnd.stardivision.math"/utf8>>; + + <<"smi"/utf8>> -> + <<"application/smil+xml"/utf8>>; + + <<"smil"/utf8>> -> + <<"application/smil+xml"/utf8>>; + + <<"smv"/utf8>> -> + <<"video/x-smv"/utf8>>; + + <<"smzip"/utf8>> -> + <<"application/vnd.stepmania.package"/utf8>>; + + <<"snd"/utf8>> -> + <<"audio/basic"/utf8>>; + + <<"snf"/utf8>> -> + <<"application/x-font-snf"/utf8>>; + + <<"so"/utf8>> -> + <<"application/octet-stream"/utf8>>; + + <<"spc"/utf8>> -> + <<"application/x-pkcs7-certificates"/utf8>>; + + <<"spf"/utf8>> -> + <<"application/vnd.yamaha.smaf-phrase"/utf8>>; + + <<"spl"/utf8>> -> + <<"application/x-futuresplash"/utf8>>; + + <<"spot"/utf8>> -> + <<"text/vnd.in3d.spot"/utf8>>; + + <<"spp"/utf8>> -> + <<"application/scvp-vp-response"/utf8>>; + + <<"spq"/utf8>> -> + <<"application/scvp-vp-request"/utf8>>; + + <<"spx"/utf8>> -> + <<"audio/ogg"/utf8>>; + + <<"sql"/utf8>> -> + <<"application/x-sql"/utf8>>; + + <<"src"/utf8>> -> + <<"application/x-wais-source"/utf8>>; + + <<"srt"/utf8>> -> + <<"application/x-subrip"/utf8>>; + + <<"sru"/utf8>> -> + <<"application/sru+xml"/utf8>>; + + <<"srx"/utf8>> -> + <<"application/sparql-results+xml"/utf8>>; + + <<"ssdl"/utf8>> -> + <<"application/ssdl+xml"/utf8>>; + + <<"sse"/utf8>> -> + <<"application/vnd.kodak-descriptor"/utf8>>; + + <<"ssf"/utf8>> -> + <<"application/vnd.epson.ssf"/utf8>>; + + <<"ssml"/utf8>> -> + <<"application/ssml+xml"/utf8>>; + + <<"st"/utf8>> -> + <<"application/vnd.sailingtracker.track"/utf8>>; + + <<"stc"/utf8>> -> + <<"application/vnd.sun.xml.calc.template"/utf8>>; + + <<"std"/utf8>> -> + <<"application/vnd.sun.xml.draw.template"/utf8>>; + + <<"stf"/utf8>> -> + <<"application/vnd.wt.stf"/utf8>>; + + <<"sti"/utf8>> -> + <<"application/vnd.sun.xml.impress.template"/utf8>>; + + <<"stk"/utf8>> -> + <<"application/hyperstudio"/utf8>>; + + <<"stl"/utf8>> -> + <<"application/vnd.ms-pki.stl"/utf8>>; + + <<"str"/utf8>> -> + <<"application/vnd.pg.format"/utf8>>; + + <<"stw"/utf8>> -> + <<"application/vnd.sun.xml.writer.template"/utf8>>; + + <<"sub"/utf8>> -> + <<"image/vnd.dvb.subtitle"/utf8>>; + + <<"sus"/utf8>> -> + <<"application/vnd.sus-calendar"/utf8>>; + + <<"susp"/utf8>> -> + <<"application/vnd.sus-calendar"/utf8>>; + + <<"sv4cpio"/utf8>> -> + <<"application/x-sv4cpio"/utf8>>; + + <<"sv4crc"/utf8>> -> + <<"application/x-sv4crc"/utf8>>; + + <<"svc"/utf8>> -> + <<"application/vnd.dvb.service"/utf8>>; + + <<"svd"/utf8>> -> + <<"application/vnd.svd"/utf8>>; + + <<"svg"/utf8>> -> + <<"image/svg+xml"/utf8>>; + + <<"svgz"/utf8>> -> + <<"image/svg+xml"/utf8>>; + + <<"swa"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"swf"/utf8>> -> + <<"application/x-shockwave-flash"/utf8>>; + + <<"swi"/utf8>> -> + <<"application/vnd.aristanetworks.swi"/utf8>>; + + <<"sxc"/utf8>> -> + <<"application/vnd.sun.xml.calc"/utf8>>; + + <<"sxd"/utf8>> -> + <<"application/vnd.sun.xml.draw"/utf8>>; + + <<"sxg"/utf8>> -> + <<"application/vnd.sun.xml.writer.global"/utf8>>; + + <<"sxi"/utf8>> -> + <<"application/vnd.sun.xml.impress"/utf8>>; + + <<"sxm"/utf8>> -> + <<"application/vnd.sun.xml.math"/utf8>>; + + <<"sxw"/utf8>> -> + <<"application/vnd.sun.xml.writer"/utf8>>; + + <<"t"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"t3"/utf8>> -> + <<"application/x-t3vm-image"/utf8>>; + + <<"taglet"/utf8>> -> + <<"application/vnd.mynfc"/utf8>>; + + <<"tao"/utf8>> -> + <<"application/vnd.tao.intent-module-archive"/utf8>>; + + <<"tar"/utf8>> -> + <<"application/x-tar"/utf8>>; + + <<"tcap"/utf8>> -> + <<"application/vnd.3gpp2.tcap"/utf8>>; + + <<"tcl"/utf8>> -> + <<"application/x-tcl"/utf8>>; + + <<"teacher"/utf8>> -> + <<"application/vnd.smart.teacher"/utf8>>; + + <<"tei"/utf8>> -> + <<"application/tei+xml"/utf8>>; + + <<"teicorpus"/utf8>> -> + <<"application/tei+xml"/utf8>>; + + <<"tex"/utf8>> -> + <<"application/x-tex"/utf8>>; + + <<"texi"/utf8>> -> + <<"application/x-texinfo"/utf8>>; + + <<"texinfo"/utf8>> -> + <<"application/x-texinfo"/utf8>>; + + <<"text"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"tfi"/utf8>> -> + <<"application/thraud+xml"/utf8>>; + + <<"tfm"/utf8>> -> + <<"application/x-tex-tfm"/utf8>>; + + <<"tga"/utf8>> -> + <<"image/x-tga"/utf8>>; + + <<"thmx"/utf8>> -> + <<"application/vnd.ms-officetheme"/utf8>>; + + <<"tif"/utf8>> -> + <<"image/tiff"/utf8>>; + + <<"tiff"/utf8>> -> + <<"image/tiff"/utf8>>; + + <<"tmo"/utf8>> -> + <<"application/vnd.tmobile-livetv"/utf8>>; + + <<"torrent"/utf8>> -> + <<"application/x-bittorrent"/utf8>>; + + <<"tpl"/utf8>> -> + <<"application/vnd.groove-tool-template"/utf8>>; + + <<"tpt"/utf8>> -> + <<"application/vnd.trid.tpt"/utf8>>; + + <<"tr"/utf8>> -> + <<"text/troff"/utf8>>; + + <<"tra"/utf8>> -> + <<"application/vnd.trueapp"/utf8>>; + + <<"trm"/utf8>> -> + <<"application/x-msterminal"/utf8>>; + + <<"ts"/utf8>> -> + <<"video/mp2t"/utf8>>; + + <<"tsd"/utf8>> -> + <<"application/timestamped-data"/utf8>>; + + <<"tsv"/utf8>> -> + <<"text/tab-separated-values"/utf8>>; + + <<"ttc"/utf8>> -> + <<"font/collection"/utf8>>; + + <<"ttf"/utf8>> -> + <<"font/ttf"/utf8>>; + + <<"ttl"/utf8>> -> + <<"text/turtle"/utf8>>; + + <<"twd"/utf8>> -> + <<"application/vnd.simtech-mindmapper"/utf8>>; + + <<"twds"/utf8>> -> + <<"application/vnd.simtech-mindmapper"/utf8>>; + + <<"txd"/utf8>> -> + <<"application/vnd.genomatix.tuxedo"/utf8>>; + + <<"txf"/utf8>> -> + <<"application/vnd.mobius.txf"/utf8>>; + + <<"txt"/utf8>> -> + <<"text/plain"/utf8>>; + + <<"u32"/utf8>> -> + <<"application/x-authorware-bin"/utf8>>; + + <<"udeb"/utf8>> -> + <<"application/x-debian-package"/utf8>>; + + <<"ufd"/utf8>> -> + <<"application/vnd.ufdl"/utf8>>; + + <<"ufdl"/utf8>> -> + <<"application/vnd.ufdl"/utf8>>; + + <<"ulx"/utf8>> -> + <<"application/x-glulx"/utf8>>; + + <<"umj"/utf8>> -> + <<"application/vnd.umajin"/utf8>>; + + <<"unityweb"/utf8>> -> + <<"application/vnd.unity"/utf8>>; + + <<"uoml"/utf8>> -> + <<"application/vnd.uoml+xml"/utf8>>; + + <<"uri"/utf8>> -> + <<"text/uri-list"/utf8>>; + + <<"uris"/utf8>> -> + <<"text/uri-list"/utf8>>; + + <<"urls"/utf8>> -> + <<"text/uri-list"/utf8>>; + + <<"ustar"/utf8>> -> + <<"application/x-ustar"/utf8>>; + + <<"utz"/utf8>> -> + <<"application/vnd.uiq.theme"/utf8>>; + + <<"uu"/utf8>> -> + <<"text/x-uuencode"/utf8>>; + + <<"uva"/utf8>> -> + <<"audio/vnd.dece.audio"/utf8>>; + + <<"uvd"/utf8>> -> + <<"application/vnd.dece.data"/utf8>>; + + <<"uvf"/utf8>> -> + <<"application/vnd.dece.data"/utf8>>; + + <<"uvg"/utf8>> -> + <<"image/vnd.dece.graphic"/utf8>>; + + <<"uvh"/utf8>> -> + <<"video/vnd.dece.hd"/utf8>>; + + <<"uvi"/utf8>> -> + <<"image/vnd.dece.graphic"/utf8>>; + + <<"uvm"/utf8>> -> + <<"video/vnd.dece.mobile"/utf8>>; + + <<"uvp"/utf8>> -> + <<"video/vnd.dece.pd"/utf8>>; + + <<"uvs"/utf8>> -> + <<"video/vnd.dece.sd"/utf8>>; + + <<"uvt"/utf8>> -> + <<"application/vnd.dece.ttml+xml"/utf8>>; + + <<"uvu"/utf8>> -> + <<"video/vnd.uvvu.mp4"/utf8>>; + + <<"uvv"/utf8>> -> + <<"video/vnd.dece.video"/utf8>>; + + <<"uvva"/utf8>> -> + <<"audio/vnd.dece.audio"/utf8>>; + + <<"uvvd"/utf8>> -> + <<"application/vnd.dece.data"/utf8>>; + + <<"uvvf"/utf8>> -> + <<"application/vnd.dece.data"/utf8>>; + + <<"uvvg"/utf8>> -> + <<"image/vnd.dece.graphic"/utf8>>; + + <<"uvvh"/utf8>> -> + <<"video/vnd.dece.hd"/utf8>>; + + <<"uvvi"/utf8>> -> + <<"image/vnd.dece.graphic"/utf8>>; + + <<"uvvm"/utf8>> -> + <<"video/vnd.dece.mobile"/utf8>>; + + <<"uvvp"/utf8>> -> + <<"video/vnd.dece.pd"/utf8>>; + + <<"uvvs"/utf8>> -> + <<"video/vnd.dece.sd"/utf8>>; + + <<"uvvt"/utf8>> -> + <<"application/vnd.dece.ttml+xml"/utf8>>; + + <<"uvvu"/utf8>> -> + <<"video/vnd.uvvu.mp4"/utf8>>; + + <<"uvvv"/utf8>> -> + <<"video/vnd.dece.video"/utf8>>; + + <<"uvvx"/utf8>> -> + <<"application/vnd.dece.unspecified"/utf8>>; + + <<"uvvz"/utf8>> -> + <<"application/vnd.dece.zip"/utf8>>; + + <<"uvx"/utf8>> -> + <<"application/vnd.dece.unspecified"/utf8>>; + + <<"uvz"/utf8>> -> + <<"application/vnd.dece.zip"/utf8>>; + + <<"vcard"/utf8>> -> + <<"text/vcard"/utf8>>; + + <<"vcd"/utf8>> -> + <<"application/x-cdlink"/utf8>>; + + <<"vcf"/utf8>> -> + <<"text/x-vcard"/utf8>>; + + <<"vcg"/utf8>> -> + <<"application/vnd.groove-vcard"/utf8>>; + + <<"vcs"/utf8>> -> + <<"text/x-vcalendar"/utf8>>; + + <<"vcx"/utf8>> -> + <<"application/vnd.vcx"/utf8>>; + + <<"vis"/utf8>> -> + <<"application/vnd.visionary"/utf8>>; + + <<"viv"/utf8>> -> + <<"video/vnd.vivo"/utf8>>; + + <<"vob"/utf8>> -> + <<"video/x-ms-vob"/utf8>>; + + <<"vor"/utf8>> -> + <<"application/vnd.stardivision.writer"/utf8>>; + + <<"vox"/utf8>> -> + <<"application/x-authorware-bin"/utf8>>; + + <<"vrml"/utf8>> -> + <<"model/vrml"/utf8>>; + + <<"vsd"/utf8>> -> + <<"application/vnd.visio"/utf8>>; + + <<"vsf"/utf8>> -> + <<"application/vnd.vsf"/utf8>>; + + <<"vss"/utf8>> -> + <<"application/vnd.visio"/utf8>>; + + <<"vst"/utf8>> -> + <<"application/vnd.visio"/utf8>>; + + <<"vsw"/utf8>> -> + <<"application/vnd.visio"/utf8>>; + + <<"vtu"/utf8>> -> + <<"model/vnd.vtu"/utf8>>; + + <<"vxml"/utf8>> -> + <<"application/voicexml+xml"/utf8>>; + + <<"w3d"/utf8>> -> + <<"application/x-director"/utf8>>; + + <<"wad"/utf8>> -> + <<"application/x-doom"/utf8>>; + + <<"wav"/utf8>> -> + <<"audio/x-wav"/utf8>>; + + <<"wasm"/utf8>> -> + <<"application/wasm"/utf8>>; + + <<"wax"/utf8>> -> + <<"audio/x-ms-wax"/utf8>>; + + <<"wbmp"/utf8>> -> + <<"image/vnd.wap.wbmp"/utf8>>; + + <<"wbs"/utf8>> -> + <<"application/vnd.criticaltools.wbs+xml"/utf8>>; + + <<"wbxml"/utf8>> -> + <<"application/vnd.wap.wbxml"/utf8>>; + + <<"wcm"/utf8>> -> + <<"application/vnd.ms-works"/utf8>>; + + <<"wdb"/utf8>> -> + <<"application/vnd.ms-works"/utf8>>; + + <<"wdp"/utf8>> -> + <<"image/vnd.ms-photo"/utf8>>; + + <<"weba"/utf8>> -> + <<"audio/webm"/utf8>>; + + <<"webm"/utf8>> -> + <<"video/webm"/utf8>>; + + <<"webp"/utf8>> -> + <<"image/webp"/utf8>>; + + <<"wg"/utf8>> -> + <<"application/vnd.pmi.widget"/utf8>>; + + <<"wgt"/utf8>> -> + <<"application/widget"/utf8>>; + + <<"wks"/utf8>> -> + <<"application/vnd.ms-works"/utf8>>; + + <<"wm"/utf8>> -> + <<"video/x-ms-wm"/utf8>>; + + <<"wma"/utf8>> -> + <<"audio/x-ms-wma"/utf8>>; + + <<"wmd"/utf8>> -> + <<"application/x-ms-wmd"/utf8>>; + + <<"wmf"/utf8>> -> + <<"application/x-msmetafile"/utf8>>; + + <<"wml"/utf8>> -> + <<"text/vnd.wap.wml"/utf8>>; + + <<"wmlc"/utf8>> -> + <<"application/vnd.wap.wmlc"/utf8>>; + + <<"wmls"/utf8>> -> + <<"text/vnd.wap.wmlscript"/utf8>>; + + <<"wmlsc"/utf8>> -> + <<"application/vnd.wap.wmlscriptc"/utf8>>; + + <<"wmv"/utf8>> -> + <<"video/x-ms-wmv"/utf8>>; + + <<"wmx"/utf8>> -> + <<"video/x-ms-wmx"/utf8>>; + + <<"wmz"/utf8>> -> + <<"application/x-msmetafile"/utf8>>; + + <<"woff"/utf8>> -> + <<"font/woff"/utf8>>; + + <<"woff2"/utf8>> -> + <<"font/woff2"/utf8>>; + + <<"wpd"/utf8>> -> + <<"application/vnd.wordperfect"/utf8>>; + + <<"wpl"/utf8>> -> + <<"application/vnd.ms-wpl"/utf8>>; + + <<"wps"/utf8>> -> + <<"application/vnd.ms-works"/utf8>>; + + <<"wqd"/utf8>> -> + <<"application/vnd.wqd"/utf8>>; + + <<"wri"/utf8>> -> + <<"application/x-mswrite"/utf8>>; + + <<"wrl"/utf8>> -> + <<"model/vrml"/utf8>>; + + <<"wsdl"/utf8>> -> + <<"application/wsdl+xml"/utf8>>; + + <<"wspolicy"/utf8>> -> + <<"application/wspolicy+xml"/utf8>>; + + <<"wtb"/utf8>> -> + <<"application/vnd.webturbo"/utf8>>; + + <<"wvx"/utf8>> -> + <<"video/x-ms-wvx"/utf8>>; + + <<"x32"/utf8>> -> + <<"application/x-authorware-bin"/utf8>>; + + <<"x3d"/utf8>> -> + <<"model/x3d+xml"/utf8>>; + + <<"x3db"/utf8>> -> + <<"model/x3d+binary"/utf8>>; + + <<"x3dbz"/utf8>> -> + <<"model/x3d+binary"/utf8>>; + + <<"x3dv"/utf8>> -> + <<"model/x3d+vrml"/utf8>>; + + <<"x3dvz"/utf8>> -> + <<"model/x3d+vrml"/utf8>>; + + <<"x3dz"/utf8>> -> + <<"model/x3d+xml"/utf8>>; + + <<"xaml"/utf8>> -> + <<"application/xaml+xml"/utf8>>; + + <<"xap"/utf8>> -> + <<"application/x-silverlight-app"/utf8>>; + + <<"xar"/utf8>> -> + <<"application/vnd.xara"/utf8>>; + + <<"xbap"/utf8>> -> + <<"application/x-ms-xbap"/utf8>>; + + <<"xbd"/utf8>> -> + <<"application/vnd.fujixerox.docuworks.binder"/utf8>>; + + <<"xbm"/utf8>> -> + <<"image/x-xbitmap"/utf8>>; + + <<"xdf"/utf8>> -> + <<"application/xcap-diff+xml"/utf8>>; + + <<"xdm"/utf8>> -> + <<"application/vnd.syncml.dm+xml"/utf8>>; + + <<"xdp"/utf8>> -> + <<"application/vnd.adobe.xdp+xml"/utf8>>; + + <<"xdssc"/utf8>> -> + <<"application/dssc+xml"/utf8>>; + + <<"xdw"/utf8>> -> + <<"application/vnd.fujixerox.docuworks"/utf8>>; + + <<"xenc"/utf8>> -> + <<"application/xenc+xml"/utf8>>; + + <<"xer"/utf8>> -> + <<"application/patch-ops-error+xml"/utf8>>; + + <<"xfdf"/utf8>> -> + <<"application/vnd.adobe.xfdf"/utf8>>; + + <<"xfdl"/utf8>> -> + <<"application/vnd.xfdl"/utf8>>; + + <<"xht"/utf8>> -> + <<"application/xhtml+xml"/utf8>>; + + <<"xhtml"/utf8>> -> + <<"application/xhtml+xml"/utf8>>; + + <<"xhvml"/utf8>> -> + <<"application/xv+xml"/utf8>>; + + <<"xif"/utf8>> -> + <<"image/vnd.xiff"/utf8>>; + + <<"xla"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xlam"/utf8>> -> + <<"application/vnd.ms-excel.addin.macroenabled.12"/utf8>>; + + <<"xlc"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xlf"/utf8>> -> + <<"application/x-xliff+xml"/utf8>>; + + <<"xlm"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xls"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xlsb"/utf8>> -> + <<"application/vnd.ms-excel.sheet.binary.macroenabled.12"/utf8>>; + + <<"xlsm"/utf8>> -> + <<"application/vnd.ms-excel.sheet.macroenabled.12"/utf8>>; + + <<"xlsx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/utf8>>; + + <<"xlt"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xltm"/utf8>> -> + <<"application/vnd.ms-excel.template.macroenabled.12"/utf8>>; + + <<"xltx"/utf8>> -> + <<"application/vnd.openxmlformats-officedocument.spreadsheetml.template"/utf8>>; + + <<"xlw"/utf8>> -> + <<"application/vnd.ms-excel"/utf8>>; + + <<"xm"/utf8>> -> + <<"audio/xm"/utf8>>; + + <<"xml"/utf8>> -> + <<"application/xml"/utf8>>; + + <<"xo"/utf8>> -> + <<"application/vnd.olpc-sugar"/utf8>>; + + <<"xop"/utf8>> -> + <<"application/xop+xml"/utf8>>; + + <<"xpi"/utf8>> -> + <<"application/x-xpinstall"/utf8>>; + + <<"xpl"/utf8>> -> + <<"application/xproc+xml"/utf8>>; + + <<"xpm"/utf8>> -> + <<"image/x-xpixmap"/utf8>>; + + <<"xpr"/utf8>> -> + <<"application/vnd.is-xpr"/utf8>>; + + <<"xps"/utf8>> -> + <<"application/vnd.ms-xpsdocument"/utf8>>; + + <<"xpw"/utf8>> -> + <<"application/vnd.intercon.formnet"/utf8>>; + + <<"xpx"/utf8>> -> + <<"application/vnd.intercon.formnet"/utf8>>; + + <<"xsl"/utf8>> -> + <<"application/xml"/utf8>>; + + <<"xslt"/utf8>> -> + <<"application/xslt+xml"/utf8>>; + + <<"xsm"/utf8>> -> + <<"application/vnd.syncml+xml"/utf8>>; + + <<"xspf"/utf8>> -> + <<"application/xspf+xml"/utf8>>; + + <<"xul"/utf8>> -> + <<"application/vnd.mozilla.xul+xml"/utf8>>; + + <<"xvm"/utf8>> -> + <<"application/xv+xml"/utf8>>; + + <<"xvml"/utf8>> -> + <<"application/xv+xml"/utf8>>; + + <<"xwd"/utf8>> -> + <<"image/x-xwindowdump"/utf8>>; + + <<"xyz"/utf8>> -> + <<"chemical/x-xyz"/utf8>>; + + <<"xz"/utf8>> -> + <<"application/x-xz"/utf8>>; + + <<"yang"/utf8>> -> + <<"application/yang"/utf8>>; + + <<"yin"/utf8>> -> + <<"application/yin+xml"/utf8>>; + + <<"z1"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z2"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z3"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z4"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z5"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z6"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z7"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"z8"/utf8>> -> + <<"application/x-zmachine"/utf8>>; + + <<"zaz"/utf8>> -> + <<"application/vnd.zzazz.deck+xml"/utf8>>; + + <<"zip"/utf8>> -> + <<"application/zip"/utf8>>; + + <<"zir"/utf8>> -> + <<"application/vnd.zul"/utf8>>; + + <<"zirz"/utf8>> -> + <<"application/vnd.zul"/utf8>>; + + <<"zmm"/utf8>> -> + <<"application/vnd.handheld-entertainment+xml"/utf8>>; + + _ -> + <<"application/octet-stream"/utf8>> + end. + +-file("/Users/louis/src/gleam/marceau/src/marceau.gleam", 1007). +-spec mime_type_to_extensions(binary()) -> list(binary()). +mime_type_to_extensions(Mime_type) -> + case Mime_type of + <<"application/andrew-inset"/utf8>> -> + [<<"ez"/utf8>>]; + + <<"application/applixware"/utf8>> -> + [<<"aw"/utf8>>]; + + <<"application/atom+xml"/utf8>> -> + [<<"atom"/utf8>>]; + + <<"application/atomcat+xml"/utf8>> -> + [<<"atomcat"/utf8>>]; + + <<"application/atomsvc+xml"/utf8>> -> + [<<"atomsvc"/utf8>>]; + + <<"application/ccxml+xml"/utf8>> -> + [<<"ccxml"/utf8>>]; + + <<"application/cdmi-capability"/utf8>> -> + [<<"cdmia"/utf8>>]; + + <<"application/cdmi-container"/utf8>> -> + [<<"cdmic"/utf8>>]; + + <<"application/cdmi-domain"/utf8>> -> + [<<"cdmid"/utf8>>]; + + <<"application/cdmi-object"/utf8>> -> + [<<"cdmio"/utf8>>]; + + <<"application/cdmi-queue"/utf8>> -> + [<<"cdmiq"/utf8>>]; + + <<"application/cu-seeme"/utf8>> -> + [<<"cu"/utf8>>]; + + <<"application/davmount+xml"/utf8>> -> + [<<"davmount"/utf8>>]; + + <<"application/docbook+xml"/utf8>> -> + [<<"dbk"/utf8>>]; + + <<"application/dssc+der"/utf8>> -> + [<<"dssc"/utf8>>]; + + <<"application/dssc+xml"/utf8>> -> + [<<"xdssc"/utf8>>]; + + <<"application/ecmascript"/utf8>> -> + [<<"ecma"/utf8>>]; + + <<"application/emma+xml"/utf8>> -> + [<<"emma"/utf8>>]; + + <<"application/epub+zip"/utf8>> -> + [<<"epub"/utf8>>]; + + <<"application/exi"/utf8>> -> + [<<"exi"/utf8>>]; + + <<"application/font-tdpfr"/utf8>> -> + [<<"pfr"/utf8>>]; + + <<"application/gml+xml"/utf8>> -> + [<<"gml"/utf8>>]; + + <<"application/gpx+xml"/utf8>> -> + [<<"gpx"/utf8>>]; + + <<"application/gxf"/utf8>> -> + [<<"gxf"/utf8>>]; + + <<"application/hyperstudio"/utf8>> -> + [<<"stk"/utf8>>]; + + <<"application/inkml+xml"/utf8>> -> + [<<"ink"/utf8>>, <<"inkml"/utf8>>]; + + <<"application/ipfix"/utf8>> -> + [<<"ipfix"/utf8>>]; + + <<"application/java-archive"/utf8>> -> + [<<"jar"/utf8>>]; + + <<"application/java-serialized-object"/utf8>> -> + [<<"ser"/utf8>>]; + + <<"application/java-vm"/utf8>> -> + [<<"class"/utf8>>]; + + <<"application/javascript"/utf8>> -> + [<<"js"/utf8>>, <<"mjs"/utf8>>]; + + <<"application/json"/utf8>> -> + [<<"json"/utf8>>]; + + <<"application/jsonml+json"/utf8>> -> + [<<"jsonml"/utf8>>]; + + <<"application/lost+xml"/utf8>> -> + [<<"lostxml"/utf8>>]; + + <<"application/mac-binhex40"/utf8>> -> + [<<"hqx"/utf8>>]; + + <<"application/mac-compactpro"/utf8>> -> + [<<"cpt"/utf8>>]; + + <<"application/mads+xml"/utf8>> -> + [<<"mads"/utf8>>]; + + <<"application/marc"/utf8>> -> + [<<"mrc"/utf8>>]; + + <<"application/marcxml+xml"/utf8>> -> + [<<"mrcx"/utf8>>]; + + <<"application/mathematica"/utf8>> -> + [<<"ma"/utf8>>, <<"nb"/utf8>>, <<"mb"/utf8>>]; + + <<"application/mathml+xml"/utf8>> -> + [<<"mathml"/utf8>>]; + + <<"application/mbox"/utf8>> -> + [<<"mbox"/utf8>>]; + + <<"application/mediaservercontrol+xml"/utf8>> -> + [<<"mscml"/utf8>>]; + + <<"application/metalink+xml"/utf8>> -> + [<<"metalink"/utf8>>]; + + <<"application/metalink4+xml"/utf8>> -> + [<<"meta4"/utf8>>]; + + <<"application/mets+xml"/utf8>> -> + [<<"mets"/utf8>>]; + + <<"application/mods+xml"/utf8>> -> + [<<"mods"/utf8>>]; + + <<"application/mp21"/utf8>> -> + [<<"m21"/utf8>>, <<"mp21"/utf8>>]; + + <<"application/mp4"/utf8>> -> + [<<"mp4s"/utf8>>]; + + <<"application/msword"/utf8>> -> + [<<"doc"/utf8>>, <<"dot"/utf8>>]; + + <<"application/mxf"/utf8>> -> + [<<"mxf"/utf8>>]; + + <<"application/octet-stream"/utf8>> -> + [<<"bin"/utf8>>, + <<"dms"/utf8>>, + <<"lrf"/utf8>>, + <<"mar"/utf8>>, + <<"so"/utf8>>, + <<"dist"/utf8>>, + <<"distz"/utf8>>, + <<"pkg"/utf8>>, + <<"bpk"/utf8>>, + <<"dump"/utf8>>, + <<"elc"/utf8>>, + <<"deploy"/utf8>>]; + + <<"application/oda"/utf8>> -> + [<<"oda"/utf8>>]; + + <<"application/oebps-package+xml"/utf8>> -> + [<<"opf"/utf8>>]; + + <<"application/ogg"/utf8>> -> + [<<"ogx"/utf8>>]; + + <<"application/omdoc+xml"/utf8>> -> + [<<"omdoc"/utf8>>]; + + <<"application/onenote"/utf8>> -> + [<<"onetoc"/utf8>>, + <<"onetoc2"/utf8>>, + <<"onetmp"/utf8>>, + <<"onepkg"/utf8>>]; + + <<"application/oxps"/utf8>> -> + [<<"oxps"/utf8>>]; + + <<"application/patch-ops-error+xml"/utf8>> -> + [<<"xer"/utf8>>]; + + <<"application/pdf"/utf8>> -> + [<<"pdf"/utf8>>]; + + <<"application/pgp-encrypted"/utf8>> -> + [<<"pgp"/utf8>>]; + + <<"application/pgp-signature"/utf8>> -> + [<<"asc"/utf8>>, <<"sig"/utf8>>]; + + <<"application/pics-rules"/utf8>> -> + [<<"prf"/utf8>>]; + + <<"application/pkcs10"/utf8>> -> + [<<"p10"/utf8>>]; + + <<"application/pkcs7-mime"/utf8>> -> + [<<"p7m"/utf8>>, <<"p7c"/utf8>>]; + + <<"application/pkcs7-signature"/utf8>> -> + [<<"p7s"/utf8>>]; + + <<"application/pkcs8"/utf8>> -> + [<<"p8"/utf8>>]; + + <<"application/pkix-attr-cert"/utf8>> -> + [<<"ac"/utf8>>]; + + <<"application/pkix-cert"/utf8>> -> + [<<"cer"/utf8>>]; + + <<"application/pkix-crl"/utf8>> -> + [<<"crl"/utf8>>]; + + <<"application/pkix-pkipath"/utf8>> -> + [<<"pkipath"/utf8>>]; + + <<"application/pkixcmp"/utf8>> -> + [<<"pki"/utf8>>]; + + <<"application/pls+xml"/utf8>> -> + [<<"pls"/utf8>>]; + + <<"application/postscript"/utf8>> -> + [<<"ai"/utf8>>, <<"eps"/utf8>>, <<"ps"/utf8>>]; + + <<"application/prs.cww"/utf8>> -> + [<<"cww"/utf8>>]; + + <<"application/pskc+xml"/utf8>> -> + [<<"pskcxml"/utf8>>]; + + <<"application/rdf+xml"/utf8>> -> + [<<"rdf"/utf8>>]; + + <<"application/reginfo+xml"/utf8>> -> + [<<"rif"/utf8>>]; + + <<"application/relax-ng-compact-syntax"/utf8>> -> + [<<"rnc"/utf8>>]; + + <<"application/resource-lists+xml"/utf8>> -> + [<<"rl"/utf8>>]; + + <<"application/resource-lists-diff+xml"/utf8>> -> + [<<"rld"/utf8>>]; + + <<"application/rls-services+xml"/utf8>> -> + [<<"rs"/utf8>>]; + + <<"application/rpki-ghostbusters"/utf8>> -> + [<<"gbr"/utf8>>]; + + <<"application/rpki-manifest"/utf8>> -> + [<<"mft"/utf8>>]; + + <<"application/rpki-roa"/utf8>> -> + [<<"roa"/utf8>>]; + + <<"application/rsd+xml"/utf8>> -> + [<<"rsd"/utf8>>]; + + <<"application/rss+xml"/utf8>> -> + [<<"rss"/utf8>>]; + + <<"application/rtf"/utf8>> -> + [<<"rtf"/utf8>>]; + + <<"application/sbml+xml"/utf8>> -> + [<<"sbml"/utf8>>]; + + <<"application/scvp-cv-request"/utf8>> -> + [<<"scq"/utf8>>]; + + <<"application/scvp-cv-response"/utf8>> -> + [<<"scs"/utf8>>]; + + <<"application/scvp-vp-request"/utf8>> -> + [<<"spq"/utf8>>]; + + <<"application/scvp-vp-response"/utf8>> -> + [<<"spp"/utf8>>]; + + <<"application/sdp"/utf8>> -> + [<<"sdp"/utf8>>]; + + <<"application/set-payment-initiation"/utf8>> -> + [<<"setpay"/utf8>>]; + + <<"application/set-registration-initiation"/utf8>> -> + [<<"setreg"/utf8>>]; + + <<"application/shf+xml"/utf8>> -> + [<<"shf"/utf8>>]; + + <<"application/smil+xml"/utf8>> -> + [<<"smi"/utf8>>, <<"smil"/utf8>>]; + + <<"application/sparql-query"/utf8>> -> + [<<"rq"/utf8>>]; + + <<"application/sparql-results+xml"/utf8>> -> + [<<"srx"/utf8>>]; + + <<"application/srgs"/utf8>> -> + [<<"gram"/utf8>>]; + + <<"application/srgs+xml"/utf8>> -> + [<<"grxml"/utf8>>]; + + <<"application/sru+xml"/utf8>> -> + [<<"sru"/utf8>>]; + + <<"application/ssdl+xml"/utf8>> -> + [<<"ssdl"/utf8>>]; + + <<"application/ssml+xml"/utf8>> -> + [<<"ssml"/utf8>>]; + + <<"application/tei+xml"/utf8>> -> + [<<"tei"/utf8>>, <<"teicorpus"/utf8>>]; + + <<"application/thraud+xml"/utf8>> -> + [<<"tfi"/utf8>>]; + + <<"application/timestamped-data"/utf8>> -> + [<<"tsd"/utf8>>]; + + <<"application/vnd.3gpp.pic-bw-large"/utf8>> -> + [<<"plb"/utf8>>]; + + <<"application/vnd.3gpp.pic-bw-small"/utf8>> -> + [<<"psb"/utf8>>]; + + <<"application/vnd.3gpp.pic-bw-var"/utf8>> -> + [<<"pvb"/utf8>>]; + + <<"application/vnd.3gpp2.tcap"/utf8>> -> + [<<"tcap"/utf8>>]; + + <<"application/vnd.3m.post-it-notes"/utf8>> -> + [<<"pwn"/utf8>>]; + + <<"application/vnd.accpac.simply.aso"/utf8>> -> + [<<"aso"/utf8>>]; + + <<"application/vnd.accpac.simply.imp"/utf8>> -> + [<<"imp"/utf8>>]; + + <<"application/vnd.acucobol"/utf8>> -> + [<<"acu"/utf8>>]; + + <<"application/vnd.acucorp"/utf8>> -> + [<<"atc"/utf8>>, <<"acutc"/utf8>>]; + + <<"application/vnd.adobe.air-application-installer-package+zip"/utf8>> -> + [<<"air"/utf8>>]; + + <<"application/vnd.adobe.formscentral.fcdt"/utf8>> -> + [<<"fcdt"/utf8>>]; + + <<"application/vnd.adobe.fxp"/utf8>> -> + [<<"fxp"/utf8>>, <<"fxpl"/utf8>>]; + + <<"application/vnd.adobe.xdp+xml"/utf8>> -> + [<<"xdp"/utf8>>]; + + <<"application/vnd.adobe.xfdf"/utf8>> -> + [<<"xfdf"/utf8>>]; + + <<"application/vnd.ahead.space"/utf8>> -> + [<<"ahead"/utf8>>]; + + <<"application/vnd.airzip.filesecure.azf"/utf8>> -> + [<<"azf"/utf8>>]; + + <<"application/vnd.airzip.filesecure.azs"/utf8>> -> + [<<"azs"/utf8>>]; + + <<"application/vnd.amazon.ebook"/utf8>> -> + [<<"azw"/utf8>>]; + + <<"application/vnd.americandynamics.acc"/utf8>> -> + [<<"acc"/utf8>>]; + + <<"application/vnd.amiga.ami"/utf8>> -> + [<<"ami"/utf8>>]; + + <<"application/vnd.android.package-archive"/utf8>> -> + [<<"apk"/utf8>>]; + + <<"application/vnd.anser-web-certificate-issue-initiation"/utf8>> -> + [<<"cii"/utf8>>]; + + <<"application/vnd.anser-web-funds-transfer-initiation"/utf8>> -> + [<<"fti"/utf8>>]; + + <<"application/vnd.antix.game-component"/utf8>> -> + [<<"atx"/utf8>>]; + + <<"application/vnd.apple.installer+xml"/utf8>> -> + [<<"mpkg"/utf8>>]; + + <<"application/vnd.apple.mpegurl"/utf8>> -> + [<<"m3u8"/utf8>>]; + + <<"application/vnd.aristanetworks.swi"/utf8>> -> + [<<"swi"/utf8>>]; + + <<"application/vnd.astraea-software.iota"/utf8>> -> + [<<"iota"/utf8>>]; + + <<"application/vnd.audiograph"/utf8>> -> + [<<"aep"/utf8>>]; + + <<"application/vnd.blueice.multipass"/utf8>> -> + [<<"mpm"/utf8>>]; + + <<"application/vnd.bmi"/utf8>> -> + [<<"bmi"/utf8>>]; + + <<"application/vnd.businessobjects"/utf8>> -> + [<<"rep"/utf8>>]; + + <<"application/vnd.chemdraw+xml"/utf8>> -> + [<<"cdxml"/utf8>>]; + + <<"application/vnd.chipnuts.karaoke-mmd"/utf8>> -> + [<<"mmd"/utf8>>]; + + <<"application/vnd.cinderella"/utf8>> -> + [<<"cdy"/utf8>>]; + + <<"application/vnd.claymore"/utf8>> -> + [<<"cla"/utf8>>]; + + <<"application/vnd.cloanto.rp9"/utf8>> -> + [<<"rp9"/utf8>>]; + + <<"application/vnd.clonk.c4group"/utf8>> -> + [<<"c4g"/utf8>>, + <<"c4d"/utf8>>, + <<"c4f"/utf8>>, + <<"c4p"/utf8>>, + <<"c4u"/utf8>>]; + + <<"application/vnd.cluetrust.cartomobile-config"/utf8>> -> + [<<"c11amc"/utf8>>]; + + <<"application/vnd.cluetrust.cartomobile-config-pkg"/utf8>> -> + [<<"c11amz"/utf8>>]; + + <<"application/vnd.commonspace"/utf8>> -> + [<<"csp"/utf8>>]; + + <<"application/vnd.contact.cmsg"/utf8>> -> + [<<"cdbcmsg"/utf8>>]; + + <<"application/vnd.cosmocaller"/utf8>> -> + [<<"cmc"/utf8>>]; + + <<"application/vnd.crick.clicker"/utf8>> -> + [<<"clkx"/utf8>>]; + + <<"application/vnd.crick.clicker.keyboard"/utf8>> -> + [<<"clkk"/utf8>>]; + + <<"application/vnd.crick.clicker.palette"/utf8>> -> + [<<"clkp"/utf8>>]; + + <<"application/vnd.crick.clicker.template"/utf8>> -> + [<<"clkt"/utf8>>]; + + <<"application/vnd.crick.clicker.wordbank"/utf8>> -> + [<<"clkw"/utf8>>]; + + <<"application/vnd.criticaltools.wbs+xml"/utf8>> -> + [<<"wbs"/utf8>>]; + + <<"application/vnd.ctc-posml"/utf8>> -> + [<<"pml"/utf8>>]; + + <<"application/vnd.cups-ppd"/utf8>> -> + [<<"ppd"/utf8>>]; + + <<"application/vnd.curl.car"/utf8>> -> + [<<"car"/utf8>>]; + + <<"application/vnd.curl.pcurl"/utf8>> -> + [<<"pcurl"/utf8>>]; + + <<"application/vnd.dart"/utf8>> -> + [<<"dart"/utf8>>]; + + <<"application/vnd.data-vision.rdz"/utf8>> -> + [<<"rdz"/utf8>>]; + + <<"application/vnd.dece.data"/utf8>> -> + [<<"uvf"/utf8>>, <<"uvvf"/utf8>>, <<"uvd"/utf8>>, <<"uvvd"/utf8>>]; + + <<"application/vnd.dece.ttml+xml"/utf8>> -> + [<<"uvt"/utf8>>, <<"uvvt"/utf8>>]; + + <<"application/vnd.dece.unspecified"/utf8>> -> + [<<"uvx"/utf8>>, <<"uvvx"/utf8>>]; + + <<"application/vnd.dece.zip"/utf8>> -> + [<<"uvz"/utf8>>, <<"uvvz"/utf8>>]; + + <<"application/vnd.denovo.fcselayout-link"/utf8>> -> + [<<"fe_launch"/utf8>>]; + + <<"application/vnd.dna"/utf8>> -> + [<<"dna"/utf8>>]; + + <<"application/vnd.dolby.mlp"/utf8>> -> + [<<"mlp"/utf8>>]; + + <<"application/vnd.dpgraph"/utf8>> -> + [<<"dpg"/utf8>>]; + + <<"application/vnd.dreamfactory"/utf8>> -> + [<<"dfac"/utf8>>]; + + <<"application/vnd.ds-keypoint"/utf8>> -> + [<<"kpxx"/utf8>>]; + + <<"application/vnd.dvb.ait"/utf8>> -> + [<<"ait"/utf8>>]; + + <<"application/vnd.dvb.service"/utf8>> -> + [<<"svc"/utf8>>]; + + <<"application/vnd.dynageo"/utf8>> -> + [<<"geo"/utf8>>]; + + <<"application/vnd.ecowin.chart"/utf8>> -> + [<<"mag"/utf8>>]; + + <<"application/vnd.enliven"/utf8>> -> + [<<"nml"/utf8>>]; + + <<"application/vnd.epson.esf"/utf8>> -> + [<<"esf"/utf8>>]; + + <<"application/vnd.epson.msf"/utf8>> -> + [<<"msf"/utf8>>]; + + <<"application/vnd.epson.quickanime"/utf8>> -> + [<<"qam"/utf8>>]; + + <<"application/vnd.epson.salt"/utf8>> -> + [<<"slt"/utf8>>]; + + <<"application/vnd.epson.ssf"/utf8>> -> + [<<"ssf"/utf8>>]; + + <<"application/vnd.eszigno3+xml"/utf8>> -> + [<<"es3"/utf8>>, <<"et3"/utf8>>]; + + <<"application/vnd.ezpix-album"/utf8>> -> + [<<"ez2"/utf8>>]; + + <<"application/vnd.ezpix-package"/utf8>> -> + [<<"ez3"/utf8>>]; + + <<"application/vnd.fdf"/utf8>> -> + [<<"fdf"/utf8>>]; + + <<"application/vnd.fdsn.mseed"/utf8>> -> + [<<"mseed"/utf8>>]; + + <<"application/vnd.fdsn.seed"/utf8>> -> + [<<"seed"/utf8>>, <<"dataless"/utf8>>]; + + <<"application/vnd.flographit"/utf8>> -> + [<<"gph"/utf8>>]; + + <<"application/vnd.fluxtime.clip"/utf8>> -> + [<<"ftc"/utf8>>]; + + <<"application/vnd.framemaker"/utf8>> -> + [<<"fm"/utf8>>, <<"frame"/utf8>>, <<"maker"/utf8>>, <<"book"/utf8>>]; + + <<"application/vnd.frogans.fnc"/utf8>> -> + [<<"fnc"/utf8>>]; + + <<"application/vnd.frogans.ltf"/utf8>> -> + [<<"ltf"/utf8>>]; + + <<"application/vnd.fsc.weblaunch"/utf8>> -> + [<<"fsc"/utf8>>]; + + <<"application/vnd.fujitsu.oasys"/utf8>> -> + [<<"oas"/utf8>>]; + + <<"application/vnd.fujitsu.oasys2"/utf8>> -> + [<<"oa2"/utf8>>]; + + <<"application/vnd.fujitsu.oasys3"/utf8>> -> + [<<"oa3"/utf8>>]; + + <<"application/vnd.fujitsu.oasysgp"/utf8>> -> + [<<"fg5"/utf8>>]; + + <<"application/vnd.fujitsu.oasysprs"/utf8>> -> + [<<"bh2"/utf8>>]; + + <<"application/vnd.fujixerox.ddd"/utf8>> -> + [<<"ddd"/utf8>>]; + + <<"application/vnd.fujixerox.docuworks"/utf8>> -> + [<<"xdw"/utf8>>]; + + <<"application/vnd.fujixerox.docuworks.binder"/utf8>> -> + [<<"xbd"/utf8>>]; + + <<"application/vnd.fuzzysheet"/utf8>> -> + [<<"fzs"/utf8>>]; + + <<"application/vnd.genomatix.tuxedo"/utf8>> -> + [<<"txd"/utf8>>]; + + <<"application/vnd.geogebra.file"/utf8>> -> + [<<"ggb"/utf8>>]; + + <<"application/vnd.geogebra.slides"/utf8>> -> + [<<"ggs"/utf8>>]; + + <<"application/vnd.geogebra.tool"/utf8>> -> + [<<"ggt"/utf8>>]; + + <<"application/vnd.geometry-explorer"/utf8>> -> + [<<"gex"/utf8>>, <<"gre"/utf8>>]; + + <<"application/vnd.geonext"/utf8>> -> + [<<"gxt"/utf8>>]; + + <<"application/vnd.geoplan"/utf8>> -> + [<<"g2w"/utf8>>]; + + <<"application/vnd.geospace"/utf8>> -> + [<<"g3w"/utf8>>]; + + <<"application/vnd.gmx"/utf8>> -> + [<<"gmx"/utf8>>]; + + <<"application/vnd.google-earth.kml+xml"/utf8>> -> + [<<"kml"/utf8>>]; + + <<"application/vnd.google-earth.kmz"/utf8>> -> + [<<"kmz"/utf8>>]; + + <<"application/vnd.grafeq"/utf8>> -> + [<<"gqf"/utf8>>, <<"gqs"/utf8>>]; + + <<"application/vnd.groove-account"/utf8>> -> + [<<"gac"/utf8>>]; + + <<"application/vnd.groove-help"/utf8>> -> + [<<"ghf"/utf8>>]; + + <<"application/vnd.groove-identity-message"/utf8>> -> + [<<"gim"/utf8>>]; + + <<"application/vnd.groove-injector"/utf8>> -> + [<<"grv"/utf8>>]; + + <<"application/vnd.groove-tool-message"/utf8>> -> + [<<"gtm"/utf8>>]; + + <<"application/vnd.groove-tool-template"/utf8>> -> + [<<"tpl"/utf8>>]; + + <<"application/vnd.groove-vcard"/utf8>> -> + [<<"vcg"/utf8>>]; + + <<"application/vnd.hal+xml"/utf8>> -> + [<<"hal"/utf8>>]; + + <<"application/vnd.handheld-entertainment+xml"/utf8>> -> + [<<"zmm"/utf8>>]; + + <<"application/vnd.hbci"/utf8>> -> + [<<"hbci"/utf8>>]; + + <<"application/vnd.hhe.lesson-player"/utf8>> -> + [<<"les"/utf8>>]; + + <<"application/vnd.hp-hpgl"/utf8>> -> + [<<"hpgl"/utf8>>]; + + <<"application/vnd.hp-hpid"/utf8>> -> + [<<"hpid"/utf8>>]; + + <<"application/vnd.hp-hps"/utf8>> -> + [<<"hps"/utf8>>]; + + <<"application/vnd.hp-jlyt"/utf8>> -> + [<<"jlt"/utf8>>]; + + <<"application/vnd.hp-pcl"/utf8>> -> + [<<"pcl"/utf8>>]; + + <<"application/vnd.hp-pclxl"/utf8>> -> + [<<"pclxl"/utf8>>]; + + <<"application/vnd.hydrostatix.sof-data"/utf8>> -> + [<<"sfd-hdstx"/utf8>>]; + + <<"application/vnd.ibm.minipay"/utf8>> -> + [<<"mpy"/utf8>>]; + + <<"application/vnd.ibm.modcap"/utf8>> -> + [<<"afp"/utf8>>, <<"listafp"/utf8>>, <<"list3820"/utf8>>]; + + <<"application/vnd.ibm.rights-management"/utf8>> -> + [<<"irm"/utf8>>]; + + <<"application/vnd.ibm.secure-container"/utf8>> -> + [<<"sc"/utf8>>]; + + <<"application/vnd.iccprofile"/utf8>> -> + [<<"icc"/utf8>>, <<"icm"/utf8>>]; + + <<"application/vnd.igloader"/utf8>> -> + [<<"igl"/utf8>>]; + + <<"application/vnd.immervision-ivp"/utf8>> -> + [<<"ivp"/utf8>>]; + + <<"application/vnd.immervision-ivu"/utf8>> -> + [<<"ivu"/utf8>>]; + + <<"application/vnd.insors.igm"/utf8>> -> + [<<"igm"/utf8>>]; + + <<"application/vnd.intercon.formnet"/utf8>> -> + [<<"xpw"/utf8>>, <<"xpx"/utf8>>]; + + <<"application/vnd.intergeo"/utf8>> -> + [<<"i2g"/utf8>>]; + + <<"application/vnd.intu.qbo"/utf8>> -> + [<<"qbo"/utf8>>]; + + <<"application/vnd.intu.qfx"/utf8>> -> + [<<"qfx"/utf8>>]; + + <<"application/vnd.ipunplugged.rcprofile"/utf8>> -> + [<<"rcprofile"/utf8>>]; + + <<"application/vnd.irepository.package+xml"/utf8>> -> + [<<"irp"/utf8>>]; + + <<"application/vnd.is-xpr"/utf8>> -> + [<<"xpr"/utf8>>]; + + <<"application/vnd.isac.fcs"/utf8>> -> + [<<"fcs"/utf8>>]; + + <<"application/vnd.jam"/utf8>> -> + [<<"jam"/utf8>>]; + + <<"application/vnd.jcp.javame.midlet-rms"/utf8>> -> + [<<"rms"/utf8>>]; + + <<"application/vnd.jisp"/utf8>> -> + [<<"jisp"/utf8>>]; + + <<"application/vnd.joost.joda-archive"/utf8>> -> + [<<"joda"/utf8>>]; + + <<"application/vnd.kahootz"/utf8>> -> + [<<"ktz"/utf8>>, <<"ktr"/utf8>>]; + + <<"application/vnd.kde.karbon"/utf8>> -> + [<<"karbon"/utf8>>]; + + <<"application/vnd.kde.kchart"/utf8>> -> + [<<"chrt"/utf8>>]; + + <<"application/vnd.kde.kformula"/utf8>> -> + [<<"kfo"/utf8>>]; + + <<"application/vnd.kde.kivio"/utf8>> -> + [<<"flw"/utf8>>]; + + <<"application/vnd.kde.kontour"/utf8>> -> + [<<"kon"/utf8>>]; + + <<"application/vnd.kde.kpresenter"/utf8>> -> + [<<"kpr"/utf8>>, <<"kpt"/utf8>>]; + + <<"application/vnd.kde.kspread"/utf8>> -> + [<<"ksp"/utf8>>]; + + <<"application/vnd.kde.kword"/utf8>> -> + [<<"kwd"/utf8>>, <<"kwt"/utf8>>]; + + <<"application/vnd.kenameaapp"/utf8>> -> + [<<"htke"/utf8>>]; + + <<"application/vnd.kidspiration"/utf8>> -> + [<<"kia"/utf8>>]; + + <<"application/vnd.kinar"/utf8>> -> + [<<"kne"/utf8>>, <<"knp"/utf8>>]; + + <<"application/vnd.koan"/utf8>> -> + [<<"skp"/utf8>>, <<"skd"/utf8>>, <<"skt"/utf8>>, <<"skm"/utf8>>]; + + <<"application/vnd.kodak-descriptor"/utf8>> -> + [<<"sse"/utf8>>]; + + <<"application/vnd.las.las+xml"/utf8>> -> + [<<"lasxml"/utf8>>]; + + <<"application/vnd.llamagraphics.life-balance.desktop"/utf8>> -> + [<<"lbd"/utf8>>]; + + <<"application/vnd.llamagraphics.life-balance.exchange+xml"/utf8>> -> + [<<"lbe"/utf8>>]; + + <<"application/vnd.lotus-1-2-3"/utf8>> -> + [<<"123"/utf8>>]; + + <<"application/vnd.lotus-approach"/utf8>> -> + [<<"apr"/utf8>>]; + + <<"application/vnd.lotus-freelance"/utf8>> -> + [<<"pre"/utf8>>]; + + <<"application/vnd.lotus-notes"/utf8>> -> + [<<"nsf"/utf8>>]; + + <<"application/vnd.lotus-organizer"/utf8>> -> + [<<"org"/utf8>>]; + + <<"application/vnd.lotus-screencam"/utf8>> -> + [<<"scm"/utf8>>]; + + <<"application/vnd.lotus-wordpro"/utf8>> -> + [<<"lwp"/utf8>>]; + + <<"application/vnd.macports.portpkg"/utf8>> -> + [<<"portpkg"/utf8>>]; + + <<"application/vnd.mcd"/utf8>> -> + [<<"mcd"/utf8>>]; + + <<"application/vnd.medcalcdata"/utf8>> -> + [<<"mc1"/utf8>>]; + + <<"application/vnd.mediastation.cdkey"/utf8>> -> + [<<"cdkey"/utf8>>]; + + <<"application/vnd.mfer"/utf8>> -> + [<<"mwf"/utf8>>]; + + <<"application/vnd.mfmp"/utf8>> -> + [<<"mfm"/utf8>>]; + + <<"application/vnd.micrografx.flo"/utf8>> -> + [<<"flo"/utf8>>]; + + <<"application/vnd.micrografx.igx"/utf8>> -> + [<<"igx"/utf8>>]; + + <<"application/vnd.mif"/utf8>> -> + [<<"mif"/utf8>>]; + + <<"application/vnd.mobius.daf"/utf8>> -> + [<<"daf"/utf8>>]; + + <<"application/vnd.mobius.dis"/utf8>> -> + [<<"dis"/utf8>>]; + + <<"application/vnd.mobius.mbk"/utf8>> -> + [<<"mbk"/utf8>>]; + + <<"application/vnd.mobius.mqy"/utf8>> -> + [<<"mqy"/utf8>>]; + + <<"application/vnd.mobius.msl"/utf8>> -> + [<<"msl"/utf8>>]; + + <<"application/vnd.mobius.plc"/utf8>> -> + [<<"plc"/utf8>>]; + + <<"application/vnd.mobius.txf"/utf8>> -> + [<<"txf"/utf8>>]; + + <<"application/vnd.mophun.application"/utf8>> -> + [<<"mpn"/utf8>>]; + + <<"application/vnd.mophun.certificate"/utf8>> -> + [<<"mpc"/utf8>>]; + + <<"application/vnd.mozilla.xul+xml"/utf8>> -> + [<<"xul"/utf8>>]; + + <<"application/vnd.ms-artgalry"/utf8>> -> + [<<"cil"/utf8>>]; + + <<"application/vnd.ms-cab-compressed"/utf8>> -> + [<<"cab"/utf8>>]; + + <<"application/vnd.ms-excel"/utf8>> -> + [<<"xls"/utf8>>, + <<"xlm"/utf8>>, + <<"xla"/utf8>>, + <<"xlc"/utf8>>, + <<"xlt"/utf8>>, + <<"xlw"/utf8>>]; + + <<"application/vnd.ms-excel.addin.macroenabled.12"/utf8>> -> + [<<"xlam"/utf8>>]; + + <<"application/vnd.ms-excel.sheet.binary.macroenabled.12"/utf8>> -> + [<<"xlsb"/utf8>>]; + + <<"application/vnd.ms-excel.sheet.macroenabled.12"/utf8>> -> + [<<"xlsm"/utf8>>]; + + <<"application/vnd.ms-excel.template.macroenabled.12"/utf8>> -> + [<<"xltm"/utf8>>]; + + <<"application/vnd.ms-fontobject"/utf8>> -> + [<<"eot"/utf8>>]; + + <<"application/vnd.ms-htmlhelp"/utf8>> -> + [<<"chm"/utf8>>]; + + <<"application/vnd.ms-ims"/utf8>> -> + [<<"ims"/utf8>>]; + + <<"application/vnd.ms-lrm"/utf8>> -> + [<<"lrm"/utf8>>]; + + <<"application/vnd.ms-officetheme"/utf8>> -> + [<<"thmx"/utf8>>]; + + <<"application/vnd.ms-pki.seccat"/utf8>> -> + [<<"cat"/utf8>>]; + + <<"application/vnd.ms-pki.stl"/utf8>> -> + [<<"stl"/utf8>>]; + + <<"application/vnd.ms-powerpoint"/utf8>> -> + [<<"ppt"/utf8>>, <<"pps"/utf8>>, <<"pot"/utf8>>]; + + <<"application/vnd.ms-powerpoint.addin.macroenabled.12"/utf8>> -> + [<<"ppam"/utf8>>]; + + <<"application/vnd.ms-powerpoint.presentation.macroenabled.12"/utf8>> -> + [<<"pptm"/utf8>>]; + + <<"application/vnd.ms-powerpoint.slide.macroenabled.12"/utf8>> -> + [<<"sldm"/utf8>>]; + + <<"application/vnd.ms-powerpoint.slideshow.macroenabled.12"/utf8>> -> + [<<"ppsm"/utf8>>]; + + <<"application/vnd.ms-powerpoint.template.macroenabled.12"/utf8>> -> + [<<"potm"/utf8>>]; + + <<"application/vnd.ms-project"/utf8>> -> + [<<"mpp"/utf8>>, <<"mpt"/utf8>>]; + + <<"application/vnd.ms-word.document.macroenabled.12"/utf8>> -> + [<<"docm"/utf8>>]; + + <<"application/vnd.ms-word.template.macroenabled.12"/utf8>> -> + [<<"dotm"/utf8>>]; + + <<"application/vnd.ms-works"/utf8>> -> + [<<"wps"/utf8>>, <<"wks"/utf8>>, <<"wcm"/utf8>>, <<"wdb"/utf8>>]; + + <<"application/vnd.ms-wpl"/utf8>> -> + [<<"wpl"/utf8>>]; + + <<"application/vnd.ms-xpsdocument"/utf8>> -> + [<<"xps"/utf8>>]; + + <<"application/vnd.mseq"/utf8>> -> + [<<"mseq"/utf8>>]; + + <<"application/vnd.musician"/utf8>> -> + [<<"mus"/utf8>>]; + + <<"application/vnd.muvee.style"/utf8>> -> + [<<"msty"/utf8>>]; + + <<"application/vnd.mynfc"/utf8>> -> + [<<"taglet"/utf8>>]; + + <<"application/vnd.neurolanguage.nlu"/utf8>> -> + [<<"nlu"/utf8>>]; + + <<"application/vnd.nitf"/utf8>> -> + [<<"ntf"/utf8>>, <<"nitf"/utf8>>]; + + <<"application/vnd.noblenet-directory"/utf8>> -> + [<<"nnd"/utf8>>]; + + <<"application/vnd.noblenet-sealer"/utf8>> -> + [<<"nns"/utf8>>]; + + <<"application/vnd.noblenet-web"/utf8>> -> + [<<"nnw"/utf8>>]; + + <<"application/vnd.nokia.n-gage.data"/utf8>> -> + [<<"ngdat"/utf8>>]; + + <<"application/vnd.nokia.n-gage.symbian.install"/utf8>> -> + [<<"n-gage"/utf8>>]; + + <<"application/vnd.nokia.radio-preset"/utf8>> -> + [<<"rpst"/utf8>>]; + + <<"application/vnd.nokia.radio-presets"/utf8>> -> + [<<"rpss"/utf8>>]; + + <<"application/vnd.novadigm.edm"/utf8>> -> + [<<"edm"/utf8>>]; + + <<"application/vnd.novadigm.edx"/utf8>> -> + [<<"edx"/utf8>>]; + + <<"application/vnd.novadigm.ext"/utf8>> -> + [<<"ext"/utf8>>]; + + <<"application/vnd.oasis.opendocument.chart"/utf8>> -> + [<<"odc"/utf8>>]; + + <<"application/vnd.oasis.opendocument.chart-template"/utf8>> -> + [<<"otc"/utf8>>]; + + <<"application/vnd.oasis.opendocument.database"/utf8>> -> + [<<"odb"/utf8>>]; + + <<"application/vnd.oasis.opendocument.formula"/utf8>> -> + [<<"odf"/utf8>>]; + + <<"application/vnd.oasis.opendocument.formula-template"/utf8>> -> + [<<"odft"/utf8>>]; + + <<"application/vnd.oasis.opendocument.graphics"/utf8>> -> + [<<"odg"/utf8>>]; + + <<"application/vnd.oasis.opendocument.graphics-template"/utf8>> -> + [<<"otg"/utf8>>]; + + <<"application/vnd.oasis.opendocument.image"/utf8>> -> + [<<"odi"/utf8>>]; + + <<"application/vnd.oasis.opendocument.image-template"/utf8>> -> + [<<"oti"/utf8>>]; + + <<"application/vnd.oasis.opendocument.presentation"/utf8>> -> + [<<"odp"/utf8>>]; + + <<"application/vnd.oasis.opendocument.presentation-template"/utf8>> -> + [<<"otp"/utf8>>]; + + <<"application/vnd.oasis.opendocument.spreadsheet"/utf8>> -> + [<<"ods"/utf8>>]; + + <<"application/vnd.oasis.opendocument.spreadsheet-template"/utf8>> -> + [<<"ots"/utf8>>]; + + <<"application/vnd.oasis.opendocument.text"/utf8>> -> + [<<"odt"/utf8>>]; + + <<"application/vnd.oasis.opendocument.text-master"/utf8>> -> + [<<"odm"/utf8>>]; + + <<"application/vnd.oasis.opendocument.text-template"/utf8>> -> + [<<"ott"/utf8>>]; + + <<"application/vnd.oasis.opendocument.text-web"/utf8>> -> + [<<"oth"/utf8>>]; + + <<"application/vnd.olpc-sugar"/utf8>> -> + [<<"xo"/utf8>>]; + + <<"application/vnd.oma.dd2+xml"/utf8>> -> + [<<"dd2"/utf8>>]; + + <<"application/vnd.openofficeorg.extension"/utf8>> -> + [<<"oxt"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.presentationml.presentation"/utf8>> -> + [<<"pptx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.presentationml.slide"/utf8>> -> + [<<"sldx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.presentationml.slideshow"/utf8>> -> + [<<"ppsx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.presentationml.template"/utf8>> -> + [<<"potx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/utf8>> -> + [<<"xlsx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.spreadsheetml.template"/utf8>> -> + [<<"xltx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.wordprocessingml.document"/utf8>> -> + [<<"docx"/utf8>>]; + + <<"application/vnd.openxmlformats-officedocument.wordprocessingml.template"/utf8>> -> + [<<"dotx"/utf8>>]; + + <<"application/vnd.osgeo.mapguide.package"/utf8>> -> + [<<"mgp"/utf8>>]; + + <<"application/vnd.osgi.dp"/utf8>> -> + [<<"dp"/utf8>>]; + + <<"application/vnd.osgi.subsystem"/utf8>> -> + [<<"esa"/utf8>>]; + + <<"application/vnd.palm"/utf8>> -> + [<<"pdb"/utf8>>, <<"pqa"/utf8>>, <<"oprc"/utf8>>]; + + <<"application/vnd.pawaafile"/utf8>> -> + [<<"paw"/utf8>>]; + + <<"application/vnd.pg.format"/utf8>> -> + [<<"str"/utf8>>]; + + <<"application/vnd.pg.osasli"/utf8>> -> + [<<"ei6"/utf8>>]; + + <<"application/vnd.picsel"/utf8>> -> + [<<"efif"/utf8>>]; + + <<"application/vnd.pmi.widget"/utf8>> -> + [<<"wg"/utf8>>]; + + <<"application/vnd.pocketlearn"/utf8>> -> + [<<"plf"/utf8>>]; + + <<"application/vnd.powerbuilder6"/utf8>> -> + [<<"pbd"/utf8>>]; + + <<"application/vnd.previewsystems.box"/utf8>> -> + [<<"box"/utf8>>]; + + <<"application/vnd.proteus.magazine"/utf8>> -> + [<<"mgz"/utf8>>]; + + <<"application/vnd.publishare-delta-tree"/utf8>> -> + [<<"qps"/utf8>>]; + + <<"application/vnd.pvi.ptid1"/utf8>> -> + [<<"ptid"/utf8>>]; + + <<"application/vnd.quark.quarkxpress"/utf8>> -> + [<<"qxd"/utf8>>, + <<"qxt"/utf8>>, + <<"qwd"/utf8>>, + <<"qwt"/utf8>>, + <<"qxl"/utf8>>, + <<"qxb"/utf8>>]; + + <<"application/vnd.realvnc.bed"/utf8>> -> + [<<"bed"/utf8>>]; + + <<"application/vnd.recordare.musicxml"/utf8>> -> + [<<"mxl"/utf8>>]; + + <<"application/vnd.recordare.musicxml+xml"/utf8>> -> + [<<"musicxml"/utf8>>]; + + <<"application/vnd.rig.cryptonote"/utf8>> -> + [<<"cryptonote"/utf8>>]; + + <<"application/vnd.rim.cod"/utf8>> -> + [<<"cod"/utf8>>]; + + <<"application/vnd.rn-realmedia"/utf8>> -> + [<<"rm"/utf8>>]; + + <<"application/vnd.rn-realmedia-vbr"/utf8>> -> + [<<"rmvb"/utf8>>]; + + <<"application/vnd.route66.link66+xml"/utf8>> -> + [<<"link66"/utf8>>]; + + <<"application/vnd.sailingtracker.track"/utf8>> -> + [<<"st"/utf8>>]; + + <<"application/vnd.seemail"/utf8>> -> + [<<"see"/utf8>>]; + + <<"application/vnd.sema"/utf8>> -> + [<<"sema"/utf8>>]; + + <<"application/vnd.semd"/utf8>> -> + [<<"semd"/utf8>>]; + + <<"application/vnd.semf"/utf8>> -> + [<<"semf"/utf8>>]; + + <<"application/vnd.shana.informed.formdata"/utf8>> -> + [<<"ifm"/utf8>>]; + + <<"application/vnd.shana.informed.formtemplate"/utf8>> -> + [<<"itp"/utf8>>]; + + <<"application/vnd.shana.informed.interchange"/utf8>> -> + [<<"iif"/utf8>>]; + + <<"application/vnd.shana.informed.package"/utf8>> -> + [<<"ipk"/utf8>>]; + + <<"application/vnd.simtech-mindmapper"/utf8>> -> + [<<"twd"/utf8>>, <<"twds"/utf8>>]; + + <<"application/vnd.smaf"/utf8>> -> + [<<"mmf"/utf8>>]; + + <<"application/vnd.smart.teacher"/utf8>> -> + [<<"teacher"/utf8>>]; + + <<"application/vnd.solent.sdkm+xml"/utf8>> -> + [<<"sdkm"/utf8>>, <<"sdkd"/utf8>>]; + + <<"application/vnd.spotfire.dxp"/utf8>> -> + [<<"dxp"/utf8>>]; + + <<"application/vnd.spotfire.sfs"/utf8>> -> + [<<"sfs"/utf8>>]; + + <<"application/vnd.stardivision.calc"/utf8>> -> + [<<"sdc"/utf8>>]; + + <<"application/vnd.stardivision.draw"/utf8>> -> + [<<"sda"/utf8>>]; + + <<"application/vnd.stardivision.impress"/utf8>> -> + [<<"sdd"/utf8>>]; + + <<"application/vnd.stardivision.math"/utf8>> -> + [<<"smf"/utf8>>]; + + <<"application/vnd.stardivision.writer"/utf8>> -> + [<<"sdw"/utf8>>, <<"vor"/utf8>>]; + + <<"application/vnd.stardivision.writer-global"/utf8>> -> + [<<"sgl"/utf8>>]; + + <<"application/vnd.stepmania.package"/utf8>> -> + [<<"smzip"/utf8>>]; + + <<"application/vnd.stepmania.stepchart"/utf8>> -> + [<<"sm"/utf8>>]; + + <<"application/vnd.sun.xml.calc"/utf8>> -> + [<<"sxc"/utf8>>]; + + <<"application/vnd.sun.xml.calc.template"/utf8>> -> + [<<"stc"/utf8>>]; + + <<"application/vnd.sun.xml.draw"/utf8>> -> + [<<"sxd"/utf8>>]; + + <<"application/vnd.sun.xml.draw.template"/utf8>> -> + [<<"std"/utf8>>]; + + <<"application/vnd.sun.xml.impress"/utf8>> -> + [<<"sxi"/utf8>>]; + + <<"application/vnd.sun.xml.impress.template"/utf8>> -> + [<<"sti"/utf8>>]; + + <<"application/vnd.sun.xml.math"/utf8>> -> + [<<"sxm"/utf8>>]; + + <<"application/vnd.sun.xml.writer"/utf8>> -> + [<<"sxw"/utf8>>]; + + <<"application/vnd.sun.xml.writer.global"/utf8>> -> + [<<"sxg"/utf8>>]; + + <<"application/vnd.sun.xml.writer.template"/utf8>> -> + [<<"stw"/utf8>>]; + + <<"application/vnd.sus-calendar"/utf8>> -> + [<<"sus"/utf8>>, <<"susp"/utf8>>]; + + <<"application/vnd.svd"/utf8>> -> + [<<"svd"/utf8>>]; + + <<"application/vnd.symbian.install"/utf8>> -> + [<<"sis"/utf8>>, <<"sisx"/utf8>>]; + + <<"application/vnd.syncml+xml"/utf8>> -> + [<<"xsm"/utf8>>]; + + <<"application/vnd.syncml.dm+wbxml"/utf8>> -> + [<<"bdm"/utf8>>]; + + <<"application/vnd.syncml.dm+xml"/utf8>> -> + [<<"xdm"/utf8>>]; + + <<"application/vnd.tao.intent-module-archive"/utf8>> -> + [<<"tao"/utf8>>]; + + <<"application/vnd.tcpdump.pcap"/utf8>> -> + [<<"pcap"/utf8>>, <<"cap"/utf8>>, <<"dmp"/utf8>>]; + + <<"application/vnd.tmobile-livetv"/utf8>> -> + [<<"tmo"/utf8>>]; + + <<"application/vnd.trid.tpt"/utf8>> -> + [<<"tpt"/utf8>>]; + + <<"application/vnd.triscape.mxs"/utf8>> -> + [<<"mxs"/utf8>>]; + + <<"application/vnd.trueapp"/utf8>> -> + [<<"tra"/utf8>>]; + + <<"application/vnd.ufdl"/utf8>> -> + [<<"ufd"/utf8>>, <<"ufdl"/utf8>>]; + + <<"application/vnd.uiq.theme"/utf8>> -> + [<<"utz"/utf8>>]; + + <<"application/vnd.umajin"/utf8>> -> + [<<"umj"/utf8>>]; + + <<"application/vnd.unity"/utf8>> -> + [<<"unityweb"/utf8>>]; + + <<"application/vnd.uoml+xml"/utf8>> -> + [<<"uoml"/utf8>>]; + + <<"application/vnd.vcx"/utf8>> -> + [<<"vcx"/utf8>>]; + + <<"application/vnd.visio"/utf8>> -> + [<<"vsd"/utf8>>, <<"vst"/utf8>>, <<"vss"/utf8>>, <<"vsw"/utf8>>]; + + <<"application/vnd.visionary"/utf8>> -> + [<<"vis"/utf8>>]; + + <<"application/vnd.vsf"/utf8>> -> + [<<"vsf"/utf8>>]; + + <<"application/vnd.wap.wbxml"/utf8>> -> + [<<"wbxml"/utf8>>]; + + <<"application/vnd.wap.wmlc"/utf8>> -> + [<<"wmlc"/utf8>>]; + + <<"application/vnd.wap.wmlscriptc"/utf8>> -> + [<<"wmlsc"/utf8>>]; + + <<"application/vnd.webturbo"/utf8>> -> + [<<"wtb"/utf8>>]; + + <<"application/vnd.wolfram.player"/utf8>> -> + [<<"nbp"/utf8>>]; + + <<"application/vnd.wordperfect"/utf8>> -> + [<<"wpd"/utf8>>]; + + <<"application/vnd.wqd"/utf8>> -> + [<<"wqd"/utf8>>]; + + <<"application/vnd.wt.stf"/utf8>> -> + [<<"stf"/utf8>>]; + + <<"application/vnd.xara"/utf8>> -> + [<<"xar"/utf8>>]; + + <<"application/vnd.xfdl"/utf8>> -> + [<<"xfdl"/utf8>>]; + + <<"application/vnd.yamaha.hv-dic"/utf8>> -> + [<<"hvd"/utf8>>]; + + <<"application/vnd.yamaha.hv-script"/utf8>> -> + [<<"hvs"/utf8>>]; + + <<"application/vnd.yamaha.hv-voice"/utf8>> -> + [<<"hvp"/utf8>>]; + + <<"application/vnd.yamaha.openscoreformat"/utf8>> -> + [<<"osf"/utf8>>]; + + <<"application/vnd.yamaha.openscoreformat.osfpvg+xml"/utf8>> -> + [<<"osfpvg"/utf8>>]; + + <<"application/vnd.yamaha.smaf-audio"/utf8>> -> + [<<"saf"/utf8>>]; + + <<"application/vnd.yamaha.smaf-phrase"/utf8>> -> + [<<"spf"/utf8>>]; + + <<"application/vnd.yellowriver-custom-menu"/utf8>> -> + [<<"cmp"/utf8>>]; + + <<"application/vnd.zul"/utf8>> -> + [<<"zir"/utf8>>, <<"zirz"/utf8>>]; + + <<"application/vnd.zzazz.deck+xml"/utf8>> -> + [<<"zaz"/utf8>>]; + + <<"application/voicexml+xml"/utf8>> -> + [<<"vxml"/utf8>>]; + + <<"application/wasm"/utf8>> -> + [<<"wasm"/utf8>>]; + + <<"application/widget"/utf8>> -> + [<<"wgt"/utf8>>]; + + <<"application/winhlp"/utf8>> -> + [<<"hlp"/utf8>>]; + + <<"application/wsdl+xml"/utf8>> -> + [<<"wsdl"/utf8>>]; + + <<"application/wspolicy+xml"/utf8>> -> + [<<"wspolicy"/utf8>>]; + + <<"application/x-7z-compressed"/utf8>> -> + [<<"7z"/utf8>>]; + + <<"application/x-abiword"/utf8>> -> + [<<"abw"/utf8>>]; + + <<"application/x-ace-compressed"/utf8>> -> + [<<"ace"/utf8>>]; + + <<"application/x-apple-diskimage"/utf8>> -> + [<<"dmg"/utf8>>]; + + <<"application/x-authorware-bin"/utf8>> -> + [<<"aab"/utf8>>, <<"x32"/utf8>>, <<"u32"/utf8>>, <<"vox"/utf8>>]; + + <<"application/x-authorware-map"/utf8>> -> + [<<"aam"/utf8>>]; + + <<"application/x-authorware-seg"/utf8>> -> + [<<"aas"/utf8>>]; + + <<"application/x-bcpio"/utf8>> -> + [<<"bcpio"/utf8>>]; + + <<"application/x-bittorrent"/utf8>> -> + [<<"torrent"/utf8>>]; + + <<"application/x-blorb"/utf8>> -> + [<<"blb"/utf8>>, <<"blorb"/utf8>>]; + + <<"application/x-bzip"/utf8>> -> + [<<"bz"/utf8>>]; + + <<"application/x-bzip2"/utf8>> -> + [<<"bz2"/utf8>>, <<"boz"/utf8>>]; + + <<"application/x-cbr"/utf8>> -> + [<<"cbr"/utf8>>, + <<"cba"/utf8>>, + <<"cbt"/utf8>>, + <<"cbz"/utf8>>, + <<"cb7"/utf8>>]; + + <<"application/x-cdlink"/utf8>> -> + [<<"vcd"/utf8>>]; + + <<"application/x-cfs-compressed"/utf8>> -> + [<<"cfs"/utf8>>]; + + <<"application/x-chat"/utf8>> -> + [<<"chat"/utf8>>]; + + <<"application/x-chess-pgn"/utf8>> -> + [<<"pgn"/utf8>>]; + + <<"application/x-conference"/utf8>> -> + [<<"nsc"/utf8>>]; + + <<"application/x-cpio"/utf8>> -> + [<<"cpio"/utf8>>]; + + <<"application/x-csh"/utf8>> -> + [<<"csh"/utf8>>]; + + <<"application/x-debian-package"/utf8>> -> + [<<"deb"/utf8>>, <<"udeb"/utf8>>]; + + <<"application/x-dgc-compressed"/utf8>> -> + [<<"dgc"/utf8>>]; + + <<"application/x-director"/utf8>> -> + [<<"dir"/utf8>>, + <<"dcr"/utf8>>, + <<"dxr"/utf8>>, + <<"cst"/utf8>>, + <<"cct"/utf8>>, + <<"cxt"/utf8>>, + <<"w3d"/utf8>>, + <<"fgd"/utf8>>, + <<"swa"/utf8>>]; + + <<"application/x-doom"/utf8>> -> + [<<"wad"/utf8>>]; + + <<"application/x-dtbncx+xml"/utf8>> -> + [<<"ncx"/utf8>>]; + + <<"application/x-dtbook+xml"/utf8>> -> + [<<"dtb"/utf8>>]; + + <<"application/x-dtbresource+xml"/utf8>> -> + [<<"res"/utf8>>]; + + <<"application/x-dvi"/utf8>> -> + [<<"dvi"/utf8>>]; + + <<"application/x-envoy"/utf8>> -> + [<<"evy"/utf8>>]; + + <<"application/x-eva"/utf8>> -> + [<<"eva"/utf8>>]; + + <<"application/x-font-bdf"/utf8>> -> + [<<"bdf"/utf8>>]; + + <<"application/x-font-ghostscript"/utf8>> -> + [<<"gsf"/utf8>>]; + + <<"application/x-font-linux-psf"/utf8>> -> + [<<"psf"/utf8>>]; + + <<"application/x-font-pcf"/utf8>> -> + [<<"pcf"/utf8>>]; + + <<"application/x-font-snf"/utf8>> -> + [<<"snf"/utf8>>]; + + <<"application/x-font-type1"/utf8>> -> + [<<"pfa"/utf8>>, <<"pfb"/utf8>>, <<"pfm"/utf8>>, <<"afm"/utf8>>]; + + <<"application/x-freearc"/utf8>> -> + [<<"arc"/utf8>>]; + + <<"application/x-futuresplash"/utf8>> -> + [<<"spl"/utf8>>]; + + <<"application/x-gca-compressed"/utf8>> -> + [<<"gca"/utf8>>]; + + <<"application/x-glulx"/utf8>> -> + [<<"ulx"/utf8>>]; + + <<"application/x-gnumeric"/utf8>> -> + [<<"gnumeric"/utf8>>]; + + <<"application/x-gramps-xml"/utf8>> -> + [<<"gramps"/utf8>>]; + + <<"application/x-gtar"/utf8>> -> + [<<"gtar"/utf8>>]; + + <<"application/x-hdf"/utf8>> -> + [<<"hdf"/utf8>>]; + + <<"application/x-install-instructions"/utf8>> -> + [<<"install"/utf8>>]; + + <<"application/x-iso9660-image"/utf8>> -> + [<<"iso"/utf8>>]; + + <<"application/x-java-jnlp-file"/utf8>> -> + [<<"jnlp"/utf8>>]; + + <<"application/x-latex"/utf8>> -> + [<<"latex"/utf8>>]; + + <<"application/x-lzh-compressed"/utf8>> -> + [<<"lzh"/utf8>>, <<"lha"/utf8>>]; + + <<"application/x-mie"/utf8>> -> + [<<"mie"/utf8>>]; + + <<"application/x-mobipocket-ebook"/utf8>> -> + [<<"prc"/utf8>>, <<"mobi"/utf8>>]; + + <<"application/x-ms-application"/utf8>> -> + [<<"application"/utf8>>]; + + <<"application/x-ms-shortcut"/utf8>> -> + [<<"lnk"/utf8>>]; + + <<"application/x-ms-wmd"/utf8>> -> + [<<"wmd"/utf8>>]; + + <<"application/x-ms-wmz"/utf8>> -> + [<<"wmz"/utf8>>]; + + <<"application/x-ms-xbap"/utf8>> -> + [<<"xbap"/utf8>>]; + + <<"application/x-msaccess"/utf8>> -> + [<<"mdb"/utf8>>]; + + <<"application/x-msbinder"/utf8>> -> + [<<"obd"/utf8>>]; + + <<"application/x-mscardfile"/utf8>> -> + [<<"crd"/utf8>>]; + + <<"application/x-msclip"/utf8>> -> + [<<"clp"/utf8>>]; + + <<"application/x-msdownload"/utf8>> -> + [<<"exe"/utf8>>, + <<"dll"/utf8>>, + <<"com"/utf8>>, + <<"bat"/utf8>>, + <<"msi"/utf8>>]; + + <<"application/x-msmediaview"/utf8>> -> + [<<"mvb"/utf8>>, <<"m13"/utf8>>, <<"m14"/utf8>>]; + + <<"application/x-msmetafile"/utf8>> -> + [<<"wmf"/utf8>>, <<"wmz"/utf8>>, <<"emf"/utf8>>, <<"emz"/utf8>>]; + + <<"application/x-msmoney"/utf8>> -> + [<<"mny"/utf8>>]; + + <<"application/x-mspublisher"/utf8>> -> + [<<"pub"/utf8>>]; + + <<"application/x-msschedule"/utf8>> -> + [<<"scd"/utf8>>]; + + <<"application/x-msterminal"/utf8>> -> + [<<"trm"/utf8>>]; + + <<"application/x-mswrite"/utf8>> -> + [<<"wri"/utf8>>]; + + <<"application/x-netcdf"/utf8>> -> + [<<"nc"/utf8>>, <<"cdf"/utf8>>]; + + <<"application/x-nzb"/utf8>> -> + [<<"nzb"/utf8>>]; + + <<"application/x-pkcs12"/utf8>> -> + [<<"p12"/utf8>>, <<"pfx"/utf8>>]; + + <<"application/x-pkcs7-certificates"/utf8>> -> + [<<"p7b"/utf8>>, <<"spc"/utf8>>]; + + <<"application/x-pkcs7-certreqresp"/utf8>> -> + [<<"p7r"/utf8>>]; + + <<"application/x-rar-compressed"/utf8>> -> + [<<"rar"/utf8>>]; + + <<"application/x-research-info-systems"/utf8>> -> + [<<"ris"/utf8>>]; + + <<"application/x-sh"/utf8>> -> + [<<"sh"/utf8>>]; + + <<"application/x-shar"/utf8>> -> + [<<"shar"/utf8>>]; + + <<"application/x-shockwave-flash"/utf8>> -> + [<<"swf"/utf8>>]; + + <<"application/x-silverlight-app"/utf8>> -> + [<<"xap"/utf8>>]; + + <<"application/x-sql"/utf8>> -> + [<<"sql"/utf8>>]; + + <<"application/x-stuffit"/utf8>> -> + [<<"sit"/utf8>>]; + + <<"application/x-stuffitx"/utf8>> -> + [<<"sitx"/utf8>>]; + + <<"application/x-subrip"/utf8>> -> + [<<"srt"/utf8>>]; + + <<"application/x-sv4cpio"/utf8>> -> + [<<"sv4cpio"/utf8>>]; + + <<"application/x-sv4crc"/utf8>> -> + [<<"sv4crc"/utf8>>]; + + <<"application/x-t3vm-image"/utf8>> -> + [<<"t3"/utf8>>]; + + <<"application/x-tads"/utf8>> -> + [<<"gam"/utf8>>]; + + <<"application/x-tar"/utf8>> -> + [<<"tar"/utf8>>]; + + <<"application/x-tcl"/utf8>> -> + [<<"tcl"/utf8>>]; + + <<"application/x-tex"/utf8>> -> + [<<"tex"/utf8>>]; + + <<"application/x-tex-tfm"/utf8>> -> + [<<"tfm"/utf8>>]; + + <<"application/x-texinfo"/utf8>> -> + [<<"texinfo"/utf8>>, <<"texi"/utf8>>]; + + <<"application/x-tgif"/utf8>> -> + [<<"obj"/utf8>>]; + + <<"application/x-ustar"/utf8>> -> + [<<"ustar"/utf8>>]; + + <<"application/x-wais-source"/utf8>> -> + [<<"src"/utf8>>]; + + <<"application/x-x509-ca-cert"/utf8>> -> + [<<"der"/utf8>>, <<"crt"/utf8>>]; + + <<"application/x-xfig"/utf8>> -> + [<<"fig"/utf8>>]; + + <<"application/x-xliff+xml"/utf8>> -> + [<<"xlf"/utf8>>]; + + <<"application/x-xpinstall"/utf8>> -> + [<<"xpi"/utf8>>]; + + <<"application/x-xz"/utf8>> -> + [<<"xz"/utf8>>]; + + <<"application/x-zmachine"/utf8>> -> + [<<"z1"/utf8>>, + <<"z2"/utf8>>, + <<"z3"/utf8>>, + <<"z4"/utf8>>, + <<"z5"/utf8>>, + <<"z6"/utf8>>, + <<"z7"/utf8>>, + <<"z8"/utf8>>]; + + <<"application/xaml+xml"/utf8>> -> + [<<"xaml"/utf8>>]; + + <<"application/xcap-diff+xml"/utf8>> -> + [<<"xdf"/utf8>>]; + + <<"application/xenc+xml"/utf8>> -> + [<<"xenc"/utf8>>]; + + <<"application/xhtml+xml"/utf8>> -> + [<<"xhtml"/utf8>>, <<"xht"/utf8>>]; + + <<"application/xml"/utf8>> -> + [<<"xml"/utf8>>, <<"xsl"/utf8>>]; + + <<"application/xml-dtd"/utf8>> -> + [<<"dtd"/utf8>>]; + + <<"application/xop+xml"/utf8>> -> + [<<"xop"/utf8>>]; + + <<"application/xproc+xml"/utf8>> -> + [<<"xpl"/utf8>>]; + + <<"application/xslt+xml"/utf8>> -> + [<<"xslt"/utf8>>]; + + <<"application/xspf+xml"/utf8>> -> + [<<"xspf"/utf8>>]; + + <<"application/xv+xml"/utf8>> -> + [<<"mxml"/utf8>>, <<"xhvml"/utf8>>, <<"xvml"/utf8>>, <<"xvm"/utf8>>]; + + <<"application/yang"/utf8>> -> + [<<"yang"/utf8>>]; + + <<"application/yin+xml"/utf8>> -> + [<<"yin"/utf8>>]; + + <<"application/zip"/utf8>> -> + [<<"zip"/utf8>>]; + + <<"audio/adpcm"/utf8>> -> + [<<"adp"/utf8>>]; + + <<"audio/basic"/utf8>> -> + [<<"au"/utf8>>, <<"snd"/utf8>>]; + + <<"audio/midi"/utf8>> -> + [<<"mid"/utf8>>, <<"midi"/utf8>>, <<"kar"/utf8>>, <<"rmi"/utf8>>]; + + <<"audio/mp4"/utf8>> -> + [<<"m4a"/utf8>>, <<"mp4a"/utf8>>]; + + <<"audio/mpeg"/utf8>> -> + [<<"mpga"/utf8>>, + <<"mp2"/utf8>>, + <<"mp2a"/utf8>>, + <<"mp3"/utf8>>, + <<"m2a"/utf8>>, + <<"m3a"/utf8>>]; + + <<"audio/ogg"/utf8>> -> + [<<"oga"/utf8>>, <<"ogg"/utf8>>, <<"spx"/utf8>>, <<"opus"/utf8>>]; + + <<"audio/s3m"/utf8>> -> + [<<"s3m"/utf8>>]; + + <<"audio/silk"/utf8>> -> + [<<"sil"/utf8>>]; + + <<"audio/vnd.dece.audio"/utf8>> -> + [<<"uva"/utf8>>, <<"uvva"/utf8>>]; + + <<"audio/vnd.digital-winds"/utf8>> -> + [<<"eol"/utf8>>]; + + <<"audio/vnd.dra"/utf8>> -> + [<<"dra"/utf8>>]; + + <<"audio/vnd.dts"/utf8>> -> + [<<"dts"/utf8>>]; + + <<"audio/vnd.dts.hd"/utf8>> -> + [<<"dtshd"/utf8>>]; + + <<"audio/vnd.lucent.voice"/utf8>> -> + [<<"lvp"/utf8>>]; + + <<"audio/vnd.ms-playready.media.pya"/utf8>> -> + [<<"pya"/utf8>>]; + + <<"audio/vnd.nuera.ecelp4800"/utf8>> -> + [<<"ecelp4800"/utf8>>]; + + <<"audio/vnd.nuera.ecelp7470"/utf8>> -> + [<<"ecelp7470"/utf8>>]; + + <<"audio/vnd.nuera.ecelp9600"/utf8>> -> + [<<"ecelp9600"/utf8>>]; + + <<"audio/vnd.rip"/utf8>> -> + [<<"rip"/utf8>>]; + + <<"audio/webm"/utf8>> -> + [<<"weba"/utf8>>]; + + <<"audio/x-aac"/utf8>> -> + [<<"aac"/utf8>>]; + + <<"audio/x-aiff"/utf8>> -> + [<<"aif"/utf8>>, <<"aiff"/utf8>>, <<"aifc"/utf8>>]; + + <<"audio/x-caf"/utf8>> -> + [<<"caf"/utf8>>]; + + <<"audio/x-flac"/utf8>> -> + [<<"flac"/utf8>>]; + + <<"audio/x-matroska"/utf8>> -> + [<<"mka"/utf8>>]; + + <<"audio/x-mpegurl"/utf8>> -> + [<<"m3u"/utf8>>]; + + <<"audio/x-ms-wax"/utf8>> -> + [<<"wax"/utf8>>]; + + <<"audio/x-ms-wma"/utf8>> -> + [<<"wma"/utf8>>]; + + <<"audio/x-pn-realaudio"/utf8>> -> + [<<"ram"/utf8>>, <<"ra"/utf8>>]; + + <<"audio/x-pn-realaudio-plugin"/utf8>> -> + [<<"rmp"/utf8>>]; + + <<"audio/x-wav"/utf8>> -> + [<<"wav"/utf8>>]; + + <<"audio/xm"/utf8>> -> + [<<"xm"/utf8>>]; + + <<"chemical/x-cdx"/utf8>> -> + [<<"cdx"/utf8>>]; + + <<"chemical/x-cif"/utf8>> -> + [<<"cif"/utf8>>]; + + <<"chemical/x-cmdf"/utf8>> -> + [<<"cmdf"/utf8>>]; + + <<"chemical/x-cml"/utf8>> -> + [<<"cml"/utf8>>]; + + <<"chemical/x-csml"/utf8>> -> + [<<"csml"/utf8>>]; + + <<"chemical/x-xyz"/utf8>> -> + [<<"xyz"/utf8>>]; + + <<"font/collection"/utf8>> -> + [<<"ttc"/utf8>>]; + + <<"font/otf"/utf8>> -> + [<<"otf"/utf8>>]; + + <<"font/ttf"/utf8>> -> + [<<"ttf"/utf8>>]; + + <<"font/woff"/utf8>> -> + [<<"woff"/utf8>>]; + + <<"font/woff2"/utf8>> -> + [<<"woff2"/utf8>>]; + + <<"image/avif"/utf8>> -> + [<<"avif"/utf8>>]; + + <<"image/bmp"/utf8>> -> + [<<"bmp"/utf8>>]; + + <<"image/cgm"/utf8>> -> + [<<"cgm"/utf8>>]; + + <<"image/g3fax"/utf8>> -> + [<<"g3"/utf8>>]; + + <<"image/gif"/utf8>> -> + [<<"gif"/utf8>>]; + + <<"image/ief"/utf8>> -> + [<<"ief"/utf8>>]; + + <<"image/jpeg"/utf8>> -> + [<<"jpeg"/utf8>>, <<"jpg"/utf8>>, <<"jpe"/utf8>>]; + + <<"image/jxl"/utf8>> -> + [<<"jxl"/utf8>>]; + + <<"image/ktx"/utf8>> -> + [<<"ktx"/utf8>>]; + + <<"image/png"/utf8>> -> + [<<"png"/utf8>>]; + + <<"image/prs.btif"/utf8>> -> + [<<"btif"/utf8>>]; + + <<"image/sgi"/utf8>> -> + [<<"sgi"/utf8>>]; + + <<"image/svg+xml"/utf8>> -> + [<<"svg"/utf8>>, <<"svgz"/utf8>>]; + + <<"image/tiff"/utf8>> -> + [<<"tiff"/utf8>>, <<"tif"/utf8>>]; + + <<"image/vnd.adobe.photoshop"/utf8>> -> + [<<"psd"/utf8>>]; + + <<"image/vnd.dece.graphic"/utf8>> -> + [<<"uvi"/utf8>>, <<"uvvi"/utf8>>, <<"uvg"/utf8>>, <<"uvvg"/utf8>>]; + + <<"image/vnd.djvu"/utf8>> -> + [<<"djvu"/utf8>>, <<"djv"/utf8>>]; + + <<"image/vnd.dvb.subtitle"/utf8>> -> + [<<"sub"/utf8>>]; + + <<"image/vnd.dwg"/utf8>> -> + [<<"dwg"/utf8>>]; + + <<"image/vnd.dxf"/utf8>> -> + [<<"dxf"/utf8>>]; + + <<"image/vnd.fastbidsheet"/utf8>> -> + [<<"fbs"/utf8>>]; + + <<"image/vnd.fpx"/utf8>> -> + [<<"fpx"/utf8>>]; + + <<"image/vnd.fst"/utf8>> -> + [<<"fst"/utf8>>]; + + <<"image/vnd.fujixerox.edmics-mmr"/utf8>> -> + [<<"mmr"/utf8>>]; + + <<"image/vnd.fujixerox.edmics-rlc"/utf8>> -> + [<<"rlc"/utf8>>]; + + <<"image/vnd.ms-modi"/utf8>> -> + [<<"mdi"/utf8>>]; + + <<"image/vnd.ms-photo"/utf8>> -> + [<<"wdp"/utf8>>]; + + <<"image/vnd.net-fpx"/utf8>> -> + [<<"npx"/utf8>>]; + + <<"image/vnd.wap.wbmp"/utf8>> -> + [<<"wbmp"/utf8>>]; + + <<"image/vnd.xiff"/utf8>> -> + [<<"xif"/utf8>>]; + + <<"image/webp"/utf8>> -> + [<<"webp"/utf8>>]; + + <<"image/x-3ds"/utf8>> -> + [<<"3ds"/utf8>>]; + + <<"image/x-cmu-raster"/utf8>> -> + [<<"ras"/utf8>>]; + + <<"image/x-cmx"/utf8>> -> + [<<"cmx"/utf8>>]; + + <<"image/x-freehand"/utf8>> -> + [<<"fh"/utf8>>, + <<"fhc"/utf8>>, + <<"fh4"/utf8>>, + <<"fh5"/utf8>>, + <<"fh7"/utf8>>]; + + <<"image/x-icon"/utf8>> -> + [<<"ico"/utf8>>]; + + <<"image/x-mrsid-image"/utf8>> -> + [<<"sid"/utf8>>]; + + <<"image/x-pcx"/utf8>> -> + [<<"pcx"/utf8>>]; + + <<"image/x-pict"/utf8>> -> + [<<"pic"/utf8>>, <<"pct"/utf8>>]; + + <<"image/x-portable-anymap"/utf8>> -> + [<<"pnm"/utf8>>]; + + <<"image/x-portable-bitmap"/utf8>> -> + [<<"pbm"/utf8>>]; + + <<"image/x-portable-graymap"/utf8>> -> + [<<"pgm"/utf8>>]; + + <<"image/x-portable-pixmap"/utf8>> -> + [<<"ppm"/utf8>>]; + + <<"image/x-rgb"/utf8>> -> + [<<"rgb"/utf8>>]; + + <<"image/x-tga"/utf8>> -> + [<<"tga"/utf8>>]; + + <<"image/x-xbitmap"/utf8>> -> + [<<"xbm"/utf8>>]; + + <<"image/x-xpixmap"/utf8>> -> + [<<"xpm"/utf8>>]; + + <<"image/x-xwindowdump"/utf8>> -> + [<<"xwd"/utf8>>]; + + <<"message/rfc822"/utf8>> -> + [<<"eml"/utf8>>, <<"mime"/utf8>>]; + + <<"model/iges"/utf8>> -> + [<<"igs"/utf8>>, <<"iges"/utf8>>]; + + <<"model/mesh"/utf8>> -> + [<<"msh"/utf8>>, <<"mesh"/utf8>>, <<"silo"/utf8>>]; + + <<"model/vnd.collada+xml"/utf8>> -> + [<<"dae"/utf8>>]; + + <<"model/vnd.dwf"/utf8>> -> + [<<"dwf"/utf8>>]; + + <<"model/vnd.gdl"/utf8>> -> + [<<"gdl"/utf8>>]; + + <<"model/vnd.gtw"/utf8>> -> + [<<"gtw"/utf8>>]; + + <<"model/vnd.vtu"/utf8>> -> + [<<"vtu"/utf8>>]; + + <<"model/vrml"/utf8>> -> + [<<"wrl"/utf8>>, <<"vrml"/utf8>>]; + + <<"model/x3d+binary"/utf8>> -> + [<<"x3db"/utf8>>, <<"x3dbz"/utf8>>]; + + <<"model/x3d+vrml"/utf8>> -> + [<<"x3dv"/utf8>>, <<"x3dvz"/utf8>>]; + + <<"model/x3d+xml"/utf8>> -> + [<<"x3d"/utf8>>, <<"x3dz"/utf8>>]; + + <<"text/cache-manifest"/utf8>> -> + [<<"appcache"/utf8>>]; + + <<"text/calendar"/utf8>> -> + [<<"ics"/utf8>>, <<"ifb"/utf8>>]; + + <<"text/css"/utf8>> -> + [<<"css"/utf8>>]; + + <<"text/csv"/utf8>> -> + [<<"csv"/utf8>>]; + + <<"text/html"/utf8>> -> + [<<"html"/utf8>>, <<"htm"/utf8>>]; + + <<"text/javascript"/utf8>> -> + [<<"js"/utf8>>, <<"mjs"/utf8>>]; + + <<"text/n3"/utf8>> -> + [<<"n3"/utf8>>]; + + <<"text/plain"/utf8>> -> + [<<"txt"/utf8>>, + <<"text"/utf8>>, + <<"conf"/utf8>>, + <<"def"/utf8>>, + <<"list"/utf8>>, + <<"log"/utf8>>, + <<"in"/utf8>>]; + + <<"text/prs.lines.tag"/utf8>> -> + [<<"dsc"/utf8>>]; + + <<"text/richtext"/utf8>> -> + [<<"rtx"/utf8>>]; + + <<"text/sgml"/utf8>> -> + [<<"sgml"/utf8>>, <<"sgm"/utf8>>]; + + <<"text/tab-separated-values"/utf8>> -> + [<<"tsv"/utf8>>]; + + <<"text/troff"/utf8>> -> + [<<"t"/utf8>>, + <<"tr"/utf8>>, + <<"roff"/utf8>>, + <<"man"/utf8>>, + <<"me"/utf8>>, + <<"ms"/utf8>>]; + + <<"text/turtle"/utf8>> -> + [<<"ttl"/utf8>>]; + + <<"text/uri-list"/utf8>> -> + [<<"uri"/utf8>>, <<"uris"/utf8>>, <<"urls"/utf8>>]; + + <<"text/vcard"/utf8>> -> + [<<"vcard"/utf8>>]; + + <<"text/vnd.curl"/utf8>> -> + [<<"curl"/utf8>>]; + + <<"text/vnd.curl.dcurl"/utf8>> -> + [<<"dcurl"/utf8>>]; + + <<"text/vnd.curl.mcurl"/utf8>> -> + [<<"mcurl"/utf8>>]; + + <<"text/vnd.curl.scurl"/utf8>> -> + [<<"scurl"/utf8>>]; + + <<"text/vnd.dvb.subtitle"/utf8>> -> + [<<"sub"/utf8>>]; + + <<"text/vnd.fly"/utf8>> -> + [<<"fly"/utf8>>]; + + <<"text/vnd.fmi.flexstor"/utf8>> -> + [<<"flx"/utf8>>]; + + <<"text/vnd.graphviz"/utf8>> -> + [<<"gv"/utf8>>]; + + <<"text/vnd.in3d.3dml"/utf8>> -> + [<<"3dml"/utf8>>]; + + <<"text/vnd.in3d.spot"/utf8>> -> + [<<"spot"/utf8>>]; + + <<"text/vnd.sun.j2me.app-descriptor"/utf8>> -> + [<<"jad"/utf8>>]; + + <<"text/vnd.wap.wml"/utf8>> -> + [<<"wml"/utf8>>]; + + <<"text/vnd.wap.wmlscript"/utf8>> -> + [<<"wmls"/utf8>>]; + + <<"text/x-asm"/utf8>> -> + [<<"s"/utf8>>, <<"asm"/utf8>>]; + + <<"text/x-c"/utf8>> -> + [<<"c"/utf8>>, + <<"cc"/utf8>>, + <<"cxx"/utf8>>, + <<"cpp"/utf8>>, + <<"h"/utf8>>, + <<"hh"/utf8>>, + <<"dic"/utf8>>]; + + <<"text/x-fortran"/utf8>> -> + [<<"f"/utf8>>, <<"for"/utf8>>, <<"f77"/utf8>>, <<"f90"/utf8>>]; + + <<"text/x-java-source"/utf8>> -> + [<<"java"/utf8>>]; + + <<"text/x-nfo"/utf8>> -> + [<<"nfo"/utf8>>]; + + <<"text/x-opml"/utf8>> -> + [<<"opml"/utf8>>]; + + <<"text/x-pascal"/utf8>> -> + [<<"p"/utf8>>, <<"pas"/utf8>>]; + + <<"text/x-setext"/utf8>> -> + [<<"etx"/utf8>>]; + + <<"text/x-sfv"/utf8>> -> + [<<"sfv"/utf8>>]; + + <<"text/x-uuencode"/utf8>> -> + [<<"uu"/utf8>>]; + + <<"text/x-vcalendar"/utf8>> -> + [<<"vcs"/utf8>>]; + + <<"text/x-vcard"/utf8>> -> + [<<"vcf"/utf8>>]; + + <<"video/3gpp"/utf8>> -> + [<<"3gp"/utf8>>]; + + <<"video/3gpp2"/utf8>> -> + [<<"3g2"/utf8>>]; + + <<"video/h261"/utf8>> -> + [<<"h261"/utf8>>]; + + <<"video/h263"/utf8>> -> + [<<"h263"/utf8>>]; + + <<"video/h264"/utf8>> -> + [<<"h264"/utf8>>]; + + <<"video/jpeg"/utf8>> -> + [<<"jpgv"/utf8>>]; + + <<"video/jpm"/utf8>> -> + [<<"jpm"/utf8>>, <<"jpgm"/utf8>>]; + + <<"video/mj2"/utf8>> -> + [<<"mj2"/utf8>>, <<"mjp2"/utf8>>]; + + <<"video/mp2t"/utf8>> -> + [<<"ts"/utf8>>, <<"m2t"/utf8>>, <<"m2ts"/utf8>>, <<"mts"/utf8>>]; + + <<"video/mp4"/utf8>> -> + [<<"mp4"/utf8>>, <<"mp4v"/utf8>>, <<"mpg4"/utf8>>]; + + <<"video/mpeg"/utf8>> -> + [<<"mpeg"/utf8>>, + <<"mpg"/utf8>>, + <<"mpe"/utf8>>, + <<"m1v"/utf8>>, + <<"m2v"/utf8>>]; + + <<"video/ogg"/utf8>> -> + [<<"ogv"/utf8>>]; + + <<"video/quicktime"/utf8>> -> + [<<"qt"/utf8>>, <<"mov"/utf8>>]; + + <<"video/vnd.dece.hd"/utf8>> -> + [<<"uvh"/utf8>>, <<"uvvh"/utf8>>]; + + <<"video/vnd.dece.mobile"/utf8>> -> + [<<"uvm"/utf8>>, <<"uvvm"/utf8>>]; + + <<"video/vnd.dece.pd"/utf8>> -> + [<<"uvp"/utf8>>, <<"uvvp"/utf8>>]; + + <<"video/vnd.dece.sd"/utf8>> -> + [<<"uvs"/utf8>>, <<"uvvs"/utf8>>]; + + <<"video/vnd.dece.video"/utf8>> -> + [<<"uvv"/utf8>>, <<"uvvv"/utf8>>]; + + <<"video/vnd.dvb.file"/utf8>> -> + [<<"dvb"/utf8>>]; + + <<"video/vnd.fvt"/utf8>> -> + [<<"fvt"/utf8>>]; + + <<"video/vnd.mpegurl"/utf8>> -> + [<<"mxu"/utf8>>, <<"m4u"/utf8>>]; + + <<"video/vnd.ms-playready.media.pyv"/utf8>> -> + [<<"pyv"/utf8>>]; + + <<"video/vnd.uvvu.mp4"/utf8>> -> + [<<"uvu"/utf8>>, <<"uvvu"/utf8>>]; + + <<"video/vnd.vivo"/utf8>> -> + [<<"viv"/utf8>>]; + + <<"video/webm"/utf8>> -> + [<<"webm"/utf8>>]; + + <<"video/x-f4v"/utf8>> -> + [<<"f4v"/utf8>>]; + + <<"video/x-fli"/utf8>> -> + [<<"fli"/utf8>>]; + + <<"video/x-flv"/utf8>> -> + [<<"flv"/utf8>>]; + + <<"video/x-m4v"/utf8>> -> + [<<"m4v"/utf8>>]; + + <<"video/x-matroska"/utf8>> -> + [<<"mkv"/utf8>>, <<"mk3d"/utf8>>, <<"mks"/utf8>>]; + + <<"video/x-mng"/utf8>> -> + [<<"mng"/utf8>>]; + + <<"video/x-ms-asf"/utf8>> -> + [<<"asf"/utf8>>, <<"asx"/utf8>>]; + + <<"video/x-ms-vob"/utf8>> -> + [<<"vob"/utf8>>]; + + <<"video/x-ms-wm"/utf8>> -> + [<<"wm"/utf8>>]; + + <<"video/x-ms-wmv"/utf8>> -> + [<<"wmv"/utf8>>]; + + <<"video/x-ms-wmx"/utf8>> -> + [<<"wmx"/utf8>>]; + + <<"video/x-ms-wvx"/utf8>> -> + [<<"wvx"/utf8>>]; + + <<"video/x-msvideo"/utf8>> -> + [<<"avi"/utf8>>]; + + <<"video/x-sgi-movie"/utf8>> -> + [<<"movie"/utf8>>]; + + <<"video/x-smv"/utf8>> -> + [<<"smv"/utf8>>]; + + <<"x-conference/x-cooltalk"/utf8>> -> + [<<"ice"/utf8>>]; + + _ -> + [] + end. diff --git a/examples/http_interactions/build/packages/marceau/src/marceau.gleam b/examples/http_interactions/build/packages/marceau/src/marceau.gleam new file mode 100644 index 0000000..e7b2b1b --- /dev/null +++ b/examples/http_interactions/build/packages/marceau/src/marceau.gleam @@ -0,0 +1,1806 @@ +pub fn extension_to_mime_type(extension: String) -> String { + case extension { + "123" -> "application/vnd.lotus-1-2-3" + "3dml" -> "text/vnd.in3d.3dml" + "3ds" -> "image/x-3ds" + "3g2" -> "video/3gpp2" + "3gp" -> "video/3gpp" + "7z" -> "application/x-7z-compressed" + "aab" -> "application/x-authorware-bin" + "aac" -> "audio/x-aac" + "aam" -> "application/x-authorware-map" + "aas" -> "application/x-authorware-seg" + "abw" -> "application/x-abiword" + "ac" -> "application/pkix-attr-cert" + "acc" -> "application/vnd.americandynamics.acc" + "ace" -> "application/x-ace-compressed" + "acu" -> "application/vnd.acucobol" + "acutc" -> "application/vnd.acucorp" + "adp" -> "audio/adpcm" + "aep" -> "application/vnd.audiograph" + "afm" -> "application/x-font-type1" + "afp" -> "application/vnd.ibm.modcap" + "ahead" -> "application/vnd.ahead.space" + "ai" -> "application/postscript" + "aif" -> "audio/x-aiff" + "aifc" -> "audio/x-aiff" + "aiff" -> "audio/x-aiff" + "air" -> "application/vnd.adobe.air-application-installer-package+zip" + "ait" -> "application/vnd.dvb.ait" + "ami" -> "application/vnd.amiga.ami" + "apk" -> "application/vnd.android.package-archive" + "appcache" -> "text/cache-manifest" + "application" -> "application/x-ms-application" + "apr" -> "application/vnd.lotus-approach" + "arc" -> "application/x-freearc" + "asc" -> "application/pgp-signature" + "asf" -> "video/x-ms-asf" + "asm" -> "text/x-asm" + "aso" -> "application/vnd.accpac.simply.aso" + "asx" -> "video/x-ms-asf" + "atc" -> "application/vnd.acucorp" + "atom" -> "application/atom+xml" + "atomcat" -> "application/atomcat+xml" + "atomsvc" -> "application/atomsvc+xml" + "atx" -> "application/vnd.antix.game-component" + "au" -> "audio/basic" + "avi" -> "video/x-msvideo" + "avif" -> "image/avif" + "aw" -> "application/applixware" + "azf" -> "application/vnd.airzip.filesecure.azf" + "azs" -> "application/vnd.airzip.filesecure.azs" + "azw" -> "application/vnd.amazon.ebook" + "bat" -> "application/x-msdownload" + "bcpio" -> "application/x-bcpio" + "bdf" -> "application/x-font-bdf" + "bdm" -> "application/vnd.syncml.dm+wbxml" + "bed" -> "application/vnd.realvnc.bed" + "bh2" -> "application/vnd.fujitsu.oasysprs" + "bin" -> "application/octet-stream" + "blb" -> "application/x-blorb" + "blorb" -> "application/x-blorb" + "bmi" -> "application/vnd.bmi" + "bmp" -> "image/bmp" + "book" -> "application/vnd.framemaker" + "box" -> "application/vnd.previewsystems.box" + "boz" -> "application/x-bzip2" + "bpk" -> "application/octet-stream" + "btif" -> "image/prs.btif" + "bz" -> "application/x-bzip" + "bz2" -> "application/x-bzip2" + "c" -> "text/x-c" + "c11amc" -> "application/vnd.cluetrust.cartomobile-config" + "c11amz" -> "application/vnd.cluetrust.cartomobile-config-pkg" + "c4d" -> "application/vnd.clonk.c4group" + "c4f" -> "application/vnd.clonk.c4group" + "c4g" -> "application/vnd.clonk.c4group" + "c4p" -> "application/vnd.clonk.c4group" + "c4u" -> "application/vnd.clonk.c4group" + "cab" -> "application/vnd.ms-cab-compressed" + "caf" -> "audio/x-caf" + "cap" -> "application/vnd.tcpdump.pcap" + "car" -> "application/vnd.curl.car" + "cat" -> "application/vnd.ms-pki.seccat" + "cb7" -> "application/x-cbr" + "cba" -> "application/x-cbr" + "cbr" -> "application/x-cbr" + "cbt" -> "application/x-cbr" + "cbz" -> "application/x-cbr" + "cc" -> "text/x-c" + "cct" -> "application/x-director" + "ccxml" -> "application/ccxml+xml" + "cdbcmsg" -> "application/vnd.contact.cmsg" + "cdf" -> "application/x-netcdf" + "cdkey" -> "application/vnd.mediastation.cdkey" + "cdmia" -> "application/cdmi-capability" + "cdmic" -> "application/cdmi-container" + "cdmid" -> "application/cdmi-domain" + "cdmio" -> "application/cdmi-object" + "cdmiq" -> "application/cdmi-queue" + "cdx" -> "chemical/x-cdx" + "cdxml" -> "application/vnd.chemdraw+xml" + "cdy" -> "application/vnd.cinderella" + "cer" -> "application/pkix-cert" + "cfs" -> "application/x-cfs-compressed" + "cgm" -> "image/cgm" + "chat" -> "application/x-chat" + "chm" -> "application/vnd.ms-htmlhelp" + "chrt" -> "application/vnd.kde.kchart" + "cif" -> "chemical/x-cif" + "cii" -> "application/vnd.anser-web-certificate-issue-initiation" + "cil" -> "application/vnd.ms-artgalry" + "cla" -> "application/vnd.claymore" + "class" -> "application/java-vm" + "clkk" -> "application/vnd.crick.clicker.keyboard" + "clkp" -> "application/vnd.crick.clicker.palette" + "clkt" -> "application/vnd.crick.clicker.template" + "clkw" -> "application/vnd.crick.clicker.wordbank" + "clkx" -> "application/vnd.crick.clicker" + "clp" -> "application/x-msclip" + "cmc" -> "application/vnd.cosmocaller" + "cmdf" -> "chemical/x-cmdf" + "cml" -> "chemical/x-cml" + "cmp" -> "application/vnd.yellowriver-custom-menu" + "cmx" -> "image/x-cmx" + "cod" -> "application/vnd.rim.cod" + "com" -> "application/x-msdownload" + "conf" -> "text/plain" + "cpio" -> "application/x-cpio" + "cpp" -> "text/x-c" + "cpt" -> "application/mac-compactpro" + "crd" -> "application/x-mscardfile" + "crl" -> "application/pkix-crl" + "crt" -> "application/x-x509-ca-cert" + "cryptonote" -> "application/vnd.rig.cryptonote" + "csh" -> "application/x-csh" + "csml" -> "chemical/x-csml" + "csp" -> "application/vnd.commonspace" + "css" -> "text/css" + "cst" -> "application/x-director" + "csv" -> "text/csv" + "cu" -> "application/cu-seeme" + "curl" -> "text/vnd.curl" + "cww" -> "application/prs.cww" + "cxt" -> "application/x-director" + "cxx" -> "text/x-c" + "dae" -> "model/vnd.collada+xml" + "daf" -> "application/vnd.mobius.daf" + "dart" -> "application/vnd.dart" + "dataless" -> "application/vnd.fdsn.seed" + "davmount" -> "application/davmount+xml" + "dbk" -> "application/docbook+xml" + "dcr" -> "application/x-director" + "dcurl" -> "text/vnd.curl.dcurl" + "dd2" -> "application/vnd.oma.dd2+xml" + "ddd" -> "application/vnd.fujixerox.ddd" + "deb" -> "application/x-debian-package" + "def" -> "text/plain" + "deploy" -> "application/octet-stream" + "der" -> "application/x-x509-ca-cert" + "dfac" -> "application/vnd.dreamfactory" + "dgc" -> "application/x-dgc-compressed" + "dic" -> "text/x-c" + "dir" -> "application/x-director" + "dis" -> "application/vnd.mobius.dis" + "dist" -> "application/octet-stream" + "distz" -> "application/octet-stream" + "djv" -> "image/vnd.djvu" + "djvu" -> "image/vnd.djvu" + "dll" -> "application/x-msdownload" + "dmg" -> "application/x-apple-diskimage" + "dmp" -> "application/vnd.tcpdump.pcap" + "dms" -> "application/octet-stream" + "dna" -> "application/vnd.dna" + "doc" -> "application/msword" + "docm" -> "application/vnd.ms-word.document.macroenabled.12" + "docx" -> + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + "dot" -> "application/msword" + "dotm" -> "application/vnd.ms-word.template.macroenabled.12" + "dotx" -> + "application/vnd.openxmlformats-officedocument.wordprocessingml.template" + "dp" -> "application/vnd.osgi.dp" + "dpg" -> "application/vnd.dpgraph" + "dra" -> "audio/vnd.dra" + "dsc" -> "text/prs.lines.tag" + "dssc" -> "application/dssc+der" + "dtb" -> "application/x-dtbook+xml" + "dtd" -> "application/xml-dtd" + "dts" -> "audio/vnd.dts" + "dtshd" -> "audio/vnd.dts.hd" + "dump" -> "application/octet-stream" + "dvb" -> "video/vnd.dvb.file" + "dvi" -> "application/x-dvi" + "dwf" -> "model/vnd.dwf" + "dwg" -> "image/vnd.dwg" + "dxf" -> "image/vnd.dxf" + "dxp" -> "application/vnd.spotfire.dxp" + "dxr" -> "application/x-director" + "ecelp4800" -> "audio/vnd.nuera.ecelp4800" + "ecelp7470" -> "audio/vnd.nuera.ecelp7470" + "ecelp9600" -> "audio/vnd.nuera.ecelp9600" + "ecma" -> "application/ecmascript" + "edm" -> "application/vnd.novadigm.edm" + "edx" -> "application/vnd.novadigm.edx" + "efif" -> "application/vnd.picsel" + "ei6" -> "application/vnd.pg.osasli" + "elc" -> "application/octet-stream" + "emf" -> "application/x-msmetafile" + "eml" -> "message/rfc822" + "emma" -> "application/emma+xml" + "emz" -> "application/x-msmetafile" + "eol" -> "audio/vnd.digital-winds" + "eot" -> "application/vnd.ms-fontobject" + "eps" -> "application/postscript" + "epub" -> "application/epub+zip" + "es3" -> "application/vnd.eszigno3+xml" + "esa" -> "application/vnd.osgi.subsystem" + "esf" -> "application/vnd.epson.esf" + "et3" -> "application/vnd.eszigno3+xml" + "etx" -> "text/x-setext" + "eva" -> "application/x-eva" + "evy" -> "application/x-envoy" + "exe" -> "application/x-msdownload" + "exi" -> "application/exi" + "ext" -> "application/vnd.novadigm.ext" + "ez" -> "application/andrew-inset" + "ez2" -> "application/vnd.ezpix-album" + "ez3" -> "application/vnd.ezpix-package" + "f" -> "text/x-fortran" + "f4v" -> "video/x-f4v" + "f77" -> "text/x-fortran" + "f90" -> "text/x-fortran" + "fbs" -> "image/vnd.fastbidsheet" + "fcdt" -> "application/vnd.adobe.formscentral.fcdt" + "fcs" -> "application/vnd.isac.fcs" + "fdf" -> "application/vnd.fdf" + "fe_launch" -> "application/vnd.denovo.fcselayout-link" + "fg5" -> "application/vnd.fujitsu.oasysgp" + "fgd" -> "application/x-director" + "fh" -> "image/x-freehand" + "fh4" -> "image/x-freehand" + "fh5" -> "image/x-freehand" + "fh7" -> "image/x-freehand" + "fhc" -> "image/x-freehand" + "fig" -> "application/x-xfig" + "flac" -> "audio/x-flac" + "fli" -> "video/x-fli" + "flo" -> "application/vnd.micrografx.flo" + "flv" -> "video/x-flv" + "flw" -> "application/vnd.kde.kivio" + "flx" -> "text/vnd.fmi.flexstor" + "fly" -> "text/vnd.fly" + "fm" -> "application/vnd.framemaker" + "fnc" -> "application/vnd.frogans.fnc" + "for" -> "text/x-fortran" + "fpx" -> "image/vnd.fpx" + "frame" -> "application/vnd.framemaker" + "fsc" -> "application/vnd.fsc.weblaunch" + "fst" -> "image/vnd.fst" + "ftc" -> "application/vnd.fluxtime.clip" + "fti" -> "application/vnd.anser-web-funds-transfer-initiation" + "fvt" -> "video/vnd.fvt" + "fxp" -> "application/vnd.adobe.fxp" + "fxpl" -> "application/vnd.adobe.fxp" + "fzs" -> "application/vnd.fuzzysheet" + "g2w" -> "application/vnd.geoplan" + "g3" -> "image/g3fax" + "g3w" -> "application/vnd.geospace" + "gac" -> "application/vnd.groove-account" + "gam" -> "application/x-tads" + "gbr" -> "application/rpki-ghostbusters" + "gca" -> "application/x-gca-compressed" + "gdl" -> "model/vnd.gdl" + "geo" -> "application/vnd.dynageo" + "gex" -> "application/vnd.geometry-explorer" + "ggb" -> "application/vnd.geogebra.file" + "ggs" -> "application/vnd.geogebra.slides" + "ggt" -> "application/vnd.geogebra.tool" + "ghf" -> "application/vnd.groove-help" + "gif" -> "image/gif" + "gim" -> "application/vnd.groove-identity-message" + "gml" -> "application/gml+xml" + "gmx" -> "application/vnd.gmx" + "gnumeric" -> "application/x-gnumeric" + "gph" -> "application/vnd.flographit" + "gpx" -> "application/gpx+xml" + "gqf" -> "application/vnd.grafeq" + "gqs" -> "application/vnd.grafeq" + "gram" -> "application/srgs" + "gramps" -> "application/x-gramps-xml" + "gre" -> "application/vnd.geometry-explorer" + "grv" -> "application/vnd.groove-injector" + "grxml" -> "application/srgs+xml" + "gsf" -> "application/x-font-ghostscript" + "gtar" -> "application/x-gtar" + "gtm" -> "application/vnd.groove-tool-message" + "gtw" -> "model/vnd.gtw" + "gv" -> "text/vnd.graphviz" + "gxf" -> "application/gxf" + "gxt" -> "application/vnd.geonext" + "h" -> "text/x-c" + "h261" -> "video/h261" + "h263" -> "video/h263" + "h264" -> "video/h264" + "hal" -> "application/vnd.hal+xml" + "hbci" -> "application/vnd.hbci" + "hdf" -> "application/x-hdf" + "hh" -> "text/x-c" + "hlp" -> "application/winhlp" + "hpgl" -> "application/vnd.hp-hpgl" + "hpid" -> "application/vnd.hp-hpid" + "hps" -> "application/vnd.hp-hps" + "hqx" -> "application/mac-binhex40" + "htke" -> "application/vnd.kenameaapp" + "htm" -> "text/html" + "html" -> "text/html" + "hvd" -> "application/vnd.yamaha.hv-dic" + "hvp" -> "application/vnd.yamaha.hv-voice" + "hvs" -> "application/vnd.yamaha.hv-script" + "i2g" -> "application/vnd.intergeo" + "icc" -> "application/vnd.iccprofile" + "ice" -> "x-conference/x-cooltalk" + "icm" -> "application/vnd.iccprofile" + "ico" -> "image/x-icon" + "ics" -> "text/calendar" + "ief" -> "image/ief" + "ifb" -> "text/calendar" + "ifm" -> "application/vnd.shana.informed.formdata" + "iges" -> "model/iges" + "igl" -> "application/vnd.igloader" + "igm" -> "application/vnd.insors.igm" + "igs" -> "model/iges" + "igx" -> "application/vnd.micrografx.igx" + "iif" -> "application/vnd.shana.informed.interchange" + "imp" -> "application/vnd.accpac.simply.imp" + "ims" -> "application/vnd.ms-ims" + "in" -> "text/plain" + "ink" -> "application/inkml+xml" + "inkml" -> "application/inkml+xml" + "install" -> "application/x-install-instructions" + "iota" -> "application/vnd.astraea-software.iota" + "ipfix" -> "application/ipfix" + "ipk" -> "application/vnd.shana.informed.package" + "irm" -> "application/vnd.ibm.rights-management" + "irp" -> "application/vnd.irepository.package+xml" + "iso" -> "application/x-iso9660-image" + "itp" -> "application/vnd.shana.informed.formtemplate" + "ivp" -> "application/vnd.immervision-ivp" + "ivu" -> "application/vnd.immervision-ivu" + "jad" -> "text/vnd.sun.j2me.app-descriptor" + "jam" -> "application/vnd.jam" + "jar" -> "application/java-archive" + "java" -> "text/x-java-source" + "jisp" -> "application/vnd.jisp" + "jlt" -> "application/vnd.hp-jlyt" + "jnlp" -> "application/x-java-jnlp-file" + "joda" -> "application/vnd.joost.joda-archive" + "jpe" -> "image/jpeg" + "jpeg" -> "image/jpeg" + "jpg" -> "image/jpeg" + "jpgm" -> "video/jpm" + "jpgv" -> "video/jpeg" + "jpm" -> "video/jpm" + "js" -> "text/javascript" + "json" -> "application/json" + "jsonml" -> "application/jsonml+json" + "jxl" -> "image/jxl" + "kar" -> "audio/midi" + "karbon" -> "application/vnd.kde.karbon" + "kfo" -> "application/vnd.kde.kformula" + "kia" -> "application/vnd.kidspiration" + "kml" -> "application/vnd.google-earth.kml+xml" + "kmz" -> "application/vnd.google-earth.kmz" + "kne" -> "application/vnd.kinar" + "knp" -> "application/vnd.kinar" + "kon" -> "application/vnd.kde.kontour" + "kpr" -> "application/vnd.kde.kpresenter" + "kpt" -> "application/vnd.kde.kpresenter" + "kpxx" -> "application/vnd.ds-keypoint" + "ksp" -> "application/vnd.kde.kspread" + "ktr" -> "application/vnd.kahootz" + "ktx" -> "image/ktx" + "ktz" -> "application/vnd.kahootz" + "kwd" -> "application/vnd.kde.kword" + "kwt" -> "application/vnd.kde.kword" + "lasxml" -> "application/vnd.las.las+xml" + "latex" -> "application/x-latex" + "lbd" -> "application/vnd.llamagraphics.life-balance.desktop" + "lbe" -> "application/vnd.llamagraphics.life-balance.exchange+xml" + "les" -> "application/vnd.hhe.lesson-player" + "lha" -> "application/x-lzh-compressed" + "link66" -> "application/vnd.route66.link66+xml" + "list" -> "text/plain" + "list3820" -> "application/vnd.ibm.modcap" + "listafp" -> "application/vnd.ibm.modcap" + "lnk" -> "application/x-ms-shortcut" + "log" -> "text/plain" + "lostxml" -> "application/lost+xml" + "lrf" -> "application/octet-stream" + "lrm" -> "application/vnd.ms-lrm" + "ltf" -> "application/vnd.frogans.ltf" + "lvp" -> "audio/vnd.lucent.voice" + "lwp" -> "application/vnd.lotus-wordpro" + "lzh" -> "application/x-lzh-compressed" + "m13" -> "application/x-msmediaview" + "m14" -> "application/x-msmediaview" + "m1v" -> "video/mpeg" + "m21" -> "application/mp21" + "m2a" -> "audio/mpeg" + "m2t" -> "video/mp2t" + "m2ts" -> "video/mp2t" + "m2v" -> "video/mpeg" + "m3a" -> "audio/mpeg" + "m3u" -> "audio/x-mpegurl" + "m3u8" -> "application/vnd.apple.mpegurl" + "m4a" -> "audio/mp4" + "m4u" -> "video/vnd.mpegurl" + "m4v" -> "video/x-m4v" + "ma" -> "application/mathematica" + "mads" -> "application/mads+xml" + "mag" -> "application/vnd.ecowin.chart" + "maker" -> "application/vnd.framemaker" + "man" -> "text/troff" + "mar" -> "application/octet-stream" + "mathml" -> "application/mathml+xml" + "mb" -> "application/mathematica" + "mbk" -> "application/vnd.mobius.mbk" + "mbox" -> "application/mbox" + "mc1" -> "application/vnd.medcalcdata" + "mcd" -> "application/vnd.mcd" + "mcurl" -> "text/vnd.curl.mcurl" + "mdb" -> "application/x-msaccess" + "mdi" -> "image/vnd.ms-modi" + "me" -> "text/troff" + "mesh" -> "model/mesh" + "meta4" -> "application/metalink4+xml" + "metalink" -> "application/metalink+xml" + "mets" -> "application/mets+xml" + "mfm" -> "application/vnd.mfmp" + "mft" -> "application/rpki-manifest" + "mgp" -> "application/vnd.osgeo.mapguide.package" + "mgz" -> "application/vnd.proteus.magazine" + "mid" -> "audio/midi" + "midi" -> "audio/midi" + "mie" -> "application/x-mie" + "mif" -> "application/vnd.mif" + "mime" -> "message/rfc822" + "mj2" -> "video/mj2" + "mjp2" -> "video/mj2" + "mjs" -> "text/javascript" + "mk3d" -> "video/x-matroska" + "mka" -> "audio/x-matroska" + "mks" -> "video/x-matroska" + "mkv" -> "video/x-matroska" + "mlp" -> "application/vnd.dolby.mlp" + "mmd" -> "application/vnd.chipnuts.karaoke-mmd" + "mmf" -> "application/vnd.smaf" + "mmr" -> "image/vnd.fujixerox.edmics-mmr" + "mng" -> "video/x-mng" + "mny" -> "application/x-msmoney" + "mobi" -> "application/x-mobipocket-ebook" + "mods" -> "application/mods+xml" + "mov" -> "video/quicktime" + "movie" -> "video/x-sgi-movie" + "mp2" -> "audio/mpeg" + "mp21" -> "application/mp21" + "mp2a" -> "audio/mpeg" + "mp3" -> "audio/mpeg" + "mp4" -> "video/mp4" + "mp4a" -> "audio/mp4" + "mp4s" -> "application/mp4" + "mp4v" -> "video/mp4" + "mpc" -> "application/vnd.mophun.certificate" + "mpe" -> "video/mpeg" + "mpeg" -> "video/mpeg" + "mpg" -> "video/mpeg" + "mpg4" -> "video/mp4" + "mpga" -> "audio/mpeg" + "mpkg" -> "application/vnd.apple.installer+xml" + "mpm" -> "application/vnd.blueice.multipass" + "mpn" -> "application/vnd.mophun.application" + "mpp" -> "application/vnd.ms-project" + "mpt" -> "application/vnd.ms-project" + "mpy" -> "application/vnd.ibm.minipay" + "mqy" -> "application/vnd.mobius.mqy" + "mrc" -> "application/marc" + "mrcx" -> "application/marcxml+xml" + "ms" -> "text/troff" + "mscml" -> "application/mediaservercontrol+xml" + "mseed" -> "application/vnd.fdsn.mseed" + "mseq" -> "application/vnd.mseq" + "msf" -> "application/vnd.epson.msf" + "msh" -> "model/mesh" + "msi" -> "application/x-msdownload" + "msl" -> "application/vnd.mobius.msl" + "msty" -> "application/vnd.muvee.style" + "mts" -> "video/mp2t" + "mus" -> "application/vnd.musician" + "musicxml" -> "application/vnd.recordare.musicxml+xml" + "mvb" -> "application/x-msmediaview" + "mwf" -> "application/vnd.mfer" + "mxf" -> "application/mxf" + "mxl" -> "application/vnd.recordare.musicxml" + "mxml" -> "application/xv+xml" + "mxs" -> "application/vnd.triscape.mxs" + "mxu" -> "video/vnd.mpegurl" + "n-gage" -> "application/vnd.nokia.n-gage.symbian.install" + "n3" -> "text/n3" + "nb" -> "application/mathematica" + "nbp" -> "application/vnd.wolfram.player" + "nc" -> "application/x-netcdf" + "ncx" -> "application/x-dtbncx+xml" + "nfo" -> "text/x-nfo" + "ngdat" -> "application/vnd.nokia.n-gage.data" + "nitf" -> "application/vnd.nitf" + "nlu" -> "application/vnd.neurolanguage.nlu" + "nml" -> "application/vnd.enliven" + "nnd" -> "application/vnd.noblenet-directory" + "nns" -> "application/vnd.noblenet-sealer" + "nnw" -> "application/vnd.noblenet-web" + "npx" -> "image/vnd.net-fpx" + "nsc" -> "application/x-conference" + "nsf" -> "application/vnd.lotus-notes" + "ntf" -> "application/vnd.nitf" + "nzb" -> "application/x-nzb" + "oa2" -> "application/vnd.fujitsu.oasys2" + "oa3" -> "application/vnd.fujitsu.oasys3" + "oas" -> "application/vnd.fujitsu.oasys" + "obd" -> "application/x-msbinder" + "obj" -> "application/x-tgif" + "oda" -> "application/oda" + "odb" -> "application/vnd.oasis.opendocument.database" + "odc" -> "application/vnd.oasis.opendocument.chart" + "odf" -> "application/vnd.oasis.opendocument.formula" + "odft" -> "application/vnd.oasis.opendocument.formula-template" + "odg" -> "application/vnd.oasis.opendocument.graphics" + "odi" -> "application/vnd.oasis.opendocument.image" + "odm" -> "application/vnd.oasis.opendocument.text-master" + "odp" -> "application/vnd.oasis.opendocument.presentation" + "ods" -> "application/vnd.oasis.opendocument.spreadsheet" + "odt" -> "application/vnd.oasis.opendocument.text" + "oga" -> "audio/ogg" + "ogg" -> "audio/ogg" + "ogv" -> "video/ogg" + "ogx" -> "application/ogg" + "omdoc" -> "application/omdoc+xml" + "onepkg" -> "application/onenote" + "onetmp" -> "application/onenote" + "onetoc" -> "application/onenote" + "onetoc2" -> "application/onenote" + "opf" -> "application/oebps-package+xml" + "opml" -> "text/x-opml" + "oprc" -> "application/vnd.palm" + "opus" -> "audio/ogg" + "org" -> "application/vnd.lotus-organizer" + "osf" -> "application/vnd.yamaha.openscoreformat" + "osfpvg" -> "application/vnd.yamaha.openscoreformat.osfpvg+xml" + "otc" -> "application/vnd.oasis.opendocument.chart-template" + "otf" -> "font/otf" + "otg" -> "application/vnd.oasis.opendocument.graphics-template" + "oth" -> "application/vnd.oasis.opendocument.text-web" + "oti" -> "application/vnd.oasis.opendocument.image-template" + "otp" -> "application/vnd.oasis.opendocument.presentation-template" + "ots" -> "application/vnd.oasis.opendocument.spreadsheet-template" + "ott" -> "application/vnd.oasis.opendocument.text-template" + "oxps" -> "application/oxps" + "oxt" -> "application/vnd.openofficeorg.extension" + "p" -> "text/x-pascal" + "p10" -> "application/pkcs10" + "p12" -> "application/x-pkcs12" + "p7b" -> "application/x-pkcs7-certificates" + "p7c" -> "application/pkcs7-mime" + "p7m" -> "application/pkcs7-mime" + "p7r" -> "application/x-pkcs7-certreqresp" + "p7s" -> "application/pkcs7-signature" + "p8" -> "application/pkcs8" + "pas" -> "text/x-pascal" + "paw" -> "application/vnd.pawaafile" + "pbd" -> "application/vnd.powerbuilder6" + "pbm" -> "image/x-portable-bitmap" + "pcap" -> "application/vnd.tcpdump.pcap" + "pcf" -> "application/x-font-pcf" + "pcl" -> "application/vnd.hp-pcl" + "pclxl" -> "application/vnd.hp-pclxl" + "pct" -> "image/x-pict" + "pcurl" -> "application/vnd.curl.pcurl" + "pcx" -> "image/x-pcx" + "pdb" -> "application/vnd.palm" + "pdf" -> "application/pdf" + "pfa" -> "application/x-font-type1" + "pfb" -> "application/x-font-type1" + "pfm" -> "application/x-font-type1" + "pfr" -> "application/font-tdpfr" + "pfx" -> "application/x-pkcs12" + "pgm" -> "image/x-portable-graymap" + "pgn" -> "application/x-chess-pgn" + "pgp" -> "application/pgp-encrypted" + "pic" -> "image/x-pict" + "pkg" -> "application/octet-stream" + "pki" -> "application/pkixcmp" + "pkipath" -> "application/pkix-pkipath" + "plb" -> "application/vnd.3gpp.pic-bw-large" + "plc" -> "application/vnd.mobius.plc" + "plf" -> "application/vnd.pocketlearn" + "pls" -> "application/pls+xml" + "pml" -> "application/vnd.ctc-posml" + "png" -> "image/png" + "pnm" -> "image/x-portable-anymap" + "portpkg" -> "application/vnd.macports.portpkg" + "pot" -> "application/vnd.ms-powerpoint" + "potm" -> "application/vnd.ms-powerpoint.template.macroenabled.12" + "potx" -> + "application/vnd.openxmlformats-officedocument.presentationml.template" + "ppam" -> "application/vnd.ms-powerpoint.addin.macroenabled.12" + "ppd" -> "application/vnd.cups-ppd" + "ppm" -> "image/x-portable-pixmap" + "pps" -> "application/vnd.ms-powerpoint" + "ppsm" -> "application/vnd.ms-powerpoint.slideshow.macroenabled.12" + "ppsx" -> + "application/vnd.openxmlformats-officedocument.presentationml.slideshow" + "ppt" -> "application/vnd.ms-powerpoint" + "pptm" -> "application/vnd.ms-powerpoint.presentation.macroenabled.12" + "pptx" -> + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + "pqa" -> "application/vnd.palm" + "prc" -> "application/x-mobipocket-ebook" + "pre" -> "application/vnd.lotus-freelance" + "prf" -> "application/pics-rules" + "ps" -> "application/postscript" + "psb" -> "application/vnd.3gpp.pic-bw-small" + "psd" -> "image/vnd.adobe.photoshop" + "psf" -> "application/x-font-linux-psf" + "pskcxml" -> "application/pskc+xml" + "ptid" -> "application/vnd.pvi.ptid1" + "pub" -> "application/x-mspublisher" + "pvb" -> "application/vnd.3gpp.pic-bw-var" + "pwn" -> "application/vnd.3m.post-it-notes" + "pya" -> "audio/vnd.ms-playready.media.pya" + "pyv" -> "video/vnd.ms-playready.media.pyv" + "qam" -> "application/vnd.epson.quickanime" + "qbo" -> "application/vnd.intu.qbo" + "qfx" -> "application/vnd.intu.qfx" + "qps" -> "application/vnd.publishare-delta-tree" + "qt" -> "video/quicktime" + "qwd" -> "application/vnd.quark.quarkxpress" + "qwt" -> "application/vnd.quark.quarkxpress" + "qxb" -> "application/vnd.quark.quarkxpress" + "qxd" -> "application/vnd.quark.quarkxpress" + "qxl" -> "application/vnd.quark.quarkxpress" + "qxt" -> "application/vnd.quark.quarkxpress" + "ra" -> "audio/x-pn-realaudio" + "ram" -> "audio/x-pn-realaudio" + "rar" -> "application/x-rar-compressed" + "ras" -> "image/x-cmu-raster" + "rcprofile" -> "application/vnd.ipunplugged.rcprofile" + "rdf" -> "application/rdf+xml" + "rdz" -> "application/vnd.data-vision.rdz" + "rep" -> "application/vnd.businessobjects" + "res" -> "application/x-dtbresource+xml" + "rgb" -> "image/x-rgb" + "rif" -> "application/reginfo+xml" + "rip" -> "audio/vnd.rip" + "ris" -> "application/x-research-info-systems" + "rl" -> "application/resource-lists+xml" + "rlc" -> "image/vnd.fujixerox.edmics-rlc" + "rld" -> "application/resource-lists-diff+xml" + "rm" -> "application/vnd.rn-realmedia" + "rmi" -> "audio/midi" + "rmp" -> "audio/x-pn-realaudio-plugin" + "rms" -> "application/vnd.jcp.javame.midlet-rms" + "rmvb" -> "application/vnd.rn-realmedia-vbr" + "rnc" -> "application/relax-ng-compact-syntax" + "roa" -> "application/rpki-roa" + "roff" -> "text/troff" + "rp9" -> "application/vnd.cloanto.rp9" + "rpss" -> "application/vnd.nokia.radio-presets" + "rpst" -> "application/vnd.nokia.radio-preset" + "rq" -> "application/sparql-query" + "rs" -> "application/rls-services+xml" + "rsd" -> "application/rsd+xml" + "rss" -> "application/rss+xml" + "rtf" -> "application/rtf" + "rtx" -> "text/richtext" + "s" -> "text/x-asm" + "s3m" -> "audio/s3m" + "saf" -> "application/vnd.yamaha.smaf-audio" + "sbml" -> "application/sbml+xml" + "sc" -> "application/vnd.ibm.secure-container" + "scd" -> "application/x-msschedule" + "scm" -> "application/vnd.lotus-screencam" + "scq" -> "application/scvp-cv-request" + "scs" -> "application/scvp-cv-response" + "scurl" -> "text/vnd.curl.scurl" + "sda" -> "application/vnd.stardivision.draw" + "sdc" -> "application/vnd.stardivision.calc" + "sdd" -> "application/vnd.stardivision.impress" + "sdkd" -> "application/vnd.solent.sdkm+xml" + "sdkm" -> "application/vnd.solent.sdkm+xml" + "sdp" -> "application/sdp" + "sdw" -> "application/vnd.stardivision.writer" + "see" -> "application/vnd.seemail" + "seed" -> "application/vnd.fdsn.seed" + "sema" -> "application/vnd.sema" + "semd" -> "application/vnd.semd" + "semf" -> "application/vnd.semf" + "ser" -> "application/java-serialized-object" + "setpay" -> "application/set-payment-initiation" + "setreg" -> "application/set-registration-initiation" + "sfd-hdstx" -> "application/vnd.hydrostatix.sof-data" + "sfs" -> "application/vnd.spotfire.sfs" + "sfv" -> "text/x-sfv" + "sgi" -> "image/sgi" + "sgl" -> "application/vnd.stardivision.writer-global" + "sgm" -> "text/sgml" + "sgml" -> "text/sgml" + "sh" -> "application/x-sh" + "shar" -> "application/x-shar" + "shf" -> "application/shf+xml" + "sid" -> "image/x-mrsid-image" + "sig" -> "application/pgp-signature" + "sil" -> "audio/silk" + "silo" -> "model/mesh" + "sis" -> "application/vnd.symbian.install" + "sisx" -> "application/vnd.symbian.install" + "sit" -> "application/x-stuffit" + "sitx" -> "application/x-stuffitx" + "skd" -> "application/vnd.koan" + "skm" -> "application/vnd.koan" + "skp" -> "application/vnd.koan" + "skt" -> "application/vnd.koan" + "sldm" -> "application/vnd.ms-powerpoint.slide.macroenabled.12" + "sldx" -> + "application/vnd.openxmlformats-officedocument.presentationml.slide" + "slt" -> "application/vnd.epson.salt" + "sm" -> "application/vnd.stepmania.stepchart" + "smf" -> "application/vnd.stardivision.math" + "smi" -> "application/smil+xml" + "smil" -> "application/smil+xml" + "smv" -> "video/x-smv" + "smzip" -> "application/vnd.stepmania.package" + "snd" -> "audio/basic" + "snf" -> "application/x-font-snf" + "so" -> "application/octet-stream" + "spc" -> "application/x-pkcs7-certificates" + "spf" -> "application/vnd.yamaha.smaf-phrase" + "spl" -> "application/x-futuresplash" + "spot" -> "text/vnd.in3d.spot" + "spp" -> "application/scvp-vp-response" + "spq" -> "application/scvp-vp-request" + "spx" -> "audio/ogg" + "sql" -> "application/x-sql" + "src" -> "application/x-wais-source" + "srt" -> "application/x-subrip" + "sru" -> "application/sru+xml" + "srx" -> "application/sparql-results+xml" + "ssdl" -> "application/ssdl+xml" + "sse" -> "application/vnd.kodak-descriptor" + "ssf" -> "application/vnd.epson.ssf" + "ssml" -> "application/ssml+xml" + "st" -> "application/vnd.sailingtracker.track" + "stc" -> "application/vnd.sun.xml.calc.template" + "std" -> "application/vnd.sun.xml.draw.template" + "stf" -> "application/vnd.wt.stf" + "sti" -> "application/vnd.sun.xml.impress.template" + "stk" -> "application/hyperstudio" + "stl" -> "application/vnd.ms-pki.stl" + "str" -> "application/vnd.pg.format" + "stw" -> "application/vnd.sun.xml.writer.template" + "sub" -> "image/vnd.dvb.subtitle" + "sus" -> "application/vnd.sus-calendar" + "susp" -> "application/vnd.sus-calendar" + "sv4cpio" -> "application/x-sv4cpio" + "sv4crc" -> "application/x-sv4crc" + "svc" -> "application/vnd.dvb.service" + "svd" -> "application/vnd.svd" + "svg" -> "image/svg+xml" + "svgz" -> "image/svg+xml" + "swa" -> "application/x-director" + "swf" -> "application/x-shockwave-flash" + "swi" -> "application/vnd.aristanetworks.swi" + "sxc" -> "application/vnd.sun.xml.calc" + "sxd" -> "application/vnd.sun.xml.draw" + "sxg" -> "application/vnd.sun.xml.writer.global" + "sxi" -> "application/vnd.sun.xml.impress" + "sxm" -> "application/vnd.sun.xml.math" + "sxw" -> "application/vnd.sun.xml.writer" + "t" -> "text/troff" + "t3" -> "application/x-t3vm-image" + "taglet" -> "application/vnd.mynfc" + "tao" -> "application/vnd.tao.intent-module-archive" + "tar" -> "application/x-tar" + "tcap" -> "application/vnd.3gpp2.tcap" + "tcl" -> "application/x-tcl" + "teacher" -> "application/vnd.smart.teacher" + "tei" -> "application/tei+xml" + "teicorpus" -> "application/tei+xml" + "tex" -> "application/x-tex" + "texi" -> "application/x-texinfo" + "texinfo" -> "application/x-texinfo" + "text" -> "text/plain" + "tfi" -> "application/thraud+xml" + "tfm" -> "application/x-tex-tfm" + "tga" -> "image/x-tga" + "thmx" -> "application/vnd.ms-officetheme" + "tif" -> "image/tiff" + "tiff" -> "image/tiff" + "tmo" -> "application/vnd.tmobile-livetv" + "torrent" -> "application/x-bittorrent" + "tpl" -> "application/vnd.groove-tool-template" + "tpt" -> "application/vnd.trid.tpt" + "tr" -> "text/troff" + "tra" -> "application/vnd.trueapp" + "trm" -> "application/x-msterminal" + "ts" -> "video/mp2t" + "tsd" -> "application/timestamped-data" + "tsv" -> "text/tab-separated-values" + "ttc" -> "font/collection" + "ttf" -> "font/ttf" + "ttl" -> "text/turtle" + "twd" -> "application/vnd.simtech-mindmapper" + "twds" -> "application/vnd.simtech-mindmapper" + "txd" -> "application/vnd.genomatix.tuxedo" + "txf" -> "application/vnd.mobius.txf" + "txt" -> "text/plain" + "u32" -> "application/x-authorware-bin" + "udeb" -> "application/x-debian-package" + "ufd" -> "application/vnd.ufdl" + "ufdl" -> "application/vnd.ufdl" + "ulx" -> "application/x-glulx" + "umj" -> "application/vnd.umajin" + "unityweb" -> "application/vnd.unity" + "uoml" -> "application/vnd.uoml+xml" + "uri" -> "text/uri-list" + "uris" -> "text/uri-list" + "urls" -> "text/uri-list" + "ustar" -> "application/x-ustar" + "utz" -> "application/vnd.uiq.theme" + "uu" -> "text/x-uuencode" + "uva" -> "audio/vnd.dece.audio" + "uvd" -> "application/vnd.dece.data" + "uvf" -> "application/vnd.dece.data" + "uvg" -> "image/vnd.dece.graphic" + "uvh" -> "video/vnd.dece.hd" + "uvi" -> "image/vnd.dece.graphic" + "uvm" -> "video/vnd.dece.mobile" + "uvp" -> "video/vnd.dece.pd" + "uvs" -> "video/vnd.dece.sd" + "uvt" -> "application/vnd.dece.ttml+xml" + "uvu" -> "video/vnd.uvvu.mp4" + "uvv" -> "video/vnd.dece.video" + "uvva" -> "audio/vnd.dece.audio" + "uvvd" -> "application/vnd.dece.data" + "uvvf" -> "application/vnd.dece.data" + "uvvg" -> "image/vnd.dece.graphic" + "uvvh" -> "video/vnd.dece.hd" + "uvvi" -> "image/vnd.dece.graphic" + "uvvm" -> "video/vnd.dece.mobile" + "uvvp" -> "video/vnd.dece.pd" + "uvvs" -> "video/vnd.dece.sd" + "uvvt" -> "application/vnd.dece.ttml+xml" + "uvvu" -> "video/vnd.uvvu.mp4" + "uvvv" -> "video/vnd.dece.video" + "uvvx" -> "application/vnd.dece.unspecified" + "uvvz" -> "application/vnd.dece.zip" + "uvx" -> "application/vnd.dece.unspecified" + "uvz" -> "application/vnd.dece.zip" + "vcard" -> "text/vcard" + "vcd" -> "application/x-cdlink" + "vcf" -> "text/x-vcard" + "vcg" -> "application/vnd.groove-vcard" + "vcs" -> "text/x-vcalendar" + "vcx" -> "application/vnd.vcx" + "vis" -> "application/vnd.visionary" + "viv" -> "video/vnd.vivo" + "vob" -> "video/x-ms-vob" + "vor" -> "application/vnd.stardivision.writer" + "vox" -> "application/x-authorware-bin" + "vrml" -> "model/vrml" + "vsd" -> "application/vnd.visio" + "vsf" -> "application/vnd.vsf" + "vss" -> "application/vnd.visio" + "vst" -> "application/vnd.visio" + "vsw" -> "application/vnd.visio" + "vtu" -> "model/vnd.vtu" + "vxml" -> "application/voicexml+xml" + "w3d" -> "application/x-director" + "wad" -> "application/x-doom" + "wav" -> "audio/x-wav" + "wasm" -> "application/wasm" + "wax" -> "audio/x-ms-wax" + "wbmp" -> "image/vnd.wap.wbmp" + "wbs" -> "application/vnd.criticaltools.wbs+xml" + "wbxml" -> "application/vnd.wap.wbxml" + "wcm" -> "application/vnd.ms-works" + "wdb" -> "application/vnd.ms-works" + "wdp" -> "image/vnd.ms-photo" + "weba" -> "audio/webm" + "webm" -> "video/webm" + "webp" -> "image/webp" + "wg" -> "application/vnd.pmi.widget" + "wgt" -> "application/widget" + "wks" -> "application/vnd.ms-works" + "wm" -> "video/x-ms-wm" + "wma" -> "audio/x-ms-wma" + "wmd" -> "application/x-ms-wmd" + "wmf" -> "application/x-msmetafile" + "wml" -> "text/vnd.wap.wml" + "wmlc" -> "application/vnd.wap.wmlc" + "wmls" -> "text/vnd.wap.wmlscript" + "wmlsc" -> "application/vnd.wap.wmlscriptc" + "wmv" -> "video/x-ms-wmv" + "wmx" -> "video/x-ms-wmx" + "wmz" -> "application/x-msmetafile" + "woff" -> "font/woff" + "woff2" -> "font/woff2" + "wpd" -> "application/vnd.wordperfect" + "wpl" -> "application/vnd.ms-wpl" + "wps" -> "application/vnd.ms-works" + "wqd" -> "application/vnd.wqd" + "wri" -> "application/x-mswrite" + "wrl" -> "model/vrml" + "wsdl" -> "application/wsdl+xml" + "wspolicy" -> "application/wspolicy+xml" + "wtb" -> "application/vnd.webturbo" + "wvx" -> "video/x-ms-wvx" + "x32" -> "application/x-authorware-bin" + "x3d" -> "model/x3d+xml" + "x3db" -> "model/x3d+binary" + "x3dbz" -> "model/x3d+binary" + "x3dv" -> "model/x3d+vrml" + "x3dvz" -> "model/x3d+vrml" + "x3dz" -> "model/x3d+xml" + "xaml" -> "application/xaml+xml" + "xap" -> "application/x-silverlight-app" + "xar" -> "application/vnd.xara" + "xbap" -> "application/x-ms-xbap" + "xbd" -> "application/vnd.fujixerox.docuworks.binder" + "xbm" -> "image/x-xbitmap" + "xdf" -> "application/xcap-diff+xml" + "xdm" -> "application/vnd.syncml.dm+xml" + "xdp" -> "application/vnd.adobe.xdp+xml" + "xdssc" -> "application/dssc+xml" + "xdw" -> "application/vnd.fujixerox.docuworks" + "xenc" -> "application/xenc+xml" + "xer" -> "application/patch-ops-error+xml" + "xfdf" -> "application/vnd.adobe.xfdf" + "xfdl" -> "application/vnd.xfdl" + "xht" -> "application/xhtml+xml" + "xhtml" -> "application/xhtml+xml" + "xhvml" -> "application/xv+xml" + "xif" -> "image/vnd.xiff" + "xla" -> "application/vnd.ms-excel" + "xlam" -> "application/vnd.ms-excel.addin.macroenabled.12" + "xlc" -> "application/vnd.ms-excel" + "xlf" -> "application/x-xliff+xml" + "xlm" -> "application/vnd.ms-excel" + "xls" -> "application/vnd.ms-excel" + "xlsb" -> "application/vnd.ms-excel.sheet.binary.macroenabled.12" + "xlsm" -> "application/vnd.ms-excel.sheet.macroenabled.12" + "xlsx" -> + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + "xlt" -> "application/vnd.ms-excel" + "xltm" -> "application/vnd.ms-excel.template.macroenabled.12" + "xltx" -> + "application/vnd.openxmlformats-officedocument.spreadsheetml.template" + "xlw" -> "application/vnd.ms-excel" + "xm" -> "audio/xm" + "xml" -> "application/xml" + "xo" -> "application/vnd.olpc-sugar" + "xop" -> "application/xop+xml" + "xpi" -> "application/x-xpinstall" + "xpl" -> "application/xproc+xml" + "xpm" -> "image/x-xpixmap" + "xpr" -> "application/vnd.is-xpr" + "xps" -> "application/vnd.ms-xpsdocument" + "xpw" -> "application/vnd.intercon.formnet" + "xpx" -> "application/vnd.intercon.formnet" + "xsl" -> "application/xml" + "xslt" -> "application/xslt+xml" + "xsm" -> "application/vnd.syncml+xml" + "xspf" -> "application/xspf+xml" + "xul" -> "application/vnd.mozilla.xul+xml" + "xvm" -> "application/xv+xml" + "xvml" -> "application/xv+xml" + "xwd" -> "image/x-xwindowdump" + "xyz" -> "chemical/x-xyz" + "xz" -> "application/x-xz" + "yang" -> "application/yang" + "yin" -> "application/yin+xml" + "z1" -> "application/x-zmachine" + "z2" -> "application/x-zmachine" + "z3" -> "application/x-zmachine" + "z4" -> "application/x-zmachine" + "z5" -> "application/x-zmachine" + "z6" -> "application/x-zmachine" + "z7" -> "application/x-zmachine" + "z8" -> "application/x-zmachine" + "zaz" -> "application/vnd.zzazz.deck+xml" + "zip" -> "application/zip" + "zir" -> "application/vnd.zul" + "zirz" -> "application/vnd.zul" + "zmm" -> "application/vnd.handheld-entertainment+xml" + _ -> "application/octet-stream" + } +} + +pub fn mime_type_to_extensions(mime_type: String) -> List(String) { + case mime_type { + "application/andrew-inset" -> ["ez"] + "application/applixware" -> ["aw"] + "application/atom+xml" -> ["atom"] + "application/atomcat+xml" -> ["atomcat"] + "application/atomsvc+xml" -> ["atomsvc"] + "application/ccxml+xml" -> ["ccxml"] + "application/cdmi-capability" -> ["cdmia"] + "application/cdmi-container" -> ["cdmic"] + "application/cdmi-domain" -> ["cdmid"] + "application/cdmi-object" -> ["cdmio"] + "application/cdmi-queue" -> ["cdmiq"] + "application/cu-seeme" -> ["cu"] + "application/davmount+xml" -> ["davmount"] + "application/docbook+xml" -> ["dbk"] + "application/dssc+der" -> ["dssc"] + "application/dssc+xml" -> ["xdssc"] + "application/ecmascript" -> ["ecma"] + "application/emma+xml" -> ["emma"] + "application/epub+zip" -> ["epub"] + "application/exi" -> ["exi"] + "application/font-tdpfr" -> ["pfr"] + "application/gml+xml" -> ["gml"] + "application/gpx+xml" -> ["gpx"] + "application/gxf" -> ["gxf"] + "application/hyperstudio" -> ["stk"] + "application/inkml+xml" -> ["ink", "inkml"] + "application/ipfix" -> ["ipfix"] + "application/java-archive" -> ["jar"] + "application/java-serialized-object" -> ["ser"] + "application/java-vm" -> ["class"] + "application/javascript" -> ["js", "mjs"] + "application/json" -> ["json"] + "application/jsonml+json" -> ["jsonml"] + "application/lost+xml" -> ["lostxml"] + "application/mac-binhex40" -> ["hqx"] + "application/mac-compactpro" -> ["cpt"] + "application/mads+xml" -> ["mads"] + "application/marc" -> ["mrc"] + "application/marcxml+xml" -> ["mrcx"] + "application/mathematica" -> ["ma", "nb", "mb"] + "application/mathml+xml" -> ["mathml"] + "application/mbox" -> ["mbox"] + "application/mediaservercontrol+xml" -> ["mscml"] + "application/metalink+xml" -> ["metalink"] + "application/metalink4+xml" -> ["meta4"] + "application/mets+xml" -> ["mets"] + "application/mods+xml" -> ["mods"] + "application/mp21" -> ["m21", "mp21"] + "application/mp4" -> ["mp4s"] + "application/msword" -> ["doc", "dot"] + "application/mxf" -> ["mxf"] + "application/octet-stream" -> [ + "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", + "elc", "deploy", + ] + "application/oda" -> ["oda"] + "application/oebps-package+xml" -> ["opf"] + "application/ogg" -> ["ogx"] + "application/omdoc+xml" -> ["omdoc"] + "application/onenote" -> ["onetoc", "onetoc2", "onetmp", "onepkg"] + "application/oxps" -> ["oxps"] + "application/patch-ops-error+xml" -> ["xer"] + "application/pdf" -> ["pdf"] + "application/pgp-encrypted" -> ["pgp"] + "application/pgp-signature" -> ["asc", "sig"] + "application/pics-rules" -> ["prf"] + "application/pkcs10" -> ["p10"] + "application/pkcs7-mime" -> ["p7m", "p7c"] + "application/pkcs7-signature" -> ["p7s"] + "application/pkcs8" -> ["p8"] + "application/pkix-attr-cert" -> ["ac"] + "application/pkix-cert" -> ["cer"] + "application/pkix-crl" -> ["crl"] + "application/pkix-pkipath" -> ["pkipath"] + "application/pkixcmp" -> ["pki"] + "application/pls+xml" -> ["pls"] + "application/postscript" -> ["ai", "eps", "ps"] + "application/prs.cww" -> ["cww"] + "application/pskc+xml" -> ["pskcxml"] + "application/rdf+xml" -> ["rdf"] + "application/reginfo+xml" -> ["rif"] + "application/relax-ng-compact-syntax" -> ["rnc"] + "application/resource-lists+xml" -> ["rl"] + "application/resource-lists-diff+xml" -> ["rld"] + "application/rls-services+xml" -> ["rs"] + "application/rpki-ghostbusters" -> ["gbr"] + "application/rpki-manifest" -> ["mft"] + "application/rpki-roa" -> ["roa"] + "application/rsd+xml" -> ["rsd"] + "application/rss+xml" -> ["rss"] + "application/rtf" -> ["rtf"] + "application/sbml+xml" -> ["sbml"] + "application/scvp-cv-request" -> ["scq"] + "application/scvp-cv-response" -> ["scs"] + "application/scvp-vp-request" -> ["spq"] + "application/scvp-vp-response" -> ["spp"] + "application/sdp" -> ["sdp"] + "application/set-payment-initiation" -> ["setpay"] + "application/set-registration-initiation" -> ["setreg"] + "application/shf+xml" -> ["shf"] + "application/smil+xml" -> ["smi", "smil"] + "application/sparql-query" -> ["rq"] + "application/sparql-results+xml" -> ["srx"] + "application/srgs" -> ["gram"] + "application/srgs+xml" -> ["grxml"] + "application/sru+xml" -> ["sru"] + "application/ssdl+xml" -> ["ssdl"] + "application/ssml+xml" -> ["ssml"] + "application/tei+xml" -> ["tei", "teicorpus"] + "application/thraud+xml" -> ["tfi"] + "application/timestamped-data" -> ["tsd"] + "application/vnd.3gpp.pic-bw-large" -> ["plb"] + "application/vnd.3gpp.pic-bw-small" -> ["psb"] + "application/vnd.3gpp.pic-bw-var" -> ["pvb"] + "application/vnd.3gpp2.tcap" -> ["tcap"] + "application/vnd.3m.post-it-notes" -> ["pwn"] + "application/vnd.accpac.simply.aso" -> ["aso"] + "application/vnd.accpac.simply.imp" -> ["imp"] + "application/vnd.acucobol" -> ["acu"] + "application/vnd.acucorp" -> ["atc", "acutc"] + "application/vnd.adobe.air-application-installer-package+zip" -> ["air"] + "application/vnd.adobe.formscentral.fcdt" -> ["fcdt"] + "application/vnd.adobe.fxp" -> ["fxp", "fxpl"] + "application/vnd.adobe.xdp+xml" -> ["xdp"] + "application/vnd.adobe.xfdf" -> ["xfdf"] + "application/vnd.ahead.space" -> ["ahead"] + "application/vnd.airzip.filesecure.azf" -> ["azf"] + "application/vnd.airzip.filesecure.azs" -> ["azs"] + "application/vnd.amazon.ebook" -> ["azw"] + "application/vnd.americandynamics.acc" -> ["acc"] + "application/vnd.amiga.ami" -> ["ami"] + "application/vnd.android.package-archive" -> ["apk"] + "application/vnd.anser-web-certificate-issue-initiation" -> ["cii"] + "application/vnd.anser-web-funds-transfer-initiation" -> ["fti"] + "application/vnd.antix.game-component" -> ["atx"] + "application/vnd.apple.installer+xml" -> ["mpkg"] + "application/vnd.apple.mpegurl" -> ["m3u8"] + "application/vnd.aristanetworks.swi" -> ["swi"] + "application/vnd.astraea-software.iota" -> ["iota"] + "application/vnd.audiograph" -> ["aep"] + "application/vnd.blueice.multipass" -> ["mpm"] + "application/vnd.bmi" -> ["bmi"] + "application/vnd.businessobjects" -> ["rep"] + "application/vnd.chemdraw+xml" -> ["cdxml"] + "application/vnd.chipnuts.karaoke-mmd" -> ["mmd"] + "application/vnd.cinderella" -> ["cdy"] + "application/vnd.claymore" -> ["cla"] + "application/vnd.cloanto.rp9" -> ["rp9"] + "application/vnd.clonk.c4group" -> ["c4g", "c4d", "c4f", "c4p", "c4u"] + "application/vnd.cluetrust.cartomobile-config" -> ["c11amc"] + "application/vnd.cluetrust.cartomobile-config-pkg" -> ["c11amz"] + "application/vnd.commonspace" -> ["csp"] + "application/vnd.contact.cmsg" -> ["cdbcmsg"] + "application/vnd.cosmocaller" -> ["cmc"] + "application/vnd.crick.clicker" -> ["clkx"] + "application/vnd.crick.clicker.keyboard" -> ["clkk"] + "application/vnd.crick.clicker.palette" -> ["clkp"] + "application/vnd.crick.clicker.template" -> ["clkt"] + "application/vnd.crick.clicker.wordbank" -> ["clkw"] + "application/vnd.criticaltools.wbs+xml" -> ["wbs"] + "application/vnd.ctc-posml" -> ["pml"] + "application/vnd.cups-ppd" -> ["ppd"] + "application/vnd.curl.car" -> ["car"] + "application/vnd.curl.pcurl" -> ["pcurl"] + "application/vnd.dart" -> ["dart"] + "application/vnd.data-vision.rdz" -> ["rdz"] + "application/vnd.dece.data" -> ["uvf", "uvvf", "uvd", "uvvd"] + "application/vnd.dece.ttml+xml" -> ["uvt", "uvvt"] + "application/vnd.dece.unspecified" -> ["uvx", "uvvx"] + "application/vnd.dece.zip" -> ["uvz", "uvvz"] + "application/vnd.denovo.fcselayout-link" -> ["fe_launch"] + "application/vnd.dna" -> ["dna"] + "application/vnd.dolby.mlp" -> ["mlp"] + "application/vnd.dpgraph" -> ["dpg"] + "application/vnd.dreamfactory" -> ["dfac"] + "application/vnd.ds-keypoint" -> ["kpxx"] + "application/vnd.dvb.ait" -> ["ait"] + "application/vnd.dvb.service" -> ["svc"] + "application/vnd.dynageo" -> ["geo"] + "application/vnd.ecowin.chart" -> ["mag"] + "application/vnd.enliven" -> ["nml"] + "application/vnd.epson.esf" -> ["esf"] + "application/vnd.epson.msf" -> ["msf"] + "application/vnd.epson.quickanime" -> ["qam"] + "application/vnd.epson.salt" -> ["slt"] + "application/vnd.epson.ssf" -> ["ssf"] + "application/vnd.eszigno3+xml" -> ["es3", "et3"] + "application/vnd.ezpix-album" -> ["ez2"] + "application/vnd.ezpix-package" -> ["ez3"] + "application/vnd.fdf" -> ["fdf"] + "application/vnd.fdsn.mseed" -> ["mseed"] + "application/vnd.fdsn.seed" -> ["seed", "dataless"] + "application/vnd.flographit" -> ["gph"] + "application/vnd.fluxtime.clip" -> ["ftc"] + "application/vnd.framemaker" -> ["fm", "frame", "maker", "book"] + "application/vnd.frogans.fnc" -> ["fnc"] + "application/vnd.frogans.ltf" -> ["ltf"] + "application/vnd.fsc.weblaunch" -> ["fsc"] + "application/vnd.fujitsu.oasys" -> ["oas"] + "application/vnd.fujitsu.oasys2" -> ["oa2"] + "application/vnd.fujitsu.oasys3" -> ["oa3"] + "application/vnd.fujitsu.oasysgp" -> ["fg5"] + "application/vnd.fujitsu.oasysprs" -> ["bh2"] + "application/vnd.fujixerox.ddd" -> ["ddd"] + "application/vnd.fujixerox.docuworks" -> ["xdw"] + "application/vnd.fujixerox.docuworks.binder" -> ["xbd"] + "application/vnd.fuzzysheet" -> ["fzs"] + "application/vnd.genomatix.tuxedo" -> ["txd"] + "application/vnd.geogebra.file" -> ["ggb"] + "application/vnd.geogebra.slides" -> ["ggs"] + "application/vnd.geogebra.tool" -> ["ggt"] + "application/vnd.geometry-explorer" -> ["gex", "gre"] + "application/vnd.geonext" -> ["gxt"] + "application/vnd.geoplan" -> ["g2w"] + "application/vnd.geospace" -> ["g3w"] + "application/vnd.gmx" -> ["gmx"] + "application/vnd.google-earth.kml+xml" -> ["kml"] + "application/vnd.google-earth.kmz" -> ["kmz"] + "application/vnd.grafeq" -> ["gqf", "gqs"] + "application/vnd.groove-account" -> ["gac"] + "application/vnd.groove-help" -> ["ghf"] + "application/vnd.groove-identity-message" -> ["gim"] + "application/vnd.groove-injector" -> ["grv"] + "application/vnd.groove-tool-message" -> ["gtm"] + "application/vnd.groove-tool-template" -> ["tpl"] + "application/vnd.groove-vcard" -> ["vcg"] + "application/vnd.hal+xml" -> ["hal"] + "application/vnd.handheld-entertainment+xml" -> ["zmm"] + "application/vnd.hbci" -> ["hbci"] + "application/vnd.hhe.lesson-player" -> ["les"] + "application/vnd.hp-hpgl" -> ["hpgl"] + "application/vnd.hp-hpid" -> ["hpid"] + "application/vnd.hp-hps" -> ["hps"] + "application/vnd.hp-jlyt" -> ["jlt"] + "application/vnd.hp-pcl" -> ["pcl"] + "application/vnd.hp-pclxl" -> ["pclxl"] + "application/vnd.hydrostatix.sof-data" -> ["sfd-hdstx"] + "application/vnd.ibm.minipay" -> ["mpy"] + "application/vnd.ibm.modcap" -> ["afp", "listafp", "list3820"] + "application/vnd.ibm.rights-management" -> ["irm"] + "application/vnd.ibm.secure-container" -> ["sc"] + "application/vnd.iccprofile" -> ["icc", "icm"] + "application/vnd.igloader" -> ["igl"] + "application/vnd.immervision-ivp" -> ["ivp"] + "application/vnd.immervision-ivu" -> ["ivu"] + "application/vnd.insors.igm" -> ["igm"] + "application/vnd.intercon.formnet" -> ["xpw", "xpx"] + "application/vnd.intergeo" -> ["i2g"] + "application/vnd.intu.qbo" -> ["qbo"] + "application/vnd.intu.qfx" -> ["qfx"] + "application/vnd.ipunplugged.rcprofile" -> ["rcprofile"] + "application/vnd.irepository.package+xml" -> ["irp"] + "application/vnd.is-xpr" -> ["xpr"] + "application/vnd.isac.fcs" -> ["fcs"] + "application/vnd.jam" -> ["jam"] + "application/vnd.jcp.javame.midlet-rms" -> ["rms"] + "application/vnd.jisp" -> ["jisp"] + "application/vnd.joost.joda-archive" -> ["joda"] + "application/vnd.kahootz" -> ["ktz", "ktr"] + "application/vnd.kde.karbon" -> ["karbon"] + "application/vnd.kde.kchart" -> ["chrt"] + "application/vnd.kde.kformula" -> ["kfo"] + "application/vnd.kde.kivio" -> ["flw"] + "application/vnd.kde.kontour" -> ["kon"] + "application/vnd.kde.kpresenter" -> ["kpr", "kpt"] + "application/vnd.kde.kspread" -> ["ksp"] + "application/vnd.kde.kword" -> ["kwd", "kwt"] + "application/vnd.kenameaapp" -> ["htke"] + "application/vnd.kidspiration" -> ["kia"] + "application/vnd.kinar" -> ["kne", "knp"] + "application/vnd.koan" -> ["skp", "skd", "skt", "skm"] + "application/vnd.kodak-descriptor" -> ["sse"] + "application/vnd.las.las+xml" -> ["lasxml"] + "application/vnd.llamagraphics.life-balance.desktop" -> ["lbd"] + "application/vnd.llamagraphics.life-balance.exchange+xml" -> ["lbe"] + "application/vnd.lotus-1-2-3" -> ["123"] + "application/vnd.lotus-approach" -> ["apr"] + "application/vnd.lotus-freelance" -> ["pre"] + "application/vnd.lotus-notes" -> ["nsf"] + "application/vnd.lotus-organizer" -> ["org"] + "application/vnd.lotus-screencam" -> ["scm"] + "application/vnd.lotus-wordpro" -> ["lwp"] + "application/vnd.macports.portpkg" -> ["portpkg"] + "application/vnd.mcd" -> ["mcd"] + "application/vnd.medcalcdata" -> ["mc1"] + "application/vnd.mediastation.cdkey" -> ["cdkey"] + "application/vnd.mfer" -> ["mwf"] + "application/vnd.mfmp" -> ["mfm"] + "application/vnd.micrografx.flo" -> ["flo"] + "application/vnd.micrografx.igx" -> ["igx"] + "application/vnd.mif" -> ["mif"] + "application/vnd.mobius.daf" -> ["daf"] + "application/vnd.mobius.dis" -> ["dis"] + "application/vnd.mobius.mbk" -> ["mbk"] + "application/vnd.mobius.mqy" -> ["mqy"] + "application/vnd.mobius.msl" -> ["msl"] + "application/vnd.mobius.plc" -> ["plc"] + "application/vnd.mobius.txf" -> ["txf"] + "application/vnd.mophun.application" -> ["mpn"] + "application/vnd.mophun.certificate" -> ["mpc"] + "application/vnd.mozilla.xul+xml" -> ["xul"] + "application/vnd.ms-artgalry" -> ["cil"] + "application/vnd.ms-cab-compressed" -> ["cab"] + "application/vnd.ms-excel" -> ["xls", "xlm", "xla", "xlc", "xlt", "xlw"] + "application/vnd.ms-excel.addin.macroenabled.12" -> ["xlam"] + "application/vnd.ms-excel.sheet.binary.macroenabled.12" -> ["xlsb"] + "application/vnd.ms-excel.sheet.macroenabled.12" -> ["xlsm"] + "application/vnd.ms-excel.template.macroenabled.12" -> ["xltm"] + "application/vnd.ms-fontobject" -> ["eot"] + "application/vnd.ms-htmlhelp" -> ["chm"] + "application/vnd.ms-ims" -> ["ims"] + "application/vnd.ms-lrm" -> ["lrm"] + "application/vnd.ms-officetheme" -> ["thmx"] + "application/vnd.ms-pki.seccat" -> ["cat"] + "application/vnd.ms-pki.stl" -> ["stl"] + "application/vnd.ms-powerpoint" -> ["ppt", "pps", "pot"] + "application/vnd.ms-powerpoint.addin.macroenabled.12" -> ["ppam"] + "application/vnd.ms-powerpoint.presentation.macroenabled.12" -> ["pptm"] + "application/vnd.ms-powerpoint.slide.macroenabled.12" -> ["sldm"] + "application/vnd.ms-powerpoint.slideshow.macroenabled.12" -> ["ppsm"] + "application/vnd.ms-powerpoint.template.macroenabled.12" -> ["potm"] + "application/vnd.ms-project" -> ["mpp", "mpt"] + "application/vnd.ms-word.document.macroenabled.12" -> ["docm"] + "application/vnd.ms-word.template.macroenabled.12" -> ["dotm"] + "application/vnd.ms-works" -> ["wps", "wks", "wcm", "wdb"] + "application/vnd.ms-wpl" -> ["wpl"] + "application/vnd.ms-xpsdocument" -> ["xps"] + "application/vnd.mseq" -> ["mseq"] + "application/vnd.musician" -> ["mus"] + "application/vnd.muvee.style" -> ["msty"] + "application/vnd.mynfc" -> ["taglet"] + "application/vnd.neurolanguage.nlu" -> ["nlu"] + "application/vnd.nitf" -> ["ntf", "nitf"] + "application/vnd.noblenet-directory" -> ["nnd"] + "application/vnd.noblenet-sealer" -> ["nns"] + "application/vnd.noblenet-web" -> ["nnw"] + "application/vnd.nokia.n-gage.data" -> ["ngdat"] + "application/vnd.nokia.n-gage.symbian.install" -> ["n-gage"] + "application/vnd.nokia.radio-preset" -> ["rpst"] + "application/vnd.nokia.radio-presets" -> ["rpss"] + "application/vnd.novadigm.edm" -> ["edm"] + "application/vnd.novadigm.edx" -> ["edx"] + "application/vnd.novadigm.ext" -> ["ext"] + "application/vnd.oasis.opendocument.chart" -> ["odc"] + "application/vnd.oasis.opendocument.chart-template" -> ["otc"] + "application/vnd.oasis.opendocument.database" -> ["odb"] + "application/vnd.oasis.opendocument.formula" -> ["odf"] + "application/vnd.oasis.opendocument.formula-template" -> ["odft"] + "application/vnd.oasis.opendocument.graphics" -> ["odg"] + "application/vnd.oasis.opendocument.graphics-template" -> ["otg"] + "application/vnd.oasis.opendocument.image" -> ["odi"] + "application/vnd.oasis.opendocument.image-template" -> ["oti"] + "application/vnd.oasis.opendocument.presentation" -> ["odp"] + "application/vnd.oasis.opendocument.presentation-template" -> ["otp"] + "application/vnd.oasis.opendocument.spreadsheet" -> ["ods"] + "application/vnd.oasis.opendocument.spreadsheet-template" -> ["ots"] + "application/vnd.oasis.opendocument.text" -> ["odt"] + "application/vnd.oasis.opendocument.text-master" -> ["odm"] + "application/vnd.oasis.opendocument.text-template" -> ["ott"] + "application/vnd.oasis.opendocument.text-web" -> ["oth"] + "application/vnd.olpc-sugar" -> ["xo"] + "application/vnd.oma.dd2+xml" -> ["dd2"] + "application/vnd.openofficeorg.extension" -> ["oxt"] + "application/vnd.openxmlformats-officedocument.presentationml.presentation" -> [ + "pptx", + ] + "application/vnd.openxmlformats-officedocument.presentationml.slide" -> [ + "sldx", + ] + "application/vnd.openxmlformats-officedocument.presentationml.slideshow" -> [ + "ppsx", + ] + "application/vnd.openxmlformats-officedocument.presentationml.template" -> [ + "potx", + ] + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -> [ + "xlsx", + ] + "application/vnd.openxmlformats-officedocument.spreadsheetml.template" -> [ + "xltx", + ] + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> [ + "docx", + ] + "application/vnd.openxmlformats-officedocument.wordprocessingml.template" -> [ + "dotx", + ] + "application/vnd.osgeo.mapguide.package" -> ["mgp"] + "application/vnd.osgi.dp" -> ["dp"] + "application/vnd.osgi.subsystem" -> ["esa"] + "application/vnd.palm" -> ["pdb", "pqa", "oprc"] + "application/vnd.pawaafile" -> ["paw"] + "application/vnd.pg.format" -> ["str"] + "application/vnd.pg.osasli" -> ["ei6"] + "application/vnd.picsel" -> ["efif"] + "application/vnd.pmi.widget" -> ["wg"] + "application/vnd.pocketlearn" -> ["plf"] + "application/vnd.powerbuilder6" -> ["pbd"] + "application/vnd.previewsystems.box" -> ["box"] + "application/vnd.proteus.magazine" -> ["mgz"] + "application/vnd.publishare-delta-tree" -> ["qps"] + "application/vnd.pvi.ptid1" -> ["ptid"] + "application/vnd.quark.quarkxpress" -> [ + "qxd", "qxt", "qwd", "qwt", "qxl", "qxb", + ] + "application/vnd.realvnc.bed" -> ["bed"] + "application/vnd.recordare.musicxml" -> ["mxl"] + "application/vnd.recordare.musicxml+xml" -> ["musicxml"] + "application/vnd.rig.cryptonote" -> ["cryptonote"] + "application/vnd.rim.cod" -> ["cod"] + "application/vnd.rn-realmedia" -> ["rm"] + "application/vnd.rn-realmedia-vbr" -> ["rmvb"] + "application/vnd.route66.link66+xml" -> ["link66"] + "application/vnd.sailingtracker.track" -> ["st"] + "application/vnd.seemail" -> ["see"] + "application/vnd.sema" -> ["sema"] + "application/vnd.semd" -> ["semd"] + "application/vnd.semf" -> ["semf"] + "application/vnd.shana.informed.formdata" -> ["ifm"] + "application/vnd.shana.informed.formtemplate" -> ["itp"] + "application/vnd.shana.informed.interchange" -> ["iif"] + "application/vnd.shana.informed.package" -> ["ipk"] + "application/vnd.simtech-mindmapper" -> ["twd", "twds"] + "application/vnd.smaf" -> ["mmf"] + "application/vnd.smart.teacher" -> ["teacher"] + "application/vnd.solent.sdkm+xml" -> ["sdkm", "sdkd"] + "application/vnd.spotfire.dxp" -> ["dxp"] + "application/vnd.spotfire.sfs" -> ["sfs"] + "application/vnd.stardivision.calc" -> ["sdc"] + "application/vnd.stardivision.draw" -> ["sda"] + "application/vnd.stardivision.impress" -> ["sdd"] + "application/vnd.stardivision.math" -> ["smf"] + "application/vnd.stardivision.writer" -> ["sdw", "vor"] + "application/vnd.stardivision.writer-global" -> ["sgl"] + "application/vnd.stepmania.package" -> ["smzip"] + "application/vnd.stepmania.stepchart" -> ["sm"] + "application/vnd.sun.xml.calc" -> ["sxc"] + "application/vnd.sun.xml.calc.template" -> ["stc"] + "application/vnd.sun.xml.draw" -> ["sxd"] + "application/vnd.sun.xml.draw.template" -> ["std"] + "application/vnd.sun.xml.impress" -> ["sxi"] + "application/vnd.sun.xml.impress.template" -> ["sti"] + "application/vnd.sun.xml.math" -> ["sxm"] + "application/vnd.sun.xml.writer" -> ["sxw"] + "application/vnd.sun.xml.writer.global" -> ["sxg"] + "application/vnd.sun.xml.writer.template" -> ["stw"] + "application/vnd.sus-calendar" -> ["sus", "susp"] + "application/vnd.svd" -> ["svd"] + "application/vnd.symbian.install" -> ["sis", "sisx"] + "application/vnd.syncml+xml" -> ["xsm"] + "application/vnd.syncml.dm+wbxml" -> ["bdm"] + "application/vnd.syncml.dm+xml" -> ["xdm"] + "application/vnd.tao.intent-module-archive" -> ["tao"] + "application/vnd.tcpdump.pcap" -> ["pcap", "cap", "dmp"] + "application/vnd.tmobile-livetv" -> ["tmo"] + "application/vnd.trid.tpt" -> ["tpt"] + "application/vnd.triscape.mxs" -> ["mxs"] + "application/vnd.trueapp" -> ["tra"] + "application/vnd.ufdl" -> ["ufd", "ufdl"] + "application/vnd.uiq.theme" -> ["utz"] + "application/vnd.umajin" -> ["umj"] + "application/vnd.unity" -> ["unityweb"] + "application/vnd.uoml+xml" -> ["uoml"] + "application/vnd.vcx" -> ["vcx"] + "application/vnd.visio" -> ["vsd", "vst", "vss", "vsw"] + "application/vnd.visionary" -> ["vis"] + "application/vnd.vsf" -> ["vsf"] + "application/vnd.wap.wbxml" -> ["wbxml"] + "application/vnd.wap.wmlc" -> ["wmlc"] + "application/vnd.wap.wmlscriptc" -> ["wmlsc"] + "application/vnd.webturbo" -> ["wtb"] + "application/vnd.wolfram.player" -> ["nbp"] + "application/vnd.wordperfect" -> ["wpd"] + "application/vnd.wqd" -> ["wqd"] + "application/vnd.wt.stf" -> ["stf"] + "application/vnd.xara" -> ["xar"] + "application/vnd.xfdl" -> ["xfdl"] + "application/vnd.yamaha.hv-dic" -> ["hvd"] + "application/vnd.yamaha.hv-script" -> ["hvs"] + "application/vnd.yamaha.hv-voice" -> ["hvp"] + "application/vnd.yamaha.openscoreformat" -> ["osf"] + "application/vnd.yamaha.openscoreformat.osfpvg+xml" -> ["osfpvg"] + "application/vnd.yamaha.smaf-audio" -> ["saf"] + "application/vnd.yamaha.smaf-phrase" -> ["spf"] + "application/vnd.yellowriver-custom-menu" -> ["cmp"] + "application/vnd.zul" -> ["zir", "zirz"] + "application/vnd.zzazz.deck+xml" -> ["zaz"] + "application/voicexml+xml" -> ["vxml"] + "application/wasm" -> ["wasm"] + "application/widget" -> ["wgt"] + "application/winhlp" -> ["hlp"] + "application/wsdl+xml" -> ["wsdl"] + "application/wspolicy+xml" -> ["wspolicy"] + "application/x-7z-compressed" -> ["7z"] + "application/x-abiword" -> ["abw"] + "application/x-ace-compressed" -> ["ace"] + "application/x-apple-diskimage" -> ["dmg"] + "application/x-authorware-bin" -> ["aab", "x32", "u32", "vox"] + "application/x-authorware-map" -> ["aam"] + "application/x-authorware-seg" -> ["aas"] + "application/x-bcpio" -> ["bcpio"] + "application/x-bittorrent" -> ["torrent"] + "application/x-blorb" -> ["blb", "blorb"] + "application/x-bzip" -> ["bz"] + "application/x-bzip2" -> ["bz2", "boz"] + "application/x-cbr" -> ["cbr", "cba", "cbt", "cbz", "cb7"] + "application/x-cdlink" -> ["vcd"] + "application/x-cfs-compressed" -> ["cfs"] + "application/x-chat" -> ["chat"] + "application/x-chess-pgn" -> ["pgn"] + "application/x-conference" -> ["nsc"] + "application/x-cpio" -> ["cpio"] + "application/x-csh" -> ["csh"] + "application/x-debian-package" -> ["deb", "udeb"] + "application/x-dgc-compressed" -> ["dgc"] + "application/x-director" -> [ + "dir", "dcr", "dxr", "cst", "cct", "cxt", "w3d", "fgd", "swa", + ] + "application/x-doom" -> ["wad"] + "application/x-dtbncx+xml" -> ["ncx"] + "application/x-dtbook+xml" -> ["dtb"] + "application/x-dtbresource+xml" -> ["res"] + "application/x-dvi" -> ["dvi"] + "application/x-envoy" -> ["evy"] + "application/x-eva" -> ["eva"] + "application/x-font-bdf" -> ["bdf"] + "application/x-font-ghostscript" -> ["gsf"] + "application/x-font-linux-psf" -> ["psf"] + "application/x-font-pcf" -> ["pcf"] + "application/x-font-snf" -> ["snf"] + "application/x-font-type1" -> ["pfa", "pfb", "pfm", "afm"] + "application/x-freearc" -> ["arc"] + "application/x-futuresplash" -> ["spl"] + "application/x-gca-compressed" -> ["gca"] + "application/x-glulx" -> ["ulx"] + "application/x-gnumeric" -> ["gnumeric"] + "application/x-gramps-xml" -> ["gramps"] + "application/x-gtar" -> ["gtar"] + "application/x-hdf" -> ["hdf"] + "application/x-install-instructions" -> ["install"] + "application/x-iso9660-image" -> ["iso"] + "application/x-java-jnlp-file" -> ["jnlp"] + "application/x-latex" -> ["latex"] + "application/x-lzh-compressed" -> ["lzh", "lha"] + "application/x-mie" -> ["mie"] + "application/x-mobipocket-ebook" -> ["prc", "mobi"] + "application/x-ms-application" -> ["application"] + "application/x-ms-shortcut" -> ["lnk"] + "application/x-ms-wmd" -> ["wmd"] + "application/x-ms-wmz" -> ["wmz"] + "application/x-ms-xbap" -> ["xbap"] + "application/x-msaccess" -> ["mdb"] + "application/x-msbinder" -> ["obd"] + "application/x-mscardfile" -> ["crd"] + "application/x-msclip" -> ["clp"] + "application/x-msdownload" -> ["exe", "dll", "com", "bat", "msi"] + "application/x-msmediaview" -> ["mvb", "m13", "m14"] + "application/x-msmetafile" -> ["wmf", "wmz", "emf", "emz"] + "application/x-msmoney" -> ["mny"] + "application/x-mspublisher" -> ["pub"] + "application/x-msschedule" -> ["scd"] + "application/x-msterminal" -> ["trm"] + "application/x-mswrite" -> ["wri"] + "application/x-netcdf" -> ["nc", "cdf"] + "application/x-nzb" -> ["nzb"] + "application/x-pkcs12" -> ["p12", "pfx"] + "application/x-pkcs7-certificates" -> ["p7b", "spc"] + "application/x-pkcs7-certreqresp" -> ["p7r"] + "application/x-rar-compressed" -> ["rar"] + "application/x-research-info-systems" -> ["ris"] + "application/x-sh" -> ["sh"] + "application/x-shar" -> ["shar"] + "application/x-shockwave-flash" -> ["swf"] + "application/x-silverlight-app" -> ["xap"] + "application/x-sql" -> ["sql"] + "application/x-stuffit" -> ["sit"] + "application/x-stuffitx" -> ["sitx"] + "application/x-subrip" -> ["srt"] + "application/x-sv4cpio" -> ["sv4cpio"] + "application/x-sv4crc" -> ["sv4crc"] + "application/x-t3vm-image" -> ["t3"] + "application/x-tads" -> ["gam"] + "application/x-tar" -> ["tar"] + "application/x-tcl" -> ["tcl"] + "application/x-tex" -> ["tex"] + "application/x-tex-tfm" -> ["tfm"] + "application/x-texinfo" -> ["texinfo", "texi"] + "application/x-tgif" -> ["obj"] + "application/x-ustar" -> ["ustar"] + "application/x-wais-source" -> ["src"] + "application/x-x509-ca-cert" -> ["der", "crt"] + "application/x-xfig" -> ["fig"] + "application/x-xliff+xml" -> ["xlf"] + "application/x-xpinstall" -> ["xpi"] + "application/x-xz" -> ["xz"] + "application/x-zmachine" -> ["z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8"] + "application/xaml+xml" -> ["xaml"] + "application/xcap-diff+xml" -> ["xdf"] + "application/xenc+xml" -> ["xenc"] + "application/xhtml+xml" -> ["xhtml", "xht"] + "application/xml" -> ["xml", "xsl"] + "application/xml-dtd" -> ["dtd"] + "application/xop+xml" -> ["xop"] + "application/xproc+xml" -> ["xpl"] + "application/xslt+xml" -> ["xslt"] + "application/xspf+xml" -> ["xspf"] + "application/xv+xml" -> ["mxml", "xhvml", "xvml", "xvm"] + "application/yang" -> ["yang"] + "application/yin+xml" -> ["yin"] + "application/zip" -> ["zip"] + "audio/adpcm" -> ["adp"] + "audio/basic" -> ["au", "snd"] + "audio/midi" -> ["mid", "midi", "kar", "rmi"] + "audio/mp4" -> ["m4a", "mp4a"] + "audio/mpeg" -> ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"] + "audio/ogg" -> ["oga", "ogg", "spx", "opus"] + "audio/s3m" -> ["s3m"] + "audio/silk" -> ["sil"] + "audio/vnd.dece.audio" -> ["uva", "uvva"] + "audio/vnd.digital-winds" -> ["eol"] + "audio/vnd.dra" -> ["dra"] + "audio/vnd.dts" -> ["dts"] + "audio/vnd.dts.hd" -> ["dtshd"] + "audio/vnd.lucent.voice" -> ["lvp"] + "audio/vnd.ms-playready.media.pya" -> ["pya"] + "audio/vnd.nuera.ecelp4800" -> ["ecelp4800"] + "audio/vnd.nuera.ecelp7470" -> ["ecelp7470"] + "audio/vnd.nuera.ecelp9600" -> ["ecelp9600"] + "audio/vnd.rip" -> ["rip"] + "audio/webm" -> ["weba"] + "audio/x-aac" -> ["aac"] + "audio/x-aiff" -> ["aif", "aiff", "aifc"] + "audio/x-caf" -> ["caf"] + "audio/x-flac" -> ["flac"] + "audio/x-matroska" -> ["mka"] + "audio/x-mpegurl" -> ["m3u"] + "audio/x-ms-wax" -> ["wax"] + "audio/x-ms-wma" -> ["wma"] + "audio/x-pn-realaudio" -> ["ram", "ra"] + "audio/x-pn-realaudio-plugin" -> ["rmp"] + "audio/x-wav" -> ["wav"] + "audio/xm" -> ["xm"] + "chemical/x-cdx" -> ["cdx"] + "chemical/x-cif" -> ["cif"] + "chemical/x-cmdf" -> ["cmdf"] + "chemical/x-cml" -> ["cml"] + "chemical/x-csml" -> ["csml"] + "chemical/x-xyz" -> ["xyz"] + "font/collection" -> ["ttc"] + "font/otf" -> ["otf"] + "font/ttf" -> ["ttf"] + "font/woff" -> ["woff"] + "font/woff2" -> ["woff2"] + "image/avif" -> ["avif"] + "image/bmp" -> ["bmp"] + "image/cgm" -> ["cgm"] + "image/g3fax" -> ["g3"] + "image/gif" -> ["gif"] + "image/ief" -> ["ief"] + "image/jpeg" -> ["jpeg", "jpg", "jpe"] + "image/jxl" -> ["jxl"] + "image/ktx" -> ["ktx"] + "image/png" -> ["png"] + "image/prs.btif" -> ["btif"] + "image/sgi" -> ["sgi"] + "image/svg+xml" -> ["svg", "svgz"] + "image/tiff" -> ["tiff", "tif"] + "image/vnd.adobe.photoshop" -> ["psd"] + "image/vnd.dece.graphic" -> ["uvi", "uvvi", "uvg", "uvvg"] + "image/vnd.djvu" -> ["djvu", "djv"] + "image/vnd.dvb.subtitle" -> ["sub"] + "image/vnd.dwg" -> ["dwg"] + "image/vnd.dxf" -> ["dxf"] + "image/vnd.fastbidsheet" -> ["fbs"] + "image/vnd.fpx" -> ["fpx"] + "image/vnd.fst" -> ["fst"] + "image/vnd.fujixerox.edmics-mmr" -> ["mmr"] + "image/vnd.fujixerox.edmics-rlc" -> ["rlc"] + "image/vnd.ms-modi" -> ["mdi"] + "image/vnd.ms-photo" -> ["wdp"] + "image/vnd.net-fpx" -> ["npx"] + "image/vnd.wap.wbmp" -> ["wbmp"] + "image/vnd.xiff" -> ["xif"] + "image/webp" -> ["webp"] + "image/x-3ds" -> ["3ds"] + "image/x-cmu-raster" -> ["ras"] + "image/x-cmx" -> ["cmx"] + "image/x-freehand" -> ["fh", "fhc", "fh4", "fh5", "fh7"] + "image/x-icon" -> ["ico"] + "image/x-mrsid-image" -> ["sid"] + "image/x-pcx" -> ["pcx"] + "image/x-pict" -> ["pic", "pct"] + "image/x-portable-anymap" -> ["pnm"] + "image/x-portable-bitmap" -> ["pbm"] + "image/x-portable-graymap" -> ["pgm"] + "image/x-portable-pixmap" -> ["ppm"] + "image/x-rgb" -> ["rgb"] + "image/x-tga" -> ["tga"] + "image/x-xbitmap" -> ["xbm"] + "image/x-xpixmap" -> ["xpm"] + "image/x-xwindowdump" -> ["xwd"] + "message/rfc822" -> ["eml", "mime"] + "model/iges" -> ["igs", "iges"] + "model/mesh" -> ["msh", "mesh", "silo"] + "model/vnd.collada+xml" -> ["dae"] + "model/vnd.dwf" -> ["dwf"] + "model/vnd.gdl" -> ["gdl"] + "model/vnd.gtw" -> ["gtw"] + "model/vnd.vtu" -> ["vtu"] + "model/vrml" -> ["wrl", "vrml"] + "model/x3d+binary" -> ["x3db", "x3dbz"] + "model/x3d+vrml" -> ["x3dv", "x3dvz"] + "model/x3d+xml" -> ["x3d", "x3dz"] + "text/cache-manifest" -> ["appcache"] + "text/calendar" -> ["ics", "ifb"] + "text/css" -> ["css"] + "text/csv" -> ["csv"] + "text/html" -> ["html", "htm"] + "text/javascript" -> ["js", "mjs"] + "text/n3" -> ["n3"] + "text/plain" -> ["txt", "text", "conf", "def", "list", "log", "in"] + "text/prs.lines.tag" -> ["dsc"] + "text/richtext" -> ["rtx"] + "text/sgml" -> ["sgml", "sgm"] + "text/tab-separated-values" -> ["tsv"] + "text/troff" -> ["t", "tr", "roff", "man", "me", "ms"] + "text/turtle" -> ["ttl"] + "text/uri-list" -> ["uri", "uris", "urls"] + "text/vcard" -> ["vcard"] + "text/vnd.curl" -> ["curl"] + "text/vnd.curl.dcurl" -> ["dcurl"] + "text/vnd.curl.mcurl" -> ["mcurl"] + "text/vnd.curl.scurl" -> ["scurl"] + "text/vnd.dvb.subtitle" -> ["sub"] + "text/vnd.fly" -> ["fly"] + "text/vnd.fmi.flexstor" -> ["flx"] + "text/vnd.graphviz" -> ["gv"] + "text/vnd.in3d.3dml" -> ["3dml"] + "text/vnd.in3d.spot" -> ["spot"] + "text/vnd.sun.j2me.app-descriptor" -> ["jad"] + "text/vnd.wap.wml" -> ["wml"] + "text/vnd.wap.wmlscript" -> ["wmls"] + "text/x-asm" -> ["s", "asm"] + "text/x-c" -> ["c", "cc", "cxx", "cpp", "h", "hh", "dic"] + "text/x-fortran" -> ["f", "for", "f77", "f90"] + "text/x-java-source" -> ["java"] + "text/x-nfo" -> ["nfo"] + "text/x-opml" -> ["opml"] + "text/x-pascal" -> ["p", "pas"] + "text/x-setext" -> ["etx"] + "text/x-sfv" -> ["sfv"] + "text/x-uuencode" -> ["uu"] + "text/x-vcalendar" -> ["vcs"] + "text/x-vcard" -> ["vcf"] + "video/3gpp" -> ["3gp"] + "video/3gpp2" -> ["3g2"] + "video/h261" -> ["h261"] + "video/h263" -> ["h263"] + "video/h264" -> ["h264"] + "video/jpeg" -> ["jpgv"] + "video/jpm" -> ["jpm", "jpgm"] + "video/mj2" -> ["mj2", "mjp2"] + "video/mp2t" -> ["ts", "m2t", "m2ts", "mts"] + "video/mp4" -> ["mp4", "mp4v", "mpg4"] + "video/mpeg" -> ["mpeg", "mpg", "mpe", "m1v", "m2v"] + "video/ogg" -> ["ogv"] + "video/quicktime" -> ["qt", "mov"] + "video/vnd.dece.hd" -> ["uvh", "uvvh"] + "video/vnd.dece.mobile" -> ["uvm", "uvvm"] + "video/vnd.dece.pd" -> ["uvp", "uvvp"] + "video/vnd.dece.sd" -> ["uvs", "uvvs"] + "video/vnd.dece.video" -> ["uvv", "uvvv"] + "video/vnd.dvb.file" -> ["dvb"] + "video/vnd.fvt" -> ["fvt"] + "video/vnd.mpegurl" -> ["mxu", "m4u"] + "video/vnd.ms-playready.media.pyv" -> ["pyv"] + "video/vnd.uvvu.mp4" -> ["uvu", "uvvu"] + "video/vnd.vivo" -> ["viv"] + "video/webm" -> ["webm"] + "video/x-f4v" -> ["f4v"] + "video/x-fli" -> ["fli"] + "video/x-flv" -> ["flv"] + "video/x-m4v" -> ["m4v"] + "video/x-matroska" -> ["mkv", "mk3d", "mks"] + "video/x-mng" -> ["mng"] + "video/x-ms-asf" -> ["asf", "asx"] + "video/x-ms-vob" -> ["vob"] + "video/x-ms-wm" -> ["wm"] + "video/x-ms-wmv" -> ["wmv"] + "video/x-ms-wmx" -> ["wmx"] + "video/x-ms-wvx" -> ["wvx"] + "video/x-msvideo" -> ["avi"] + "video/x-sgi-movie" -> ["movie"] + "video/x-smv" -> ["smv"] + "x-conference/x-cooltalk" -> ["ice"] + _ -> [] + } +} diff --git a/examples/http_interactions/build/packages/mist/LICENSE b/examples/http_interactions/build/packages/mist/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/mist/README.md b/examples/http_interactions/build/packages/mist/README.md new file mode 100644 index 0000000..65cb1f1 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/README.md @@ -0,0 +1,233 @@ +# mist + +[![Package Version](https://img.shields.io/hexpm/v/mist)](https://hex.pm/packages/mist) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/mist/) + +## A glistening Gleam web server. + +To follow along with the example below, you can create a new project and add the +dependencies as follows: + +```sh +$ gleam new +$ cd +$ gleam add mist logging gleam_erlang gleam_http +``` + +The main entrypoint for your application is `mist.start`. The argument to this +function is generated from the opaque `Builder` type. It can be constructed with +the `mist.new` function, and fed updated configuration options with the +associated methods (demonstrated in the examples below). + +```gleam +import gleam/bit_array +import gleam/bytes_tree +import gleam/erlang/process.{type Subject} +import gleam/http/request.{type Request} +import gleam/http/response.{type Response} +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{None} +import gleam/result +import gleam/string +import logging +import mist.{type Connection, type ResponseData} + +const index = " + + Mist Example + + + Hello, world! + +" + +pub fn main() { + logging.configure() + logging.set_level(logging.Debug) + + let not_found = + response.new(404) + |> response.set_body(mist.Bytes(bytes_tree.new())) + + let assert Ok(_) = + fn(req: Request(Connection)) -> Response(ResponseData) { + let _ = case mist.get_connection_info(req.body) { + Ok(info) -> { + logging.log( + logging.Info, + "Got a request from: " <> mist.connection_info_to_string(info), + ) + } + Error(_nil) -> { + logging.log(logging.Info, "Failed to get connection info") + } + } + case request.path_segments(req) { + [] -> + response.new(200) + |> response.prepend_header("my-value", "abc") + |> response.prepend_header("my-value", "123") + |> response.set_body(mist.Bytes(bytes_tree.from_string(index))) + ["ws"] -> + mist.websocket( + request: req, + on_init: fn(_conn) { #(Nil, None) }, + on_close: fn(_state) { io.println("goodbye!") }, + handler: handle_ws_message, + ) + ["echo"] -> echo_body(req) + ["chunk"] -> + mist.chunked( + req, + response.new(200), + fn(subj) { + process.spawn(fn() { send_chunks(subj) }) + Nil + }, + fn(state, msg, connection) { + case msg { + Data(str) -> { + let assert Ok(_nil) = + mist.send_chunk(connection, bit_array.from_string(str)) + mist.chunk_continue(state) + } + Done -> { + mist.chunk_stop() + } + } + }, + ) + ["file", ..rest] -> serve_file(req, rest) + ["stream"] -> handle_stream(req) + + _ -> not_found + } + } + |> mist.new + |> mist.bind("localhost") + |> mist.with_ipv6 + |> mist.port(4000) + |> mist.start + + process.sleep_forever() +} + +pub type MyMessage { + Broadcast(String) +} + +fn handle_ws_message(state, message, conn) { + case message { + mist.Text("ping") -> { + let assert Ok(_) = mist.send_text_frame(conn, "pong") + mist.continue(state) + } + mist.Text(msg) -> { + logging.log(logging.Info, "Received text frame: " <> msg) + mist.continue(state) + } + mist.Binary(msg) -> { + logging.log( + logging.Info, + "Received binary frame (" + <> int.to_string(bit_array.byte_size(msg)) + <> ")", + ) + mist.continue(state) + } + mist.Custom(Broadcast(text)) -> { + let assert Ok(_) = mist.send_text_frame(conn, text) + mist.continue(state) + } + mist.Closed | mist.Shutdown -> mist.stop() + } +} + +fn echo_body(request: Request(Connection)) -> Response(ResponseData) { + let content_type = + request + |> request.get_header("content-type") + |> result.unwrap("text/plain") + + mist.read_body(request, 1024 * 1024 * 10) + |> result.map(fn(req) { + response.new(200) + |> response.set_body(mist.Bytes(bytes_tree.from_bit_array(req.body))) + |> response.set_header("content-type", content_type) + }) + |> result.lazy_unwrap(fn() { + response.new(400) + |> response.set_body(mist.Bytes(bytes_tree.new())) + }) +} + +type ChunkMessage { + Data(data: String) + Done +} + +fn send_chunks(subject: Subject(ChunkMessage)) { + ["one", "two", "three"] + |> list.each(fn(data) { + process.sleep(2000) + process.send(subject, Data(data)) + }) + process.send(subject, Done) +} + +fn serve_file( + _req: Request(Connection), + path: List(String), +) -> Response(ResponseData) { + let file_path = string.join(path, "/") + + // Omitting validation for brevity + mist.send_file(file_path, offset: 0, limit: None) + |> result.map(fn(file) { + let content_type = guess_content_type(file_path) + response.new(200) + |> response.prepend_header("content-type", content_type) + |> response.set_body(file) + }) + |> result.lazy_unwrap(fn() { + response.new(404) + |> response.set_body(mist.Bytes(bytes_tree.new())) + }) +} + +fn handle_stream(req: Request(Connection)) -> Response(ResponseData) { + let failed = + response.new(400) |> response.set_body(mist.Bytes(bytes_tree.new())) + case mist.stream(req) { + Ok(consume) -> { + case do_handle_stream(consume, <<>>) { + Ok(body) -> + response.new(200) + |> response.set_body(mist.Bytes(bytes_tree.from_bit_array(body))) + Error(_reason) -> failed + } + } + Error(_reason) -> { + failed + } + } +} + +fn do_handle_stream( + consume: fn(Int) -> Result(mist.Chunk, mist.ReadError), + body: BitArray, +) -> Result(BitArray, Nil) { + case consume(1024) { + Ok(mist.Chunk(data, consume)) -> + do_handle_stream(consume, <>) + Ok(mist.Done) -> Ok(body) + Error(_reason) -> Error(Nil) + } +} + +fn guess_content_type(_path: String) -> String { + "application/octet-stream" +} +``` diff --git a/examples/http_interactions/build/packages/mist/gleam.toml b/examples/http_interactions/build/packages/mist/gleam.toml new file mode 100644 index 0000000..821afff --- /dev/null +++ b/examples/http_interactions/build/packages/mist/gleam.toml @@ -0,0 +1,26 @@ +name = "mist" +version = "6.0.3" + +licences = ["Apache-2.0"] +description = "a misty Gleam web server" +repository = { type = "github", user = "rawhat", repo = "mist" } +target = "erlang" +gleam = ">= 1.11.0" + +[erlang] +application_start_module = "mist@internal@clock" + +[dependencies] +gleam_erlang = ">= 1.0.0 and < 2.0.0" +gleam_http = ">= 4.0.0 and < 5.0.0" +gleam_stdlib = ">= 1.0.0 and < 2.0.0" +hpack_erl = ">= 0.3.0 and < 1.0.0" +logging = ">= 1.0.0 and < 2.0.0" +glisten = ">= 9.0.0 and < 10.0.0" +exception = ">= 2.1.0 and < 3.0.0" +gramps = ">= 6.0.0 and < 7.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +gleam_hackney = ">= 1.2.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@buffer_Buffer.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@buffer_Buffer.hrl new file mode 100644 index 0000000..95dd5e5 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@buffer_Buffer.hrl @@ -0,0 +1 @@ +-record(buffer, {remaining :: integer(), data :: bitstring()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@file_File.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@file_File.hrl new file mode 100644 index 0000000..5bd539f --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@file_File.hrl @@ -0,0 +1,4 @@ +-record(file, { + descriptor :: mist@internal@file:file_descriptor(), + file_size :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http1.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http1.hrl new file mode 100644 index 0000000..d556097 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http1.hrl @@ -0,0 +1,4 @@ +-record(http1, { + state :: mist@internal@http@handler:state(), + self :: gleam@erlang@process:subject(mist@internal@http2@stream:send_message()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http2.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http2.hrl new file mode 100644 index 0000000..09bbc21 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@handler_Http2.hrl @@ -0,0 +1 @@ +-record(http2, {state :: mist@internal@http2@handler:state()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Continuation.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Continuation.hrl new file mode 100644 index 0000000..754a828 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Continuation.hrl @@ -0,0 +1,4 @@ +-record(continuation, { + data :: mist@internal@http2@frame:data(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Data.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Data.hrl new file mode 100644 index 0000000..e1de948 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Data.hrl @@ -0,0 +1,5 @@ +-record(data, { + data :: bitstring(), + end_stream :: boolean(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_GoAway.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_GoAway.hrl new file mode 100644 index 0000000..00541f8 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_GoAway.hrl @@ -0,0 +1,5 @@ +-record(go_away, { + data :: bitstring(), + error :: mist@internal@http2@frame:connection_error(), + last_stream_id :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Header.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Header.hrl new file mode 100644 index 0000000..6bce4e0 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Header.hrl @@ -0,0 +1,6 @@ +-record(header, { + data :: mist@internal@http2@frame:data(), + end_stream :: boolean(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + priority :: gleam@option:option(mist@internal@http2@frame:header_priority()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_HeaderPriority.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_HeaderPriority.hrl new file mode 100644 index 0000000..05ecc39 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_HeaderPriority.hrl @@ -0,0 +1,5 @@ +-record(header_priority, { + exclusive :: boolean(), + stream_dependency :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + weight :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Ping.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Ping.hrl new file mode 100644 index 0000000..edff9f9 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Ping.hrl @@ -0,0 +1 @@ +-record(ping, {ack :: boolean(), data :: bitstring()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Priority.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Priority.hrl new file mode 100644 index 0000000..803bb0e --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Priority.hrl @@ -0,0 +1,6 @@ +-record(priority, { + exclusive :: boolean(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + stream_dependency :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + weight :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_PushPromise.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_PushPromise.hrl new file mode 100644 index 0000000..968cd0f --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_PushPromise.hrl @@ -0,0 +1,5 @@ +-record(push_promise, { + data :: mist@internal@http2@frame:data(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + promised_stream_id :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Settings.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Settings.hrl new file mode 100644 index 0000000..07169d9 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Settings.hrl @@ -0,0 +1,4 @@ +-record(settings, { + ack :: boolean(), + settings :: list(mist@internal@http2@frame:setting()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Termination.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Termination.hrl new file mode 100644 index 0000000..792801b --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_Termination.hrl @@ -0,0 +1,4 @@ +-record(termination, { + error :: mist@internal@http2@frame:connection_error(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_WindowUpdate.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_WindowUpdate.hrl new file mode 100644 index 0000000..66312e8 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@frame_WindowUpdate.hrl @@ -0,0 +1,4 @@ +-record(window_update, { + amount :: integer(), + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@handler_State.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@handler_State.hrl new file mode 100644 index 0000000..9f46b1c --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@handler_State.hrl @@ -0,0 +1,12 @@ +-record(state, { + fragment :: gleam@option:option(mist@internal@http2@frame:frame()), + frame_buffer :: mist@internal@buffer:buffer(), + pending_sends :: list(mist@internal@http2@handler:pending_send()), + receive_hpack_context :: mist@internal@http2:hpack_context(), + self :: gleam@erlang@process:subject(mist@internal@http2@stream:send_message()), + send_hpack_context :: mist@internal@http2:hpack_context(), + send_window_size :: integer(), + receive_window_size :: integer(), + settings :: mist@internal@http2:http2_settings(), + streams :: gleam@dict:dict(mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), mist@internal@http2@stream:state()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Data.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Data.hrl new file mode 100644 index 0000000..1c530f9 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Data.hrl @@ -0,0 +1 @@ +-record(data, {bits :: bitstring(), 'end' :: boolean()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_InternalState.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_InternalState.hrl new file mode 100644 index 0000000..da284f3 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_InternalState.hrl @@ -0,0 +1,7 @@ +-record(internal_state, { + data_selector :: gleam@erlang@process:selector(mist@internal@http2@stream:message()), + data_subject :: gleam@erlang@process:subject(mist@internal@http2@stream:message()), + 'end' :: boolean(), + pending_response :: gleam@option:option(gleam@http@response:response(mist@internal@http:response_data())), + to_remove :: bitstring() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Send.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Send.hrl new file mode 100644 index 0000000..e881c02 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_Send.hrl @@ -0,0 +1,4 @@ +-record(send, { + identifier :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + resp :: gleam@http@response:response(mist@internal@http:response_data()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_State.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_State.hrl new file mode 100644 index 0000000..0a07332 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2@stream_State.hrl @@ -0,0 +1,8 @@ +-record(state, { + id :: mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + state :: mist@internal@http2@stream:stream_state(), + subject :: gleam@erlang@process:subject(mist@internal@http2@stream:message()), + receive_window_size :: integer(), + send_window_size :: integer(), + pending_content_length :: gleam@option:option(integer()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http2_Http2Settings.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http2_Http2Settings.hrl new file mode 100644 index 0000000..61b2db3 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http2_Http2Settings.hrl @@ -0,0 +1,8 @@ +-record(http2_settings, { + header_table_size :: integer(), + server_push :: mist@internal@http2@frame:push_state(), + max_concurrent_streams :: integer(), + initial_window_size :: integer(), + max_frame_size :: integer(), + max_header_list_size :: gleam@option:option(integer()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http@handler_State.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http@handler_State.hrl new file mode 100644 index 0000000..6088112 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http@handler_State.hrl @@ -0,0 +1 @@ +-record(state, {idle_timer :: gleam@option:option(gleam@erlang@process:timer())}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http_Chunk.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Chunk.hrl new file mode 100644 index 0000000..2f17c5b --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Chunk.hrl @@ -0,0 +1 @@ +-record(chunk, {data :: bitstring(), buffer :: mist@internal@buffer:buffer()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http_Connection.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Connection.hrl new file mode 100644 index 0000000..cf2764f --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Connection.hrl @@ -0,0 +1,8 @@ +-record(connection, { + body :: mist@internal@http:body(), + socket :: glisten@socket:socket(), + transport :: glisten@transport:transport(), + factory_name :: gleam@erlang@process:name(gleam@otp@factory_supervisor:message(fun(() -> {ok, + gleam@otp@actor:started(gleam@erlang@process:pid_())} | + {error, gleam@otp@actor:start_error()}), gleam@erlang@process:pid_())) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http_File.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http_File.hrl new file mode 100644 index 0000000..e2a7a90 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http_File.hrl @@ -0,0 +1,5 @@ +-record(file, { + descriptor :: mist@internal@file:file_descriptor(), + offset :: integer(), + length :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http_Http1Request.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Http1Request.hrl new file mode 100644 index 0000000..95d584a --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Http1Request.hrl @@ -0,0 +1,4 @@ +-record(http1_request, { + request :: gleam@http@request:request(mist@internal@http:connection()), + version :: mist@internal@http:http_version() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@http_Stream.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Stream.hrl new file mode 100644 index 0000000..b808e87 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@http_Stream.hrl @@ -0,0 +1,6 @@ +-record(stream, { + selector :: gleam@erlang@process:selector(bitstring()), + data :: bitstring(), + remaining :: integer(), + attempts :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@next_AbnormalStop.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@next_AbnormalStop.hrl new file mode 100644 index 0000000..d2ca52b --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@next_AbnormalStop.hrl @@ -0,0 +1 @@ +-record(abnormal_stop, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@next_Continue.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@next_Continue.hrl new file mode 100644 index 0000000..85677d1 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@next_Continue.hrl @@ -0,0 +1,4 @@ +-record(continue, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketConnection.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketConnection.hrl new file mode 100644 index 0000000..b2be7c6 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketConnection.hrl @@ -0,0 +1,5 @@ +-record(websocket_connection, { + socket :: glisten@socket:socket(), + transport :: glisten@transport:transport(), + deflate :: gleam@option:option(gramps@websocket@compression:context()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketState.hrl b/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketState.hrl new file mode 100644 index 0000000..871bd45 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist@internal@websocket_WebsocketState.hrl @@ -0,0 +1,5 @@ +-record(websocket_state, { + buffer :: bitstring(), + user :: any(), + permessage_deflate :: gleam@option:option(gramps@websocket@compression:compression()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_AbnormalStop.hrl b/examples/http_interactions/build/packages/mist/include/mist_AbnormalStop.hrl new file mode 100644 index 0000000..d2ca52b --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_AbnormalStop.hrl @@ -0,0 +1 @@ +-record(abnormal_stop, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_Builder.hrl b/examples/http_interactions/build/packages/mist/include/mist_Builder.hrl new file mode 100644 index 0000000..f062637 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_Builder.hrl @@ -0,0 +1,8 @@ +-record(builder, { + port :: integer(), + handler :: fun((gleam@http@request:request(any())) -> gleam@http@response:response(any())), + after_start :: fun((integer(), gleam@http:scheme(), mist:ip_address()) -> nil), + interface :: binary(), + ipv6_support :: boolean(), + tls_options :: gleam@option:option(mist:tls_options()) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_Chunk.hrl b/examples/http_interactions/build/packages/mist/include/mist_Chunk.hrl new file mode 100644 index 0000000..ce632d8 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_Chunk.hrl @@ -0,0 +1,5 @@ +-record(chunk, { + data :: bitstring(), + consume :: fun((integer()) -> {ok, mist:chunk()} | + {error, mist:read_error()}) +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_ChunkAbort.hrl b/examples/http_interactions/build/packages/mist/include/mist_ChunkAbort.hrl new file mode 100644 index 0000000..6b46d08 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_ChunkAbort.hrl @@ -0,0 +1 @@ +-record(chunk_abort, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_ChunkContinue.hrl b/examples/http_interactions/build/packages/mist/include/mist_ChunkContinue.hrl new file mode 100644 index 0000000..dc6c54d --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_ChunkContinue.hrl @@ -0,0 +1 @@ +-record(chunk_continue, {state :: any()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_ConnectionInfo.hrl b/examples/http_interactions/build/packages/mist/include/mist_ConnectionInfo.hrl new file mode 100644 index 0000000..017c074 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_ConnectionInfo.hrl @@ -0,0 +1 @@ +-record(connection_info, {port :: integer(), ip_address :: mist:ip_address()}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_File.hrl b/examples/http_interactions/build/packages/mist/include/mist_File.hrl new file mode 100644 index 0000000..e2a7a90 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_File.hrl @@ -0,0 +1,5 @@ +-record(file, { + descriptor :: mist@internal@file:file_descriptor(), + offset :: integer(), + length :: integer() +}). diff --git a/examples/http_interactions/build/packages/mist/include/mist_SSEEvent.hrl b/examples/http_interactions/build/packages/mist/include/mist_SSEEvent.hrl new file mode 100644 index 0000000..edc8bd1 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/include/mist_SSEEvent.hrl @@ -0,0 +1,6 @@ +-record(s_s_e_event, { + id :: gleam@option:option(binary()), + event :: gleam@option:option(binary()), + retry :: gleam@option:option(integer()), + data :: gleam@string_tree:string_tree() +}). diff --git a/examples/http_interactions/build/packages/mist/src/mist.app.src b/examples/http_interactions/build/packages/mist/src/mist.app.src new file mode 100644 index 0000000..2ea0951 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist.app.src @@ -0,0 +1,33 @@ +{application, mist, [ + {mod, {'mist@internal@clock', []}}, + {vsn, "6.0.3"}, + {applications, [exception, + gleam_erlang, + gleam_http, + gleam_otp, + gleam_stdlib, + glisten, + gramps, + hpack, + logging]}, + {description, "a misty Gleam web server"}, + {modules, [hackney_ffi, + mist, + mist@@main, + mist@internal@buffer, + mist@internal@clock, + mist@internal@encoder, + mist@internal@file, + mist@internal@handler, + mist@internal@http, + mist@internal@http2, + mist@internal@http2@flow_control, + mist@internal@http2@frame, + mist@internal@http2@handler, + mist@internal@http2@stream, + mist@internal@http@handler, + mist@internal@next, + mist@internal@websocket, + mist_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/mist/src/mist.erl b/examples/http_interactions/build/packages/mist/src/mist.erl new file mode 100644 index 0000000..2808aba --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist.erl @@ -0,0 +1,1399 @@ +-module(mist). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist.gleam"). +-export([continue/1, with_selector/2, stop/0, stop_abnormal/1, ip_address_to_string/1, connection_info_to_string/1, get_connection_info/1, send_file/3, read_body/2, stream/1, new/1, port/2, read_request_body/3, after_start/2, bind/2, with_ipv6/1, with_tls/3, start/1, supervised/1, websocket/4, send_binary_frame/2, send_text_frame/2, event/1, event_id/2, event_name/2, event_retry/2, server_sent_events/4, send_event/2, chunk_continue/1, chunk_stop/0, chunk_stop_abnormal/1, send_chunk/2, chunked/4]). +-export_type([next/2, ip_address/0, connection_info/0, response_data/0, file_error/0, read_error/0, chunk/0, chunk_state/0, tls_options/0, builder/2, port_/0, websocket_message/1, s_s_e_connection/0, s_s_e_event/0, chunk_next/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-opaque next(LWW, LWX) :: {continue, + LWW, + gleam@option:option(gleam@erlang@process:selector(LWX))} | + normal_stop | + {abnormal_stop, binary()}. + +-type ip_address() :: {ip_v4, integer(), integer(), integer(), integer()} | + {ip_v6, + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. + +-type connection_info() :: {connection_info, integer(), ip_address()}. + +-type response_data() :: websocket | + {bytes, gleam@bytes_tree:bytes_tree()} | + chunked | + {file, mist@internal@file:file_descriptor(), integer(), integer()} | + server_sent_events. + +-type file_error() :: is_dir | no_access | no_entry | unknown_file_error. + +-type read_error() :: excess_body | malformed_body. + +-type chunk() :: {chunk, + bitstring(), + fun((integer()) -> {ok, chunk()} | {error, read_error()})} | + done. + +-type chunk_state() :: {chunk_state, + mist@internal@buffer:buffer(), + mist@internal@buffer:buffer(), + boolean()}. + +-type tls_options() :: {cert_key_files, binary(), binary()}. + +-opaque builder(LWY, LWZ) :: {builder, + integer(), + fun((gleam@http@request:request(LWY)) -> gleam@http@response:response(LWZ)), + fun((integer(), gleam@http:scheme(), ip_address()) -> nil), + binary(), + boolean(), + gleam@option:option(tls_options())}. + +-type port_() :: assigned | {provided, integer()}. + +-type websocket_message(LXA) :: {text, binary()} | + {binary, bitstring()} | + closed | + shutdown | + {custom, LXA}. + +-opaque s_s_e_connection() :: {s_s_e_connection, + mist@internal@http:connection()}. + +-opaque s_s_e_event() :: {s_s_e_event, + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(integer()), + gleam@string_tree:string_tree()}. + +-type chunk_next(LXB) :: {chunk_continue, LXB} | + chunk_stop | + {chunk_abort, binary()}. + +-file("src/mist.gleam", 53). +-spec continue(LXC) -> next(LXC, any()). +continue(State) -> + {continue, State, none}. + +-file("src/mist.gleam", 57). +-spec with_selector(next(LXG, LXH), gleam@erlang@process:selector(LXH)) -> next(LXG, LXH). +with_selector(Next, Selector) -> + case Next of + {continue, State, _} -> + {continue, State, {some, Selector}}; + + _ -> + Next + end. + +-file("src/mist.gleam", 67). +-spec stop() -> next(any(), any()). +stop() -> + normal_stop. + +-file("src/mist.gleam", 71). +-spec stop_abnormal(binary()) -> next(any(), any()). +stop_abnormal(Reason) -> + {abnormal_stop, Reason}. + +-file("src/mist.gleam", 75). +-spec convert_next(next(LXV, LXW)) -> mist@internal@next:next(LXV, LXW). +convert_next(Next) -> + case Next of + {continue, State, Selector} -> + {continue, State, Selector}; + + normal_stop -> + normal_stop; + + {abnormal_stop, Reason} -> + {abnormal_stop, Reason} + end. + +-file("src/mist.gleam", 99). +-spec to_mist_ip_address(glisten:ip_address()) -> ip_address(). +to_mist_ip_address(Ip) -> + case Ip of + {ip_v4, A, B, C, D} -> + {ip_v4, A, B, C, D}; + + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} + end. + +-file("src/mist.gleam", 106). +-spec to_glisten_ip_address(ip_address()) -> glisten:ip_address(). +to_glisten_ip_address(Ip) -> + case Ip of + {ip_v4, A, B, C, D} -> + {ip_v4, A, B, C, D}; + + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} -> + {ip_v6, A@1, B@1, C@1, D@1, E, F, G, H} + end. + +-file("src/mist.gleam", 95). +?DOC( + " Convenience function for printing the `IpAddress` type. It will convert the\n" + " IPv6 loopback to the short-hand `::1`.\n" +). +-spec ip_address_to_string(ip_address()) -> binary(). +ip_address_to_string(Address) -> + glisten:ip_address_to_string(to_glisten_ip_address(Address)). + +-file("src/mist.gleam", 117). +-spec connection_info_to_string(connection_info()) -> binary(). +connection_info_to_string(Connection_info) -> + case erlang:element(3, Connection_info) of + {ip_v6, A, B, C, D, E, F, G, H} -> + Blocks = begin + _pipe = [A, B, C, D, E, F, G, H], + _pipe@1 = gleam@list:map(_pipe, fun erlang:integer_to_binary/1), + gleam@string:join(_pipe@1, <<":"/utf8>>) + end, + <<<<<<"["/utf8, Blocks/binary>>/binary, "]:"/utf8>>/binary, + (erlang:integer_to_binary(erlang:element(2, Connection_info)))/binary>>; + + {ip_v4, A@1, B@1, C@1, D@1} -> + Blocks@1 = begin + _pipe@2 = [A@1, B@1, C@1, D@1], + _pipe@3 = gleam@list:map( + _pipe@2, + fun erlang:integer_to_binary/1 + ), + gleam@string:join(_pipe@3, <<"."/utf8>>) + end, + <<<>/binary, + (erlang:integer_to_binary(erlang:element(2, Connection_info)))/binary>> + end. + +-file("src/mist.gleam", 139). +?DOC(" Tries to get the IP address and port of a connected client.\n"). +-spec get_connection_info(mist@internal@http:connection()) -> {ok, + connection_info()} | + {error, nil}. +get_connection_info(Conn) -> + _pipe = glisten@transport:peername( + erlang:element(4, Conn), + erlang:element(3, Conn) + ), + gleam@result:map( + _pipe, + fun(Pair) -> + {connection_info, + erlang:element(2, Pair), + begin + _pipe@1 = erlang:element(1, Pair), + _pipe@2 = glisten:convert_ip_address(_pipe@1), + to_mist_ip_address(_pipe@2) + end} + end + ). + +-file("src/mist.gleam", 175). +-spec convert_file_errors(mist@internal@file:file_error()) -> file_error(). +convert_file_errors(Err) -> + case Err of + is_dir -> + is_dir; + + no_access -> + no_access; + + no_entry -> + no_entry; + + unknown_file_error -> + unknown_file_error + end. + +-file("src/mist.gleam", 189). +?DOC( + " To respond with a file using Erlang's `sendfile`, use this function\n" + " with the specified offset and limit (optional). It will attempt to open the\n" + " file for reading, get its file size, and then send the file. If the read\n" + " errors, this will return the relevant `FileError`. Generally, this will be\n" + " more memory efficient than manually doing this process with `mist.Bytes`.\n" +). +-spec send_file(binary(), integer(), gleam@option:option(integer())) -> {ok, + response_data()} | + {error, file_error()}. +send_file(Path, Offset, Limit) -> + _pipe = Path, + _pipe@1 = gleam_stdlib:identity(_pipe), + _pipe@2 = mist@internal@file:stat(_pipe@1), + _pipe@3 = gleam@result:map_error(_pipe@2, fun convert_file_errors/1), + gleam@result:map( + _pipe@3, + fun(Stat) -> + Length = case Limit of + {some, Value} when (Value + Offset) > erlang:element(3, Stat) -> + erlang:element(3, Stat) - Offset; + + {some, Value@1} -> + Value@1; + + none -> + erlang:element(3, Stat) - Offset + end, + {file, erlang:element(2, Stat), Offset, Length} + end + ). + +-file("src/mist.gleam", 220). +?DOC( + " The request body is not pulled from the socket until requested. The\n" + " `content-length` header is used to determine whether the socket is read\n" + " from or not. The read may also fail, and a `ReadError` is raised.\n" +). +-spec read_body( + gleam@http@request:request(mist@internal@http:connection()), + integer() +) -> {ok, gleam@http@request:request(bitstring())} | {error, read_error()}. +read_body(Req, Max_body_limit) -> + _pipe = Req, + _pipe@1 = gleam@http@request:get_header(_pipe, <<"content-length"/utf8>>), + _pipe@2 = gleam@result:'try'(_pipe@1, fun gleam_stdlib:parse_int/1), + _pipe@3 = gleam@result:unwrap(_pipe@2, 0), + (fun(Content_length) -> case Content_length of + Value when Value =< Max_body_limit -> + _pipe@4 = mist@internal@http:read_body(Req), + gleam@result:replace_error(_pipe@4, malformed_body); + + _ -> + {error, excess_body} + end end)(_pipe@3). + +-file("src/mist.gleam", 249). +-spec do_stream( + gleam@http@request:request(mist@internal@http:connection()), + mist@internal@buffer:buffer() +) -> fun((integer()) -> {ok, chunk()} | {error, read_error()}). +do_stream(Req, Buffer) -> + fun(Size) -> + Socket = erlang:element(3, erlang:element(4, Req)), + Transport = erlang:element(4, erlang:element(4, Req)), + Byte_size = erlang:byte_size(erlang:element(3, Buffer)), + case {erlang:element(2, Buffer), Byte_size} of + {0, 0} -> + {ok, done}; + + {0, _} -> + {Data, Rest} = mist@internal@buffer:slice(Buffer, Size), + {ok, + {chunk, + Data, + do_stream(Req, mist@internal@buffer:new(Rest))}}; + + {_, Buffer_size} when Buffer_size >= Size -> + {Data@1, Rest@1} = mist@internal@buffer:slice(Buffer, Size), + New_buffer = {buffer, erlang:element(2, Buffer), Rest@1}, + {ok, {chunk, Data@1, do_stream(Req, New_buffer)}}; + + {_, _} -> + _pipe = mist@internal@http:read_data( + Socket, + Transport, + mist@internal@buffer:empty(), + invalid_body + ), + _pipe@1 = gleam@result:replace_error(_pipe, malformed_body), + gleam@result:map( + _pipe@1, + fun(Data@2) -> + Fetched_data = erlang:byte_size(Data@2), + New_buffer@1 = {buffer, + gleam@int:max( + 0, + erlang:element(2, Buffer) - Fetched_data + ), + gleam@bit_array:append( + erlang:element(3, Buffer), + Data@2 + )}, + {New_data, Rest@2} = mist@internal@buffer:slice( + New_buffer@1, + Size + ), + {chunk, + New_data, + do_stream( + Req, + {buffer, + erlang:element(2, New_buffer@1), + Rest@2} + )} + end + ) + end + end. + +-file("src/mist.gleam", 314). +-spec fetch_chunks_until( + glisten@socket:socket(), + glisten@transport:transport(), + chunk_state(), + integer() +) -> {ok, {bitstring(), chunk_state()}} | {error, read_error()}. +fetch_chunks_until(Socket, Transport, State, Byte_size) -> + Data_size = erlang:byte_size(erlang:element(3, erlang:element(2, State))), + case {erlang:element(4, State), Data_size} of + {_, Size} when Size >= Byte_size -> + {Value, Rest} = mist@internal@buffer:slice( + erlang:element(2, State), + Byte_size + ), + {ok, + {Value, + {chunk_state, + mist@internal@buffer:new(Rest), + erlang:element(3, State), + erlang:element(4, State)}}}; + + {true, _} -> + {ok, + {erlang:element(3, erlang:element(2, State)), + {chunk_state, + erlang:element(2, State), + erlang:element(3, State), + true}}}; + + {false, _} -> + case mist@internal@http:parse_chunk( + erlang:element(3, erlang:element(3, State)) + ) of + complete -> + Updated_state = {chunk_state, + erlang:element(2, State), + mist@internal@buffer:empty(), + true}, + fetch_chunks_until( + Socket, + Transport, + Updated_state, + Byte_size + ); + + {chunk, <<>>, Next_buffer} -> + _pipe = mist@internal@http:read_data( + Socket, + Transport, + Next_buffer, + invalid_body + ), + _pipe@1 = gleam@result:replace_error(_pipe, malformed_body), + gleam@result:'try'( + _pipe@1, + fun(New_data) -> + Updated_state@1 = {chunk_state, + erlang:element(2, State), + mist@internal@buffer:new(New_data), + erlang:element(4, State)}, + fetch_chunks_until( + Socket, + Transport, + Updated_state@1, + Byte_size + ) + end + ); + + {chunk, Data, Next_buffer@1} -> + Updated_state@2 = {chunk_state, + mist@internal@buffer:append( + erlang:element(2, State), + Data + ), + Next_buffer@1, + erlang:element(4, State)}, + fetch_chunks_until( + Socket, + Transport, + Updated_state@2, + Byte_size + ) + end + end. + +-file("src/mist.gleam", 294). +-spec do_stream_chunked( + gleam@http@request:request(mist@internal@http:connection()), + chunk_state() +) -> fun((integer()) -> {ok, chunk()} | {error, read_error()}). +do_stream_chunked(Req, State) -> + Socket = erlang:element(3, erlang:element(4, Req)), + Transport = erlang:element(4, erlang:element(4, Req)), + fun(Size) -> case fetch_chunks_until(Socket, Transport, State, Size) of + {ok, {Data, {chunk_state, _, _, true}}} -> + {ok, {chunk, Data, fun(_) -> {ok, done} end}}; + + {ok, {Data@1, State@1}} -> + {ok, {chunk, Data@1, do_stream_chunked(Req, State@1)}}; + + {error, _} -> + {error, malformed_body} + end end. + +-file("src/mist.gleam", 366). +?DOC( + " Rather than explicitly reading either the whole body (optionally up to\n" + " `N` bytes), this function allows you to consume a stream of the request\n" + " body. Any errors reading the body will propagate out, or `Chunk`s will be\n" + " emitted. This provides a `consume` method to attempt to grab the next\n" + " `size` chunk from the socket.\n" +). +-spec stream(gleam@http@request:request(mist@internal@http:connection())) -> {ok, + fun((integer()) -> {ok, chunk()} | {error, read_error()})} | + {error, read_error()}. +stream(Req) -> + Continue = begin + _pipe = Req, + _pipe@1 = mist@internal@http:handle_continue(_pipe), + gleam@result:replace_error(_pipe@1, malformed_body) + end, + gleam@result:map( + Continue, + fun(_) -> + Is_chunked = case gleam@http@request:get_header( + Req, + <<"transfer-encoding"/utf8>> + ) of + {ok, <<"chunked"/utf8>>} -> + true; + + _ -> + false + end, + Data@1 = case erlang:element(2, erlang:element(4, Req)) of + {initial, Data} -> Data; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"stream"/utf8>>, + line => 381, + value => _assert_fail, + start => 11891, + 'end' => 11936, + pattern_start => 11902, + pattern_end => 11920}) + end, + case Is_chunked of + true -> + State = {chunk_state, + mist@internal@buffer:new(<<>>), + mist@internal@buffer:new(Data@1), + false}, + do_stream_chunked(Req, State); + + false -> + Content_length = begin + _pipe@2 = Req, + _pipe@3 = gleam@http@request:get_header( + _pipe@2, + <<"content-length"/utf8>> + ), + _pipe@4 = gleam@result:'try'( + _pipe@3, + fun gleam_stdlib:parse_int/1 + ), + gleam@result:unwrap(_pipe@4, 0) + end, + Initial_size = erlang:byte_size(Data@1), + Buffer = {buffer, + gleam@int:max(0, Content_length - Initial_size), + Data@1}, + do_stream(Req, Buffer) + end + end + ). + +-file("src/mist.gleam", 422). +?DOC( + " Create a new `mist` handler with a given function. The default port is\n" + " 4000.\n" +). +-spec new( + fun((gleam@http@request:request(LYX)) -> gleam@http@response:response(LYZ)) +) -> builder(LYX, LYZ). +new(Handler) -> + {builder, + 4000, + Handler, + fun(Port, Scheme, Interface) -> + Address = case Interface of + {ip_v6, _, _, _, _, _, _, _, _} -> + <<<<"["/utf8, (ip_address_to_string(Interface))/binary>>/binary, + "]"/utf8>>; + + _ -> + ip_address_to_string(Interface) + end, + Message = <<<<<<<<<<"Listening on "/utf8, + (gleam@http:scheme_to_string(Scheme))/binary>>/binary, + "://"/utf8>>/binary, + Address/binary>>/binary, + ":"/utf8>>/binary, + (erlang:integer_to_binary(Port))/binary>>, + gleam_stdlib:println(Message) + end, + <<"localhost"/utf8>>, + false, + none}. + +-file("src/mist.gleam", 447). +?DOC(" Assign a different listening port to the service.\n"). +-spec port(builder(LZD, LZE), integer()) -> builder(LZD, LZE). +port(Builder, Port) -> + {builder, + Port, + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder)}. + +-file("src/mist.gleam", 454). +?DOC( + " This function allows for implicitly reading the body of requests up\n" + " to a given size. If the size is too large, or the read fails, the provided\n" + " `failure_response` will be sent back as the response.\n" +). +-spec read_request_body( + builder(bitstring(), LZJ), + integer(), + gleam@http@response:response(LZJ) +) -> builder(mist@internal@http:connection(), LZJ). +read_request_body(Builder, Bytes_limit, Failure_response) -> + Handler = fun(Request) -> case read_body(Request, Bytes_limit) of + {ok, Request@1} -> + (erlang:element(3, Builder))(Request@1); + + {error, _} -> + Failure_response + end end, + {builder, + erlang:element(2, Builder), + Handler, + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder)}. + +-file("src/mist.gleam", 470). +?DOC( + " Override the default function to be called after the service starts. The\n" + " default is to log a message with the listening port.\n" +). +-spec after_start( + builder(LZP, LZQ), + fun((integer(), gleam@http:scheme(), ip_address()) -> nil) +) -> builder(LZP, LZQ). +after_start(Builder, After_start) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + After_start, + erlang:element(5, Builder), + erlang:element(6, Builder), + erlang:element(7, Builder)}. + +-file("src/mist.gleam", 481). +?DOC( + " Specify an interface to listen on. This is a string that can have the\n" + " following values: \"localhost\", a valid IPv4 address (i.e. \"127.0.0.1\"), or\n" + " a valid IPv6 address (i.e. \"::1\"). An invalid value will cause the\n" + " application to crash.\n" +). +-spec bind(builder(LZV, LZW), binary()) -> builder(LZV, LZW). +bind(Builder, Interface) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + Interface, + erlang:element(6, Builder), + erlang:element(7, Builder)}. + +-file("src/mist.gleam", 490). +?DOC( + " By default, `mist` will listen on `localhost` over IPv4. If you specify an\n" + " IPv4 address to bind to, it will still only serve over IPv4. Calling this\n" + " function will listen on both IPv4 and IPv6 for the given interface. If it is\n" + " not supported, your application will crash. If you provide an IPv6 address\n" + " to `mist.bind`, this function will have no effect.\n" +). +-spec with_ipv6(builder(MAB, MAC)) -> builder(MAB, MAC). +with_ipv6(Builder) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + true, + erlang:element(7, Builder)}. + +-file("src/mist.gleam", 495). +?DOC(" Use HTTPS with the provided certificate and key files.\n"). +-spec with_tls(builder(MAH, MAI), binary(), binary()) -> builder(MAH, MAI). +with_tls(Builder, Cert, Key) -> + Certfile = mist_ffi:file_open(gleam_stdlib:identity(Cert)), + Keyfile = mist_ffi:file_open(gleam_stdlib:identity(Key)), + _ = case {Certfile, Keyfile} of + {{error, _}, {error, _}} -> + erlang:error(#{gleam_error => panic, + message => <<"Certificate and key file not found"/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"with_tls"/utf8>>, + line => 504}); + + {{ok, _}, {error, _}} -> + erlang:error(#{gleam_error => panic, + message => <<"Key file not found"/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"with_tls"/utf8>>, + line => 505}); + + {{error, _}, {ok, _}} -> + erlang:error(#{gleam_error => panic, + message => <<"Certificate file not found"/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"with_tls"/utf8>>, + line => 506}); + + {{ok, _}, {ok, _}} -> + nil + end, + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder), + {some, {cert_key_files, Cert, Key}}}. + +-file("src/mist.gleam", 513). +-spec convert_body_types(gleam@http@response:response(response_data())) -> gleam@http@response:response(mist@internal@http:response_data()). +convert_body_types(Resp) -> + New_body = case erlang:element(4, Resp) of + websocket -> + websocket; + + {bytes, Data} -> + {bytes, Data}; + + {file, Descriptor, Offset, Length} -> + {file, Descriptor, Offset, Length}; + + chunked -> + chunked; + + server_sent_events -> + server_sent_events + end, + gleam@http@response:set_body(Resp, New_body). + +-file("src/mist.gleam", 532). +?DOC(" Start a `mist` service with the provided builder.\n"). +-spec start(builder(mist@internal@http:connection(), response_data())) -> {ok, + gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} | + {error, gleam@otp@actor:start_error()}. +start(Builder) -> + Listener_name = gleam_erlang_ffi:new_name(<<"glisten_listener"/utf8>>), + Factory_name = gleam_erlang_ffi:new_name(<<"mist_factory_supervisor"/utf8>>), + _pipe = gleam@otp@static_supervisor:new(one_for_one), + _pipe@10 = gleam@otp@static_supervisor:add( + _pipe, + gleam@otp@supervision:supervisor( + fun() -> + _pipe@1 = fun(Req) -> + convert_body_types((erlang:element(3, Builder))(Req)) + end, + _pipe@2 = mist@internal@handler:with_func(_pipe@1, Factory_name), + _pipe@3 = glisten:new(fun mist@internal@handler:init/1, _pipe@2), + _pipe@4 = glisten:bind(_pipe@3, erlang:element(5, Builder)), + _pipe@5 = (fun(Handler) -> case erlang:element(6, Builder) of + true -> + glisten:with_ipv6(Handler); + + false -> + Handler + end end)(_pipe@4), + _pipe@7 = (fun(Handler@1) -> case erlang:element(7, Builder) of + {some, {cert_key_files, Certfile, Keyfile}} -> + _pipe@6 = Handler@1, + glisten:with_tls(_pipe@6, Certfile, Keyfile); + + _ -> + Handler@1 + end end)(_pipe@5), + _pipe@8 = glisten:with_listener_name(_pipe@7, Listener_name), + _pipe@9 = glisten:start(_pipe@8, erlang:element(2, Builder)), + gleam@result:map( + _pipe@9, + fun(Server) -> + Info = glisten:get_server_info(Listener_name, 5000), + Ip_address = to_mist_ip_address(erlang:element(3, Info)), + Scheme = case gleam@option:is_some( + erlang:element(7, Builder) + ) of + true -> + https; + + false -> + http + end, + (erlang:element(4, Builder))( + erlang:element(2, Info), + Scheme, + Ip_address + ), + Server + end + ) + end + ) + ), + _pipe@14 = gleam@otp@static_supervisor:add( + _pipe@10, + gleam@otp@supervision:supervisor( + fun() -> + _pipe@11 = gleam@otp@factory_supervisor:worker_child( + fun(Start) -> Start() end + ), + _pipe@12 = gleam@otp@factory_supervisor:named( + _pipe@11, + Factory_name + ), + _pipe@13 = gleam@otp@factory_supervisor:restart_strategy( + _pipe@12, + temporary + ), + gleam@otp@factory_supervisor:start(_pipe@13) + end + ) + ), + gleam@otp@static_supervisor:start(_pipe@14). + +-file("src/mist.gleam", 585). +?DOC(" Start the `mist` supervisor as a child of a supervision tree.\n"). +-spec supervised(builder(mist@internal@http:connection(), response_data())) -> gleam@otp@supervision:child_specification(gleam@otp@static_supervisor:supervisor()). +supervised(Builder) -> + gleam@otp@supervision:supervisor(fun() -> start(Builder) end). + +-file("src/mist.gleam", 600). +-spec internal_to_public_ws_message( + mist@internal@websocket:handler_message(MAX) +) -> {ok, websocket_message(MAX)} | {error, nil}. +internal_to_public_ws_message(Msg) -> + case Msg of + {internal, {data, {text_frame, Data}}} -> + _pipe = Data, + _pipe@1 = gleam@bit_array:to_string(_pipe), + gleam@result:map(_pipe@1, fun(Field@0) -> {text, Field@0} end); + + {internal, {data, {binary_frame, Data@1}}} -> + {ok, {binary, Data@1}}; + + {user, Msg@1} -> + {ok, {custom, Msg@1}}; + + _ -> + {error, nil} + end. + +-file("src/mist.gleam", 625). +?DOC( + " Upgrade a request to handle websockets. If the request is\n" + " malformed, or the websocket process fails to initialize, an empty\n" + " 400 response will be sent to the client.\n" + "\n" + " The `on_init` method will be called when the actual WebSocket process\n" + " is started, and the return value is the initial state and an optional\n" + " selector for receiving user messages.\n" + "\n" + " The `on_close` method is called when the WebSocket process shuts down\n" + " for any reason, valid or otherwise.\n" +). +-spec websocket( + gleam@http@request:request(mist@internal@http:connection()), + fun((MBD, websocket_message(MBE), mist@internal@websocket:websocket_connection()) -> next(MBD, MBE)), + fun((mist@internal@websocket:websocket_connection()) -> {MBD, + gleam@option:option(gleam@erlang@process:selector(MBE))}), + fun((MBD) -> nil) +) -> gleam@http@response:response(response_data()). +websocket(Request, Handler, On_init, On_close) -> + Handler@1 = fun(State, Message, Connection) -> _pipe = Message, + _pipe@1 = internal_to_public_ws_message(_pipe), + _pipe@2 = gleam@result:map( + _pipe@1, + fun(_capture) -> Handler(State, _capture, Connection) end + ), + _pipe@3 = gleam@result:unwrap(_pipe@2, continue(State)), + convert_next(_pipe@3) end, + Extensions = begin + _pipe@4 = Request, + _pipe@5 = gleam@http@request:get_header( + _pipe@4, + <<"sec-websocket-extensions"/utf8>> + ), + _pipe@6 = gleam@result:map( + _pipe@5, + fun(Header) -> gleam@string:split(Header, <<";"/utf8>>) end + ), + gleam@result:unwrap(_pipe@6, []) + end, + Socket = erlang:element(3, erlang:element(4, Request)), + Transport = erlang:element(4, erlang:element(4, Request)), + case mist@internal@http:upgrade(Socket, Transport, Extensions, Request) of + {ok, _} -> + Start = fun() -> + mist@internal@websocket:initialize_connection( + On_init, + On_close, + Handler@1, + Socket, + Transport, + Extensions + ) + end, + Factory_supervisor = gleam@otp@factory_supervisor:get_by_name( + erlang:element(5, erlang:element(4, Request)) + ), + case gleam@otp@factory_supervisor:start_child( + Factory_supervisor, + Start + ) of + {ok, Started} -> + case glisten@transport:controlling_process( + Transport, + Socket, + erlang:element(3, Started) + ) of + {ok, _} -> nil; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"websocket"/utf8>>, + line => 664, + value => _assert_fail, + start => 20644, + 'end' => 20737, + pattern_start => 20655, + pattern_end => 20660}) + end, + mist@internal@websocket:set_active(Transport, Socket), + _pipe@7 = gleam@http@response:new(200), + gleam@http@response:set_body(_pipe@7, websocket); + + {error, Start_error} -> + Msg = case Start_error of + init_timeout -> + <<"init timed out"/utf8>>; + + {init_failed, Reason} -> + <<"init failed: "/utf8, Reason/binary>>; + + {init_exited, normal} -> + <<"init exited normally"/utf8>>; + + {init_exited, killed} -> + <<"init killed"/utf8>>; + + {init_exited, {abnormal, _}} -> + <<"init exited abnormally"/utf8>> + end, + logging:log( + error, + <<"Failed to start WebSocket process: "/utf8, + Msg/binary>> + ), + _pipe@8 = gleam@http@response:new(400), + gleam@http@response:set_body( + _pipe@8, + {bytes, gleam@bytes_tree:new()} + ) + end; + + {error, _} -> + _pipe@9 = gleam@http@response:new(400), + gleam@http@response:set_body( + _pipe@9, + {bytes, gleam@bytes_tree:new()} + ) + end. + +-file("src/mist.gleam", 698). +?DOC(" Sends a binary frame across the websocket.\n"). +-spec send_binary_frame( + mist@internal@websocket:websocket_connection(), + bitstring() +) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +send_binary_frame(Connection, Frame) -> + Binary_frame = exception_ffi:rescue( + fun() -> + gramps@websocket:encode_binary_frame( + Frame, + erlang:element(4, Connection), + none + ) + end + ), + case Binary_frame of + {ok, Binary_frame@1} -> + glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + Binary_frame@1 + ); + + {error, _} -> + logging:log( + error, + <<"Cannot send messages from a different process than the WebSocket"/utf8>> + ), + erlang:error(#{gleam_error => panic, + message => <<"Exiting due to sending WebSocket message from non-owning process"/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"send_binary_frame"/utf8>>, + line => 715}) + end. + +-file("src/mist.gleam", 721). +?DOC(" Sends a text frame across the websocket.\n"). +-spec send_text_frame(mist@internal@websocket:websocket_connection(), binary()) -> {ok, + nil} | + {error, glisten@socket:socket_reason()}. +send_text_frame(Connection, Frame) -> + Text_frame = exception_ffi:rescue( + fun() -> + gramps@websocket:encode_text_frame( + Frame, + erlang:element(4, Connection), + none + ) + end + ), + case Text_frame of + {ok, Text_frame@1} -> + glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + Text_frame@1 + ); + + {error, _} -> + logging:log( + error, + <<"Cannot send messages from a different process than the WebSocket"/utf8>> + ), + erlang:error(#{gleam_error => panic, + message => <<"Exiting due to sending WebSocket message from non-owning process"/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"send_text_frame"/utf8>>, + line => 738}) + end. + +-file("src/mist.gleam", 764). +-spec event(gleam@string_tree:string_tree()) -> s_s_e_event(). +event(Data) -> + {s_s_e_event, none, none, none, Data}. + +-file("src/mist.gleam", 769). +-spec event_id(s_s_e_event(), binary()) -> s_s_e_event(). +event_id(Event, Id) -> + {s_s_e_event, + {some, Id}, + erlang:element(3, Event), + erlang:element(4, Event), + erlang:element(5, Event)}. + +-file("src/mist.gleam", 774). +-spec event_name(s_s_e_event(), binary()) -> s_s_e_event(). +event_name(Event, Name) -> + {s_s_e_event, + erlang:element(2, Event), + {some, Name}, + erlang:element(4, Event), + erlang:element(5, Event)}. + +-file("src/mist.gleam", 779). +-spec event_retry(s_s_e_event(), integer()) -> s_s_e_event(). +event_retry(Event, Retry) -> + {s_s_e_event, + erlang:element(2, Event), + erlang:element(3, Event), + {some, Retry}, + erlang:element(5, Event)}. + +-file("src/mist.gleam", 792). +?DOC( + " Sets up the connection for server-sent events. The initial response provided\n" + " here will have its headers included in the SSE setup. The body is discarded.\n" + " The `init` and `loop` parameters follow the same shape as the\n" + " `gleam/otp/actor` module.\n" + "\n" + " NOTE: There is no proper way within the spec for the server to \"close\" the\n" + " SSE connection. There are ways around it.\n" + "\n" + " See: `examples/eventz` for a sample usage.\n" +). +-spec server_sent_events( + gleam@http@request:request(mist@internal@http:connection()), + gleam@http@response:response(any()), + fun((gleam@erlang@process:subject(MBS)) -> MBU), + fun((MBU, MBS, s_s_e_connection()) -> gleam@otp@actor:next(MBU, MBS)) +) -> gleam@http@response:response(response_data()). +server_sent_events(Req, Resp, Init, Loop) -> + With_default_headers = begin + _pipe = Resp, + _pipe@1 = gleam@http@response:set_header( + _pipe, + <<"content-type"/utf8>>, + <<"text/event-stream"/utf8>> + ), + _pipe@2 = gleam@http@response:set_header( + _pipe@1, + <<"cache-control"/utf8>>, + <<"no-cache"/utf8>> + ), + gleam@http@response:set_header( + _pipe@2, + <<"connection"/utf8>>, + <<"keep-alive"/utf8>> + ) + end, + case glisten@transport:send( + erlang:element(4, erlang:element(4, Req)), + erlang:element(3, erlang:element(4, Req)), + mist@internal@encoder:response_builder( + 200, + erlang:element(3, With_default_headers), + <<"1.1"/utf8>> + ) + ) of + {ok, _} -> + Start = fun() -> + _pipe@8 = gleam@otp@actor:new_with_initialiser( + 1000, + fun(Subj) -> _pipe@3 = Init(Subj), + _pipe@4 = gleam@otp@actor:initialised(_pipe@3), + _pipe@5 = gleam@otp@actor:returning( + _pipe@4, + erlang:self() + ), + _pipe@7 = gleam@otp@actor:selecting( + _pipe@5, + begin + _pipe@6 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@6, Subj) + end + ), + {ok, _pipe@7} end + ), + _pipe@9 = gleam@otp@actor:on_message( + _pipe@8, + fun(State, Message) -> + Loop( + State, + Message, + {s_s_e_connection, erlang:element(4, Req)} + ) + end + ), + _pipe@10 = gleam@otp@actor:start(_pipe@9), + gleam@result:map( + _pipe@10, + fun(Started) -> + Pid = erlang:element(3, Started), + {started, Pid, Pid} + end + ) + end, + Factory_supervisor = gleam@otp@factory_supervisor:get_by_name( + erlang:element(5, erlang:element(4, Req)) + ), + case gleam@otp@factory_supervisor:start_child( + Factory_supervisor, + Start + ) of + {ok, Started@1} -> + case glisten@transport:controlling_process( + erlang:element(4, erlang:element(4, Req)), + erlang:element(3, erlang:element(4, Req)), + erlang:element(3, Started@1) + ) of + {ok, _} -> nil; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"server_sent_events"/utf8>>, + line => 832, + value => _assert_fail, + start => 26099, + 'end' => 26270, + pattern_start => 26110, + pattern_end => 26118}) + end, + _pipe@11 = gleam@http@response:new(200), + gleam@http@response:set_body(_pipe@11, server_sent_events); + + {error, _} -> + logging:log(error, <<"Failed to start SSE process"/utf8>>), + _pipe@12 = gleam@http@response:new(400), + gleam@http@response:set_body( + _pipe@12, + {bytes, gleam@bytes_tree:new()} + ) + end; + + {error, _} -> + _pipe@13 = gleam@http@response:new(400), + gleam@http@response:set_body( + _pipe@13, + {bytes, gleam@bytes_tree:new()} + ) + end. + +-file("src/mist.gleam", 859). +-spec send_event(s_s_e_connection(), s_s_e_event()) -> {ok, nil} | {error, nil}. +send_event(Conn, Event) -> + {s_s_e_connection, Conn@1} = Conn, + Id@1 = begin + _pipe = erlang:element(2, Event), + _pipe@1 = gleam@option:map( + _pipe, + fun(Id) -> <<<<"id: "/utf8, Id/binary>>/binary, "\n"/utf8>> end + ), + gleam@option:unwrap(_pipe@1, <<""/utf8>>) + end, + Event_name = begin + _pipe@2 = erlang:element(3, Event), + _pipe@3 = gleam@option:map( + _pipe@2, + fun(Name) -> + <<<<"event: "/utf8, Name/binary>>/binary, "\n"/utf8>> + end + ), + gleam@option:unwrap(_pipe@3, <<""/utf8>>) + end, + Retry@1 = begin + _pipe@4 = erlang:element(4, Event), + _pipe@5 = gleam@option:map( + _pipe@4, + fun(Retry) -> + <<<<"retry: "/utf8, (erlang:integer_to_binary(Retry))/binary>>/binary, + "\n"/utf8>> + end + ), + gleam@option:unwrap(_pipe@5, <<""/utf8>>) + end, + Data = begin + _pipe@6 = erlang:element(5, Event), + _pipe@7 = gleam@string_tree:split(_pipe@6, <<"\n"/utf8>>), + _pipe@8 = gleam@list:map( + _pipe@7, + fun(Row) -> gleam@string_tree:prepend(Row, <<"data: "/utf8>>) end + ), + gleam@string_tree:join(_pipe@8, <<"\n"/utf8>>) + end, + Message = begin + _pipe@9 = Data, + _pipe@10 = gleam@string_tree:prepend(_pipe@9, Event_name), + _pipe@11 = gleam@string_tree:prepend(_pipe@10, Id@1), + _pipe@12 = gleam@string_tree:prepend(_pipe@11, Retry@1), + _pipe@13 = gleam@string_tree:append(_pipe@12, <<"\n\n"/utf8>>), + gleam_stdlib:wrap_list(_pipe@13) + end, + _pipe@14 = glisten@transport:send( + erlang:element(4, Conn@1), + erlang:element(3, Conn@1), + Message + ), + _pipe@15 = gleam@result:replace(_pipe@14, nil), + gleam@result:replace_error(_pipe@15, nil). + +-file("src/mist.gleam", 975). +-spec chunk_continue(MCK) -> chunk_next(MCK). +chunk_continue(State) -> + {chunk_continue, State}. + +-file("src/mist.gleam", 979). +-spec chunk_stop() -> chunk_next(any()). +chunk_stop() -> + chunk_stop. + +-file("src/mist.gleam", 983). +-spec chunk_stop_abnormal(binary()) -> chunk_next(any()). +chunk_stop_abnormal(Reason) -> + {chunk_abort, Reason}. + +-file("src/mist.gleam", 991). +-spec int_to_hex(integer()) -> binary(). +int_to_hex(Int) -> + erlang:integer_to_list(Int, 16). + +-file("src/mist.gleam", 961). +-spec send_chunk(mist@internal@http:connection(), bitstring()) -> {ok, nil} | + {error, nil}. +send_chunk(Connection, Data) -> + Size = erlang:byte_size(Data), + Encoded = begin + _pipe = Size, + _pipe@1 = int_to_hex(_pipe), + _pipe@2 = gleam_stdlib:wrap_list(_pipe@1), + _pipe@3 = gleam@bytes_tree:append_string(_pipe@2, <<"\r\n"/utf8>>), + _pipe@4 = gleam@bytes_tree:append(_pipe@3, Data), + gleam@bytes_tree:append_string(_pipe@4, <<"\r\n"/utf8>>) + end, + _pipe@5 = glisten@transport:send( + erlang:element(4, Connection), + erlang:element(3, Connection), + Encoded + ), + gleam@result:replace_error(_pipe@5, nil). + +-file("src/mist.gleam", 898). +-spec chunked( + gleam@http@request:request(mist@internal@http:connection()), + gleam@http@response:response(any()), + fun((gleam@erlang@process:subject(MCD)) -> MCF), + fun((MCF, MCD, mist@internal@http:connection()) -> chunk_next(MCF)) +) -> gleam@http@response:response(response_data()). +chunked(Req, Response, Init, Loop) -> + Start = fun() -> + _pipe@5 = gleam@otp@actor:new_with_initialiser( + 1000, + fun(Subj) -> _pipe = Init(Subj), + _pipe@1 = gleam@otp@actor:initialised(_pipe), + _pipe@2 = gleam@otp@actor:returning(_pipe@1, erlang:self()), + _pipe@4 = gleam@otp@actor:selecting( + _pipe@2, + begin + _pipe@3 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@3, Subj) + end + ), + {ok, _pipe@4} end + ), + _pipe@6 = gleam@otp@actor:on_message( + _pipe@5, + fun(State, Message) -> + case Loop(State, Message, erlang:element(4, Req)) of + {chunk_continue, State@1} -> + gleam@otp@actor:continue(State@1); + + chunk_stop -> + _ = case send_chunk(erlang:element(4, Req), <<>>) of + {ok, _} -> + nil; + + {error, _} -> + logging:log( + debug, + <<"Failed to send final chunk"/utf8>> + ) + end, + gleam@otp@actor:stop(); + + {chunk_abort, Reason} -> + gleam@otp@actor:stop_abnormal(Reason) + end + end + ), + _pipe@7 = gleam@otp@actor:start(_pipe@6), + gleam@result:map( + _pipe@7, + fun(Started) -> + {started, + erlang:element(3, Started), + erlang:element(3, Started)} + end + ) + end, + Headers = [{<<"transfer-encoding"/utf8>>, <<"chunked"/utf8>>} | + erlang:element(3, Response)], + Initial_payload = mist@internal@encoder:response_builder( + erlang:element(2, Response), + Headers, + mist@internal@http:version_to_string(http11) + ), + case glisten@transport:send( + erlang:element(4, erlang:element(4, Req)), + erlang:element(3, erlang:element(4, Req)), + Initial_payload + ) of + {ok, _} -> nil; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"chunked"/utf8>>, + line => 939, + value => _assert_fail, + start => 29275, + 'end' => 29369, + pattern_start => 29286, + pattern_end => 29294}) + end, + Factory_supervisor = gleam@otp@factory_supervisor:get_by_name( + erlang:element(5, erlang:element(4, Req)) + ), + case gleam@otp@factory_supervisor:start_child(Factory_supervisor, Start) of + {ok, Started@1} -> + case glisten@transport:controlling_process( + erlang:element(4, erlang:element(4, Req)), + erlang:element(3, erlang:element(4, Req)), + erlang:element(3, Started@1) + ) of + {ok, _} -> nil; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist"/utf8>>, + function => <<"chunked"/utf8>>, + line => 946, + value => _assert_fail@1, + start => 29525, + 'end' => 29683, + pattern_start => 29536, + pattern_end => 29551}) + end, + _pipe@8 = gleam@http@response:new(200), + gleam@http@response:set_body(_pipe@8, chunked); + + {error, _} -> + logging:log( + error, + <<"Failed to start chunked response process"/utf8>> + ), + _pipe@9 = gleam@http@response:new(400), + gleam@http@response:set_body( + _pipe@9, + {bytes, gleam@bytes_tree:new()} + ) + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist.gleam b/examples/http_interactions/build/packages/mist/src/mist.gleam new file mode 100644 index 0000000..b10e3fd --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist.gleam @@ -0,0 +1,993 @@ +import exception +import gleam/bit_array +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/process.{type Selector, type Subject} +import gleam/http.{type Scheme, Http, Https} as gleam_http +import gleam/http/request.{type Request} +import gleam/http/response.{type Response} +import gleam/int +import gleam/io +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/otp/factory_supervisor as factory +import gleam/otp/static_supervisor.{type Supervisor} as supervisor +import gleam/otp/supervision.{type ChildSpecification} +import gleam/result +import gleam/string +import gleam/string_tree.{type StringTree} +import glisten +import glisten/transport +import gramps/websocket.{BinaryFrame, Data, TextFrame} as gramps_websocket +import logging +import mist/internal/buffer.{type Buffer, Buffer} +import mist/internal/encoder +import mist/internal/file +import mist/internal/handler +import mist/internal/http.{ + type Connection as InternalConnection, + type ResponseData as InternalResponseData, Bytes as InternalBytes, + Chunked as InternalChunked, File as InternalFile, + ServerSentEvents as InternalServerSentEvents, Websocket as InternalWebsocket, +} +import mist/internal/next +import mist/internal/websocket.{ + type HandlerMessage, type WebsocketConnection as InternalWebsocketConnection, + Internal, User, +} + +/// Re-exported type that represents the default `Request` body type. See +/// `mist.read_body` to convert this type into a `BitString`. The `Connection` +/// also holds some additional information about the request. Currently, the +/// only useful field is `client_ip` which is a `Result` with a tuple of +/// integers representing the IPv4 address. +pub type Connection = + InternalConnection + +pub opaque type Next(state, user_message) { + Continue(state, Option(Selector(user_message))) + NormalStop + AbnormalStop(reason: String) +} + +pub fn continue(state: state) -> Next(state, user_message) { + Continue(state, None) +} + +pub fn with_selector( + next: Next(state, user_message), + selector: Selector(user_message), +) -> Next(state, user_message) { + case next { + Continue(state, _) -> Continue(state, Some(selector)) + _ -> next + } +} + +pub fn stop() -> Next(state, user_message) { + NormalStop +} + +pub fn stop_abnormal(reason: String) -> Next(state, user_message) { + AbnormalStop(reason) +} + +fn convert_next( + next: Next(state, user_message), +) -> next.Next(state, user_message) { + case next { + Continue(state, selector) -> next.Continue(state, selector) + NormalStop -> next.NormalStop + AbnormalStop(reason) -> next.AbnormalStop(reason) + } +} + +/// When accessing client information, these are the possible shapes of the IP +/// addresses. A best effort will be made to determine whether IPv4 is most +/// relevant. +pub type IpAddress { + IpV4(Int, Int, Int, Int) + IpV6(Int, Int, Int, Int, Int, Int, Int, Int) +} + +/// Convenience function for printing the `IpAddress` type. It will convert the +/// IPv6 loopback to the short-hand `::1`. +pub fn ip_address_to_string(address: IpAddress) -> String { + glisten.ip_address_to_string(to_glisten_ip_address(address)) +} + +fn to_mist_ip_address(ip: glisten.IpAddress) -> IpAddress { + case ip { + glisten.IpV4(a, b, c, d) -> IpV4(a, b, c, d) + glisten.IpV6(a, b, c, d, e, f, g, h) -> IpV6(a, b, c, d, e, f, g, h) + } +} + +fn to_glisten_ip_address(ip: IpAddress) -> glisten.IpAddress { + case ip { + IpV4(a, b, c, d) -> glisten.IpV4(a, b, c, d) + IpV6(a, b, c, d, e, f, g, h) -> glisten.IpV6(a, b, c, d, e, f, g, h) + } +} + +pub type ConnectionInfo { + ConnectionInfo(port: Int, ip_address: IpAddress) +} + +pub fn connection_info_to_string(connection_info: ConnectionInfo) -> String { + case connection_info.ip_address { + IpV6(a, b, c, d, e, f, g, h) -> { + let blocks = + [a, b, c, d, e, f, g, h] + |> list.map(int.to_string) + |> string.join(":") + + "[" <> blocks <> "]:" <> int.to_string(connection_info.port) + } + IpV4(a, b, c, d) -> { + let blocks = + [a, b, c, d] + |> list.map(int.to_string) + |> string.join(".") + + blocks <> ":" <> int.to_string(connection_info.port) + } + } +} + +/// Tries to get the IP address and port of a connected client. +pub fn get_connection_info(conn: Connection) -> Result(ConnectionInfo, Nil) { + transport.peername(conn.transport, conn.socket) + |> result.map(fn(pair) { + ConnectionInfo( + ip_address: pair.0 + |> glisten.convert_ip_address + |> to_mist_ip_address, + port: pair.1, + ) + }) +} + +/// The response body type. This allows `mist` to handle these different cases +/// for you. `Bytes` is the regular data return. `Websocket` will upgrade the +/// socket to websockets, but should not be used directly. See the +/// `mist.upgrade` function for usage. `Chunked` will use +/// `Transfer-Encoding: chunked` to send an iterator in chunks. `File` will use +/// Erlang's `sendfile` to more efficiently return a file to the client. +pub type ResponseData { + Websocket + Bytes(BytesTree) + Chunked + /// See `mist.send_file` to use this response type. + File(descriptor: file.FileDescriptor, offset: Int, length: Int) + ServerSentEvents +} + +/// Potential errors when opening a file to send. This list is +/// currently not exhaustive with POSIX errors. +pub type FileError { + IsDir + NoAccess + NoEntry + UnknownFileError +} + +fn convert_file_errors(err: file.FileError) -> FileError { + case err { + file.IsDir -> IsDir + file.NoAccess -> NoAccess + file.NoEntry -> NoEntry + file.UnknownFileError -> UnknownFileError + } +} + +/// To respond with a file using Erlang's `sendfile`, use this function +/// with the specified offset and limit (optional). It will attempt to open the +/// file for reading, get its file size, and then send the file. If the read +/// errors, this will return the relevant `FileError`. Generally, this will be +/// more memory efficient than manually doing this process with `mist.Bytes`. +pub fn send_file( + path: String, + offset offset: Int, + limit limit: Option(Int), +) -> Result(ResponseData, FileError) { + path + |> bit_array.from_string + |> file.stat + |> result.map_error(convert_file_errors) + |> result.map(fn(stat) { + let length = case limit { + Some(value) if value + offset > stat.file_size -> stat.file_size - offset + Some(value) -> value + None -> stat.file_size - offset + } + File(descriptor: stat.descriptor, offset: offset, length:) + }) +} + +/// The possible errors from reading the request body. If the size is larger +/// than the provided value, `ExcessBody` is returned. If there is an error +/// reading the body from the socket or the body is malformed (i.e a chunked +/// request with invalid sizes), `MalformedBody` is returned. +pub type ReadError { + ExcessBody + MalformedBody +} + +/// The request body is not pulled from the socket until requested. The +/// `content-length` header is used to determine whether the socket is read +/// from or not. The read may also fail, and a `ReadError` is raised. +pub fn read_body( + req: Request(Connection), + max_body_limit max_body_limit: Int, +) -> Result(Request(BitArray), ReadError) { + req + |> request.get_header("content-length") + |> result.try(int.parse) + |> result.unwrap(0) + |> fn(content_length) { + case content_length { + value if value <= max_body_limit -> { + http.read_body(req) + |> result.replace_error(MalformedBody) + } + _ -> { + Error(ExcessBody) + } + } + } +} + +/// The values returning from streaming the request body. The `Chunk` +/// variant gives back some data and the next token. `Done` signifies +/// that we have completed reading the body. +pub type Chunk { + Chunk(data: BitArray, consume: fn(Int) -> Result(Chunk, ReadError)) + Done +} + +fn do_stream( + req: Request(Connection), + buffer: Buffer, +) -> fn(Int) -> Result(Chunk, ReadError) { + fn(size) { + let socket = req.body.socket + let transport = req.body.transport + let byte_size = bit_array.byte_size(buffer.data) + + case buffer.remaining, byte_size { + 0, 0 -> Ok(Done) + + 0, _buffer_size -> { + let #(data, rest) = buffer.slice(buffer, size) + Ok(Chunk(data, do_stream(req, buffer.new(rest)))) + } + + _, buffer_size if buffer_size >= size -> { + let #(data, rest) = buffer.slice(buffer, size) + let new_buffer = Buffer(..buffer, data: rest) + Ok(Chunk(data, do_stream(req, new_buffer))) + } + + _, _buffer_size -> { + http.read_data(socket, transport, buffer.empty(), http.InvalidBody) + |> result.replace_error(MalformedBody) + |> result.map(fn(data) { + let fetched_data = bit_array.byte_size(data) + let new_buffer = + Buffer( + data: bit_array.append(buffer.data, data), + remaining: int.max(0, buffer.remaining - fetched_data), + ) + let #(new_data, rest) = buffer.slice(new_buffer, size) + Chunk(new_data, do_stream(req, Buffer(..new_buffer, data: rest))) + }) + } + } + } +} + +type ChunkState { + ChunkState(data_buffer: Buffer, chunk_buffer: Buffer, done: Bool) +} + +fn do_stream_chunked( + req: Request(Connection), + state: ChunkState, +) -> fn(Int) -> Result(Chunk, ReadError) { + let socket = req.body.socket + let transport = req.body.transport + + fn(size) { + case fetch_chunks_until(socket, transport, state, size) { + Ok(#(data, ChunkState(done: True, ..))) -> { + Ok(Chunk(data, fn(_size) { Ok(Done) })) + } + Ok(#(data, state)) -> { + Ok(Chunk(data, do_stream_chunked(req, state))) + } + Error(_) -> Error(MalformedBody) + } + } +} + +fn fetch_chunks_until( + socket: glisten.Socket, + transport: transport.Transport, + state: ChunkState, + byte_size: Int, +) -> Result(#(BitArray, ChunkState), ReadError) { + let data_size = bit_array.byte_size(state.data_buffer.data) + case state.done, data_size { + _, size if size >= byte_size -> { + let #(value, rest) = buffer.slice(state.data_buffer, byte_size) + Ok(#(value, ChunkState(..state, data_buffer: buffer.new(rest)))) + } + + True, _ -> { + Ok(#(state.data_buffer.data, ChunkState(..state, done: True))) + } + + False, _ -> { + case http.parse_chunk(state.chunk_buffer.data) { + http.Complete -> { + let updated_state = + ChunkState(..state, chunk_buffer: buffer.empty(), done: True) + fetch_chunks_until(socket, transport, updated_state, byte_size) + } + http.Chunk(<<>>, next_buffer) -> { + http.read_data(socket, transport, next_buffer, http.InvalidBody) + |> result.replace_error(MalformedBody) + |> result.try(fn(new_data) { + let updated_state = + ChunkState(..state, chunk_buffer: buffer.new(new_data)) + fetch_chunks_until(socket, transport, updated_state, byte_size) + }) + } + http.Chunk(data, next_buffer) -> { + let updated_state = + ChunkState( + ..state, + data_buffer: buffer.append(state.data_buffer, data), + chunk_buffer: next_buffer, + ) + fetch_chunks_until(socket, transport, updated_state, byte_size) + } + } + } + } +} + +/// Rather than explicitly reading either the whole body (optionally up to +/// `N` bytes), this function allows you to consume a stream of the request +/// body. Any errors reading the body will propagate out, or `Chunk`s will be +/// emitted. This provides a `consume` method to attempt to grab the next +/// `size` chunk from the socket. +pub fn stream( + req: Request(Connection), +) -> Result(fn(Int) -> Result(Chunk, ReadError), ReadError) { + let continue = + req + |> http.handle_continue + |> result.replace_error(MalformedBody) + + use _nil <- result.map(continue) + + let is_chunked = case request.get_header(req, "transfer-encoding") { + Ok("chunked") -> True + _ -> False + } + + let assert http.Initial(data) = req.body.body + + case is_chunked { + True -> { + let state = ChunkState(buffer.new(<<>>), buffer.new(data), False) + do_stream_chunked(req, state) + } + False -> { + let content_length = + req + |> request.get_header("content-length") + |> result.try(int.parse) + |> result.unwrap(0) + + let initial_size = bit_array.byte_size(data) + + let buffer = + Buffer(data: data, remaining: int.max(0, content_length - initial_size)) + + do_stream(req, buffer) + } + } +} + +type TlsOptions { + CertKeyFiles(certfile: String, keyfile: String) +} + +pub opaque type Builder(request_body, response_body) { + Builder( + port: Int, + handler: fn(Request(request_body)) -> Response(response_body), + after_start: fn(Int, Scheme, IpAddress) -> Nil, + interface: String, + ipv6_support: Bool, + tls_options: Option(TlsOptions), + ) +} + +/// Create a new `mist` handler with a given function. The default port is +/// 4000. +pub fn new(handler: fn(Request(in)) -> Response(out)) -> Builder(in, out) { + Builder( + port: 4000, + handler: handler, + interface: "localhost", + ipv6_support: False, + after_start: fn(port, scheme, interface) { + let address = case interface { + IpV6(..) -> "[" <> ip_address_to_string(interface) <> "]" + _ -> ip_address_to_string(interface) + } + let message = + "Listening on " + <> gleam_http.scheme_to_string(scheme) + <> "://" + <> address + <> ":" + <> int.to_string(port) + io.println(message) + }, + tls_options: None, + ) +} + +/// Assign a different listening port to the service. +pub fn port(builder: Builder(in, out), port: Int) -> Builder(in, out) { + Builder(..builder, port: port) +} + +/// This function allows for implicitly reading the body of requests up +/// to a given size. If the size is too large, or the read fails, the provided +/// `failure_response` will be sent back as the response. +pub fn read_request_body( + builder: Builder(BitArray, out), + bytes_limit bytes_limit: Int, + failure_response failure_response: Response(out), +) -> Builder(Connection, out) { + let handler = fn(request) { + case read_body(request, bytes_limit) { + Ok(request) -> builder.handler(request) + Error(_) -> failure_response + } + } + Builder(..builder, handler:) +} + +/// Override the default function to be called after the service starts. The +/// default is to log a message with the listening port. +pub fn after_start( + builder: Builder(in, out), + after_start: fn(Int, Scheme, IpAddress) -> Nil, +) -> Builder(in, out) { + Builder(..builder, after_start: after_start) +} + +/// Specify an interface to listen on. This is a string that can have the +/// following values: "localhost", a valid IPv4 address (i.e. "127.0.0.1"), or +/// a valid IPv6 address (i.e. "::1"). An invalid value will cause the +/// application to crash. +pub fn bind(builder: Builder(in, out), interface: String) -> Builder(in, out) { + Builder(..builder, interface: interface) +} + +/// By default, `mist` will listen on `localhost` over IPv4. If you specify an +/// IPv4 address to bind to, it will still only serve over IPv4. Calling this +/// function will listen on both IPv4 and IPv6 for the given interface. If it is +/// not supported, your application will crash. If you provide an IPv6 address +/// to `mist.bind`, this function will have no effect. +pub fn with_ipv6(builder: Builder(in, out)) -> Builder(in, out) { + Builder(..builder, ipv6_support: True) +} + +/// Use HTTPS with the provided certificate and key files. +pub fn with_tls( + builder: Builder(in, out), + certfile cert: String, + keyfile key: String, +) -> Builder(in, out) { + let certfile = file.open(bit_array.from_string(cert)) + let keyfile = file.open(bit_array.from_string(key)) + + let _ = case certfile, keyfile { + Error(_), Error(_) -> panic as "Certificate and key file not found" + Ok(_), Error(_) -> panic as "Key file not found" + Error(_), Ok(_) -> panic as "Certificate file not found" + Ok(_), Ok(_) -> Nil + } + + Builder(..builder, tls_options: Some(CertKeyFiles(cert, key))) +} + +fn convert_body_types( + resp: Response(ResponseData), +) -> Response(InternalResponseData) { + let new_body = case resp.body { + Websocket -> InternalWebsocket + Bytes(data) -> InternalBytes(data) + File(descriptor, offset, length) -> InternalFile(descriptor, offset, length) + Chunked -> InternalChunked + ServerSentEvents -> InternalServerSentEvents + } + response.set_body(resp, new_body) +} + +pub type Port { + Assigned + Provided(Int) +} + +/// Start a `mist` service with the provided builder. +pub fn start( + builder: Builder(Connection, ResponseData), +) -> Result(actor.Started(Supervisor), actor.StartError) { + let listener_name = process.new_name("glisten_listener") + let factory_name = process.new_name("mist_factory_supervisor") + + supervisor.new(strategy: supervisor.OneForOne) + |> supervisor.add( + supervision.supervisor(fn() { + fn(req) { convert_body_types(builder.handler(req)) } + |> handler.with_func(factory_name) + |> glisten.new(handler.init, _) + |> glisten.bind(builder.interface) + |> fn(handler) { + case builder.ipv6_support { + True -> glisten.with_ipv6(handler) + False -> handler + } + } + |> fn(handler) { + case builder.tls_options { + Some(CertKeyFiles(certfile, keyfile)) -> + handler + |> glisten.with_tls(certfile, keyfile) + _ -> handler + } + } + |> glisten.with_listener_name(listener_name) + |> glisten.start(builder.port) + |> result.map(fn(server) { + let info = glisten.get_server_info(listener_name, 5000) + let ip_address = to_mist_ip_address(info.ip_address) + let scheme = case option.is_some(builder.tls_options) { + True -> Https + False -> Http + } + builder.after_start(info.port, scheme, ip_address) + server + }) + }), + ) + |> supervisor.add( + supervision.supervisor(fn() { + factory.worker_child(fn(start) { start() }) + |> factory.named(factory_name) + |> factory.restart_strategy(supervision.Temporary) + |> factory.start() + }), + ) + |> supervisor.start() +} + +/// Start the `mist` supervisor as a child of a supervision tree. +pub fn supervised( + builder: Builder(Connection, ResponseData), +) -> ChildSpecification(Supervisor) { + supervision.supervisor(fn() { start(builder) }) +} + +/// These are the types of messages that a websocket handler may receive. +pub type WebsocketMessage(custom) { + Text(String) + Binary(BitArray) + Closed + Shutdown + Custom(custom) +} + +fn internal_to_public_ws_message( + msg: HandlerMessage(custom), +) -> Result(WebsocketMessage(custom), Nil) { + case msg { + Internal(Data(TextFrame(data))) -> { + data + |> bit_array.to_string + |> result.map(Text) + } + Internal(Data(BinaryFrame(data))) -> Ok(Binary(data)) + User(msg) -> Ok(Custom(msg)) + _ -> Error(Nil) + } +} + +/// Upgrade a request to handle websockets. If the request is +/// malformed, or the websocket process fails to initialize, an empty +/// 400 response will be sent to the client. +/// +/// The `on_init` method will be called when the actual WebSocket process +/// is started, and the return value is the initial state and an optional +/// selector for receiving user messages. +/// +/// The `on_close` method is called when the WebSocket process shuts down +/// for any reason, valid or otherwise. +pub fn websocket( + request request: Request(Connection), + handler handler: fn(state, WebsocketMessage(message), WebsocketConnection) -> + Next(state, message), + on_init on_init: fn(WebsocketConnection) -> + #(state, Option(process.Selector(message))), + on_close on_close: fn(state) -> Nil, +) -> Response(ResponseData) { + let handler = fn(state, message, connection) { + message + |> internal_to_public_ws_message + |> result.map(handler(state, _, connection)) + |> result.unwrap(continue(state)) + |> convert_next + } + let extensions = + request + |> request.get_header("sec-websocket-extensions") + |> result.map(fn(header) { string.split(header, ";") }) + |> result.unwrap([]) + + let socket = request.body.socket + let transport = request.body.transport + case http.upgrade(socket, transport, extensions, request) { + Ok(_nil) -> { + let start = fn() { + websocket.initialize_connection( + on_init, + on_close, + handler, + socket, + transport, + extensions, + ) + } + + let factory_supervisor = factory.get_by_name(request.body.factory_name) + case factory.start_child(factory_supervisor, start) { + Ok(started) -> { + let assert Ok(_) = + transport.controlling_process(transport, socket, started.data) + websocket.set_active(transport, socket) + response.new(200) |> response.set_body(Websocket) + } + Error(start_error) -> { + let msg = case start_error { + actor.InitTimeout -> "init timed out" + actor.InitFailed(reason) -> "init failed: " <> reason + actor.InitExited(process.Normal) -> "init exited normally" + actor.InitExited(process.Killed) -> "init killed" + // TODO: provide reason maybe??? + actor.InitExited(process.Abnormal(..)) -> "init exited abnormally" + } + logging.log( + logging.Error, + "Failed to start WebSocket process: " <> msg, + ) + response.new(400) + |> response.set_body(Bytes(bytes_tree.new())) + } + } + } + Error(_reason) -> { + response.new(400) + |> response.set_body(Bytes(bytes_tree.new())) + } + } +} + +pub type WebsocketConnection = + InternalWebsocketConnection + +/// Sends a binary frame across the websocket. +pub fn send_binary_frame( + connection: WebsocketConnection, + frame: BitArray, +) -> Result(Nil, glisten.SocketReason) { + let binary_frame = + exception.rescue(fn() { + gramps_websocket.encode_binary_frame(frame, connection.deflate, None) + }) + case binary_frame { + Ok(binary_frame) -> { + transport.send(connection.transport, connection.socket, binary_frame) + } + Error(_reason) -> { + logging.log( + logging.Error, + "Cannot send messages from a different process than the WebSocket", + ) + panic as "Exiting due to sending WebSocket message from non-owning process" + } + } +} + +/// Sends a text frame across the websocket. +pub fn send_text_frame( + connection: WebsocketConnection, + frame: String, +) -> Result(Nil, glisten.SocketReason) { + let text_frame = + exception.rescue(fn() { + gramps_websocket.encode_text_frame(frame, connection.deflate, None) + }) + case text_frame { + Ok(text_frame) -> { + transport.send(connection.transport, connection.socket, text_frame) + } + Error(_reason) -> { + logging.log( + logging.Error, + "Cannot send messages from a different process than the WebSocket", + ) + panic as "Exiting due to sending WebSocket message from non-owning process" + } + } +} + +// Returned by `init_server_sent_events`. This type must be passed to +// `send_event` since we need to enforce that the correct headers / data shape +// is provided. +pub opaque type SSEConnection { + SSEConnection(Connection) +} + +// Represents each event. Only `data` is required. The `event` name will +// default to `message`. If an `id` is provided, it will be included in the +// event received by the client. `retry` is the minimum time in milliseconds +// the client needs to wait before trying to reestablish the connection. +pub opaque type SSEEvent { + SSEEvent( + id: Option(String), + event: Option(String), + retry: Option(Int), + data: StringTree, + ) +} + +// Builder for generating the base event +pub fn event(data: StringTree) -> SSEEvent { + SSEEvent(id: None, event: None, retry: None, data: data) +} + +// Adds an `id` to the event +pub fn event_id(event: SSEEvent, id: String) -> SSEEvent { + SSEEvent(..event, id: Some(id)) +} + +// Sets the `event` name field +pub fn event_name(event: SSEEvent, name: String) -> SSEEvent { + SSEEvent(..event, event: Some(name)) +} + +// Sets the `retry` reconnection time field in milliseconds +pub fn event_retry(event: SSEEvent, retry: Int) -> SSEEvent { + SSEEvent(..event, retry: Some(retry)) +} + +/// Sets up the connection for server-sent events. The initial response provided +/// here will have its headers included in the SSE setup. The body is discarded. +/// The `init` and `loop` parameters follow the same shape as the +/// `gleam/otp/actor` module. +/// +/// NOTE: There is no proper way within the spec for the server to "close" the +/// SSE connection. There are ways around it. +/// +/// See: `examples/eventz` for a sample usage. +pub fn server_sent_events( + request req: Request(Connection), + initial_response resp: Response(discard), + init init: fn(Subject(message)) -> state, + loop loop: fn(state, message, SSEConnection) -> actor.Next(state, message), +) -> Response(ResponseData) { + let with_default_headers = + resp + |> response.set_header("content-type", "text/event-stream") + |> response.set_header("cache-control", "no-cache") + |> response.set_header("connection", "keep-alive") + + case + transport.send( + req.body.transport, + req.body.socket, + encoder.response_builder(200, with_default_headers.headers, "1.1"), + ) + { + Ok(_nil) -> { + let start = fn() { + actor.new_with_initialiser(1000, fn(subj) { + init(subj) + |> actor.initialised + |> actor.returning(process.self()) + |> actor.selecting(process.new_selector() |> process.select(subj)) + |> Ok + }) + |> actor.on_message(fn(state, message) { + loop(state, message, SSEConnection(req.body)) + }) + |> actor.start + |> result.map(fn(started) { + let pid = started.data + actor.Started(pid, pid) + }) + } + let factory_supervisor = factory.get_by_name(req.body.factory_name) + case factory.start_child(factory_supervisor, start) { + Ok(started) -> { + let assert Ok(_nil) = + transport.controlling_process( + req.body.transport, + req.body.socket, + started.data, + ) + + response.new(200) |> response.set_body(ServerSentEvents) + } + Error(_start_error) -> { + logging.log(logging.Error, "Failed to start SSE process") + response.new(400) + |> response.set_body(Bytes(bytes_tree.new())) + } + } + } + Error(_nil) -> { + response.new(400) + |> response.set_body(Bytes(bytes_tree.new())) + } + } +} + +// This constructs an event from the provided type. If `id`, `event` or `retry` are +// provided, they are included in the message. The data provided is split +// across newlines, which I think is per the spec? The `Result` returned here +// can be used to determine whether the event send has succeeded. +pub fn send_event(conn: SSEConnection, event: SSEEvent) -> Result(Nil, Nil) { + let SSEConnection(conn) = conn + let id = + event.id + |> option.map(fn(id) { "id: " <> id <> "\n" }) + |> option.unwrap("") + let event_name = + event.event + |> option.map(fn(name) { "event: " <> name <> "\n" }) + |> option.unwrap("") + let retry = + event.retry + |> option.map(fn(retry) { "retry: " <> int.to_string(retry) <> "\n" }) + |> option.unwrap("") + let data = + event.data + |> string_tree.split("\n") + |> list.map(fn(row) { string_tree.prepend(row, "data: ") }) + |> string_tree.join("\n") + + let message = + data + |> string_tree.prepend(event_name) + |> string_tree.prepend(id) + |> string_tree.prepend(retry) + |> string_tree.append("\n\n") + |> bytes_tree.from_string_tree + + transport.send(conn.transport, conn.socket, message) + |> result.replace(Nil) + |> result.replace_error(Nil) +} + +pub type ChunkNext(state) { + ChunkContinue(state: state) + ChunkStop + ChunkAbort(reason: String) +} + +pub fn chunked( + request req: Request(Connection), + response response: Response(discard), + init init: fn(Subject(message)) -> state, + loop loop: fn(state, message, Connection) -> ChunkNext(state), +) -> Response(ResponseData) { + let start = fn() { + actor.new_with_initialiser(1000, fn(subj) { + init(subj) + |> actor.initialised + |> actor.returning(process.self()) + |> actor.selecting(process.new_selector() |> process.select(subj)) + |> Ok + }) + |> actor.on_message(fn(state, message) { + case loop(state, message, req.body) { + ChunkContinue(state) -> actor.continue(state) + ChunkStop -> { + let _ = case send_chunk(req.body, <<>>) { + Ok(_nil) -> Nil + Error(_reason) -> { + logging.log(logging.Debug, "Failed to send final chunk") + } + } + actor.stop() + } + ChunkAbort(reason) -> actor.stop_abnormal(reason) + } + }) + |> actor.start + |> result.map(fn(started) { actor.Started(started.data, started.data) }) + } + + let headers = [#("transfer-encoding", "chunked"), ..response.headers] + let initial_payload = + encoder.response_builder( + response.status, + headers, + http.version_to_string(http.Http11), + ) + + let assert Ok(_nil) = + transport.send(req.body.transport, req.body.socket, initial_payload) + + let factory_supervisor = factory.get_by_name(req.body.factory_name) + + case factory.start_child(factory_supervisor, start) { + Ok(started) -> { + let assert Ok(_controlled) = + transport.controlling_process( + req.body.transport, + req.body.socket, + started.data, + ) + response.new(200) |> response.set_body(Chunked) + } + Error(_start_error) -> { + logging.log(logging.Error, "Failed to start chunked response process") + response.new(400) |> response.set_body(Bytes(bytes_tree.new())) + } + } +} + +pub fn send_chunk(connection: Connection, data: BitArray) -> Result(Nil, Nil) { + let size = bit_array.byte_size(data) + let encoded = + size + |> int_to_hex + |> bytes_tree.from_string + |> bytes_tree.append_string("\r\n") + |> bytes_tree.append(data) + |> bytes_tree.append_string("\r\n") + + transport.send(connection.transport, connection.socket, encoded) + |> result.replace_error(Nil) +} + +pub fn chunk_continue(state: state) -> ChunkNext(state) { + ChunkContinue(state) +} + +pub fn chunk_stop() -> ChunkNext(state) { + ChunkStop +} + +pub fn chunk_stop_abnormal(reason: String) -> ChunkNext(state) { + ChunkAbort(reason) +} + +/// Creates a standard HTTP handler service to pass to `mist.serve` +@external(erlang, "erlang", "integer_to_list") +fn integer_to_list(int int: Int, base base: Int) -> String + +fn int_to_hex(int: Int) -> String { + integer_to_list(int, 16) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/buffer.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/buffer.gleam new file mode 100644 index 0000000..2886784 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/buffer.gleam @@ -0,0 +1,36 @@ +import gleam/bit_array +import gleam/int + +pub type Buffer { + Buffer(remaining: Int, data: BitArray) +} + +pub fn empty() -> Buffer { + Buffer(remaining: 0, data: <<>>) +} + +pub fn new(data: BitArray) -> Buffer { + Buffer(remaining: 0, data: data) +} + +pub fn append(buffer: Buffer, data: BitArray) -> Buffer { + let data_size = bit_array.byte_size(data) + let remaining = int.max(buffer.remaining - data_size, 0) + Buffer(data: <>, remaining: remaining) +} + +pub fn slice(buffer: Buffer, bits: Int) -> #(BitArray, BitArray) { + let bytes = bits * 8 + case buffer.data { + <> -> #(value, rest) + _ -> #(buffer.data, <<>>) + } +} + +pub fn with_capacity(buffer: Buffer, size: Int) -> Buffer { + Buffer(..buffer, remaining: size) +} + +pub fn size(remaining: Int) -> Buffer { + Buffer(data: <<>>, remaining: remaining) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/clock.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/clock.gleam new file mode 100644 index 0000000..dfb2433 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/clock.gleam @@ -0,0 +1,134 @@ +import gleam/erlang/atom +import gleam/erlang/process.{type Pid} +import gleam/int +import gleam/otp/actor +import gleam/result +import gleam/string +import logging + +pub type ClockMessage { + SetTime +} + +type ClockTable { + MistClock +} + +type TableKey { + DateHeader +} + +pub type EtsOpts { + Set + Protected + NamedTable + ReadConcurrency(Bool) +} + +pub fn start(_type, _args) -> Result(Pid, actor.StartError) { + actor.new_with_initialiser(500, fn(subject) { + ets_new(MistClock, [Set, Protected, NamedTable, ReadConcurrency(True)]) + process.send(subject, SetTime) + subject + |> actor.initialised + |> actor.selecting(process.new_selector() |> process.select(subject)) + |> actor.returning(subject) + |> Ok + }) + |> actor.on_message(fn(state, msg) { + case msg { + SetTime -> { + ets_insert(MistClock, #(DateHeader, date())) + process.send_after(state, 1000, SetTime) + actor.continue(state) + } + } + }) + |> actor.start + |> result.map(fn(started) { + // NOTE: This process is explicitly _not_ started with a name, so we can + // safely assert here. + let assert Ok(pid) = process.subject_owner(started.data) + pid + }) +} + +pub fn stop(_state) { + atom.create("ok") +} + +pub fn get_date() -> String { + case ets_lookup_element(MistClock, DateHeader, 2) { + Ok(value) -> value + _ -> { + logging.log(logging.Warning, "Failed to lookup date, re-calculating") + date() + } + } +} + +/// Returns today's date in a format suitable to be used as an http date header +/// (see here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date). +/// +fn date() -> String { + let #(weekday, #(year, month, day), #(hour, minute, second)) = now() + + let weekday = weekday_to_short_string(weekday) + let year = int.to_string(year) |> string.pad_start(to: 4, with: "0") + let month = month_to_short_string(month) + let day = int.to_string(day) |> string.pad_start(to: 2, with: "0") + let hour = int.to_string(hour) |> string.pad_start(to: 2, with: "0") + let minute = int.to_string(minute) |> string.pad_start(to: 2, with: "0") + let second = int.to_string(second) |> string.pad_start(to: 2, with: "0") + + { weekday <> ", " } + <> { day <> " " <> month <> " " <> year <> " " } + <> { hour <> ":" <> minute <> ":" <> second <> " GMT" } +} + +fn weekday_to_short_string(weekday: Int) -> String { + case weekday { + 1 -> "Mon" + 2 -> "Tue" + 3 -> "Wed" + 4 -> "Thu" + 5 -> "Fri" + 6 -> "Sat" + 7 -> "Sun" + _ -> panic as "erlang weekday outside of 1-7 range" + } +} + +fn month_to_short_string(month: Int) -> String { + case month { + 1 -> "Jan" + 2 -> "Feb" + 3 -> "Mar" + 4 -> "Apr" + 5 -> "May" + 6 -> "Jun" + 7 -> "Jul" + 8 -> "Aug" + 9 -> "Sep" + 10 -> "Oct" + 11 -> "Nov" + 12 -> "Dec" + _ -> panic as "erlang month outside of 1-12 range" + } +} + +@external(erlang, "mist_ffi", "now") +fn now() -> #(Int, #(Int, Int, Int), #(Int, Int, Int)) + +@external(erlang, "ets", "new") +fn ets_new(table: ClockTable, opts: List(EtsOpts)) -> ClockTable + +@external(erlang, "ets", "insert") +fn ets_insert(table: ClockTable, value: #(TableKey, String)) -> Nil + +@external(erlang, "mist_ffi", "ets_lookup_element") +fn ets_lookup_element( + table: ClockTable, + key: TableKey, + position: Int, +) -> Result(String, Nil) diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/encoder.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/encoder.gleam new file mode 100644 index 0000000..53f27f4 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/encoder.gleam @@ -0,0 +1,105 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/http.{type Header} +import gleam/http/response.{type Response} +import gleam/int +import gleam/list + +/// Turns an HTTP response into a TCP message +pub fn to_bytes_tree(resp: Response(BytesTree), version: String) -> BytesTree { + resp.status + |> response_builder(resp.headers, version) + |> bytes_tree.append_tree(resp.body) +} + +pub fn response_builder( + status: Int, + headers: List(Header), + version: String, +) -> BytesTree { + let status_string = + status + |> int.to_string + |> bytes_tree.from_string + |> bytes_tree.append(<<" ":utf8>>) + |> bytes_tree.append(status_to_bit_array(status)) + + bytes_tree.new() + |> bytes_tree.append(<<"HTTP/":utf8, version:utf8, " ":utf8>>) + |> bytes_tree.append_tree(status_string) + |> bytes_tree.append(<<"\r\n":utf8>>) + |> bytes_tree.append_tree(encode_headers(headers)) + |> bytes_tree.append(<<"\r\n":utf8>>) +} + +pub fn status_to_bit_array(status: Int) -> BitArray { + // Obviously nowhere near exhaustive... + case status { + 100 -> <<"Continue":utf8>> + 101 -> <<"Switching Protocols":utf8>> + 103 -> <<"Early Hints":utf8>> + 200 -> <<"OK":utf8>> + 201 -> <<"Created":utf8>> + 202 -> <<"Accepted":utf8>> + 203 -> <<"Non-Authoritative Information":utf8>> + 204 -> <<"No Content":utf8>> + 205 -> <<"Reset Content":utf8>> + 206 -> <<"Partial Content":utf8>> + 300 -> <<"Multiple Choices":utf8>> + 301 -> <<"Moved Permanently":utf8>> + 302 -> <<"Found":utf8>> + 303 -> <<"See Other":utf8>> + 304 -> <<"Not Modified":utf8>> + 307 -> <<"Temporary Redirect":utf8>> + 308 -> <<"Permanent Redirect":utf8>> + 400 -> <<"Bad Request":utf8>> + 401 -> <<"Unauthorized":utf8>> + 402 -> <<"Payment Required":utf8>> + 403 -> <<"Forbidden":utf8>> + 404 -> <<"Not Found":utf8>> + 405 -> <<"Method Not Allowed":utf8>> + 406 -> <<"Not Acceptable":utf8>> + 407 -> <<"Proxy Authentication Required":utf8>> + 408 -> <<"Request Timeout":utf8>> + 409 -> <<"Conflict":utf8>> + 410 -> <<"Gone":utf8>> + 411 -> <<"Length Required":utf8>> + 412 -> <<"Precondition Failed":utf8>> + 413 -> <<"Payload Too Large":utf8>> + 414 -> <<"URI Too Long":utf8>> + 415 -> <<"Unsupported Media Type":utf8>> + 416 -> <<"Range Not Satisfiable":utf8>> + 417 -> <<"Expectation Failed":utf8>> + 418 -> <<"I'm a teapot":utf8>> + 422 -> <<"Unprocessable Entity":utf8>> + 425 -> <<"Too Early":utf8>> + 426 -> <<"Upgrade Required":utf8>> + 428 -> <<"Precondition Required":utf8>> + 429 -> <<"Too Many Requests":utf8>> + 431 -> <<"Request Header Fields Too Large":utf8>> + 451 -> <<"Unavailable For Legal Reasons":utf8>> + 500 -> <<"Internal Server Error":utf8>> + 501 -> <<"Not Implemented":utf8>> + 502 -> <<"Bad Gateway":utf8>> + 503 -> <<"Service Unavailable":utf8>> + 504 -> <<"Gateway Timeout":utf8>> + 505 -> <<"HTTP Version Not Supported":utf8>> + 506 -> <<"Variant Also Negotiates":utf8>> + 507 -> <<"Insufficient Storage":utf8>> + 508 -> <<"Loop Detected":utf8>> + 510 -> <<"Not Extended":utf8>> + 511 -> <<"Network Authentication Required":utf8>> + _ -> <<"Unknown HTTP Status":utf8>> + } +} + +pub fn encode_headers(headers: List(Header)) -> BytesTree { + list.fold(headers, bytes_tree.new(), fn(builder, tup) { + let #(header, value) = tup + + builder + |> bytes_tree.append_string(header) + |> bytes_tree.append(<<": ":utf8>>) + |> bytes_tree.append_string(value) + |> bytes_tree.append(<<"\r\n":utf8>>) + }) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/file.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/file.gleam new file mode 100644 index 0000000..cd6fdbc --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/file.gleam @@ -0,0 +1,90 @@ +import gleam/bytes_tree +import gleam/result +import glisten.{type Socket, type SocketReason} +import glisten/transport.{type Transport, Ssl, Tcp} + +pub type FileDescriptor + +pub type FileError { + IsDir + NoAccess + NoEntry + UnknownFileError +} + +pub fn error_to_string(error: FileError) -> String { + case error { + IsDir -> "IsDir" + NoAccess -> "NoAccess" + NoEntry -> "NoEntry" + UnknownFileError -> "UnknownFileError" + } +} + +pub type SendError { + FileErr(FileError) + SocketErr(SocketReason) +} + +pub type File { + File(descriptor: FileDescriptor, file_size: Int) +} + +pub fn stat(filename: BitArray) -> Result(File, FileError) { + filename + |> open + |> result.map(fn(fd) { + let file_size = size(filename) + File(fd, file_size) + }) +} + +pub fn sendfile( + transport: Transport, + file_descriptor file_descriptor: FileDescriptor, + socket socket: Socket, + offset offset: Int, + bytes bytes: Int, + options options: List(a), +) -> Result(Nil, SendError) { + case transport { + Tcp(..) -> { + send_file(file_descriptor, socket, offset, bytes, options) + |> result.map_error(SocketErr) + |> result.replace(Nil) + } + Ssl(..) as transport -> { + pread(file_descriptor, offset, bytes) + |> result.map_error(FileErr) + |> result.try(fn(bits) { + transport.send(transport, socket, bytes_tree.from_bit_array(bits)) + |> result.map_error(SocketErr) + }) + } + } +} + +@external(erlang, "file", "sendfile") +fn send_file( + file_descriptor file_descriptor: FileDescriptor, + socket socket: Socket, + offset offset: Int, + bytes bytes: Int, + options options: List(a), +) -> Result(Nil, SocketReason) + +@external(erlang, "file", "pread") +fn pread( + fd: FileDescriptor, + location: Int, + bytes: Int, +) -> Result(BitArray, FileError) + +@external(erlang, "mist_ffi", "file_open") +pub fn open(file: BitArray) -> Result(FileDescriptor, FileError) + +@external(erlang, "filelib", "file_size") +fn size(path: BitArray) -> Int + +@external(erlang, "mist_ffi", "file_close") +pub fn close(file: FileDescriptor) -> Result(Nil, FileError) diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/handler.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/handler.gleam new file mode 100644 index 0000000..ddf00ee --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/handler.gleam @@ -0,0 +1,174 @@ +import gleam/bytes_tree +import gleam/erlang/process.{type Selector, type Subject} +import gleam/http/response +import gleam/option.{type Option, Some} +import gleam/otp/actor +import gleam/otp/factory_supervisor as factory +import gleam/result +import glisten.{type Loop, Packet, User} +import glisten/transport +import logging +import mist/internal/encoder +import mist/internal/http.{ + type DecodeError, type Handler, Bytes, Chunked, Connection, DiscardPacket, + File, Initial, ServerSentEvents, Websocket, +} +import mist/internal/http/handler as http_handler +import mist/internal/http2 +import mist/internal/http2/handler as http2_handler +import mist/internal/http2/stream.{type SendMessage, Send} + +pub type HandlerError { + InvalidRequest(DecodeError) + NotFound +} + +pub type State { + Http1(state: http_handler.State, self: Subject(SendMessage)) + Http2(state: http2_handler.State) +} + +pub fn new_state(subj: Subject(SendMessage)) -> State { + Http1(http_handler.initial_state(), subj) +} + +pub fn init(_conn) -> #(State, Option(Selector(SendMessage))) { + let subj = process.new_subject() + let selector = + process.new_selector() + |> process.select(subj) + + #(new_state(subj), Some(selector)) +} + +pub fn with_func( + handler: Handler, + factory_name: process.Name( + factory.Message( + fn() -> Result(actor.Started(process.Pid), actor.StartError), + process.Pid, + ), + ), +) -> Loop(State, SendMessage) { + fn(state: State, msg, conn: glisten.Connection(SendMessage)) { + let sender = conn.subject + let conn = + Connection( + body: Initial(<<>>), + socket: conn.socket, + transport: conn.transport, + factory_name:, + ) + + let result = case msg, state { + User(Send(..)), Http1(..) -> { + Error(Error("Attempted to send HTTP/2 response without upgrade")) + } + User(Send(id, resp)), Http2(state) -> { + case resp.body { + Bytes(bytes) -> { + resp + |> response.set_body(bytes) + |> http2.send_bytes_tree(conn, state.send_hpack_context, id) + } + File(..) -> Error("File sending unsupported over HTTP/2") + // TODO: properly error in some fashion for these + Websocket -> Error("WebSocket unsupported for HTTP/2") + Chunked -> Error("Chunked encoding not supported for HTTP/2") + ServerSentEvents -> Error("Server-Sent Events unsupported for HTTP/2") + } + |> result.map(fn(context) { + Http2(http2_handler.send_hpack_context(state, context)) + }) + |> result.map_error(fn(err) { + logging.log(logging.Debug, "Error sending HTTP/2 data: " <> err) + Error(err) + }) + } + Packet(msg), Http1(state, self) -> { + let _ = case state.idle_timer { + Some(t) -> process.cancel_timer(t) + _ -> process.TimerNotFound + } + msg + |> http.parse_request(conn) + |> result.map_error(fn(err) { + case err { + DiscardPacket -> Ok(Nil) + http.MalformedRequest -> { + let msg = "Received malformed HTTP request" + logging.log(logging.Warning, msg) + Error(msg) + } + http.InvalidMethod -> { + let msg = "Received invalid HTTP method" + logging.log(logging.Warning, msg) + Error(msg) + } + http.InvalidPath -> { + let msg = "Received invalid HTTP path" + logging.log(logging.Warning, msg) + Error(msg) + } + http.UnknownHeader -> { + let msg = "Received unknown HTTP header" + logging.log(logging.Warning, msg) + Error(msg) + } + http.UnknownMethod -> { + let msg = "Received unknown HTTP method" + logging.log(logging.Warning, msg) + Error(msg) + } + http.InvalidBody -> { + let msg = "Received invalid HTTP body" + logging.log(logging.Warning, msg) + Error(msg) + } + http.NoHostHeader -> { + let msg = "Missing HTTP `host` header" + logging.log(logging.Warning, msg) + let _ = + response.new(400) + |> response.prepend_header("connection", "close") + |> response.set_body(bytes_tree.new()) + |> encoder.to_bytes_tree("1.1") + |> transport.send(conn.transport, conn.socket, _) + Ok(Nil) + } + http.InvalidHttpVersion -> { + let msg = "Received invalid HTTP version" + logging.log(logging.Warning, msg) + Error(msg) + } + } + }) + |> result.try(fn(req) { + case req { + http.Http1Request(req, version) -> + http_handler.call(req, handler, sender, version) + |> result.map(fn(new_state) { + Http1(state: new_state, self: self) + }) + http.Upgrade(data) -> + http2_handler.upgrade(data, conn, self) + |> result.map(Http2) + |> result.map_error(Error) + } + }) + } + Packet(msg), Http2(state) -> { + state + |> http2_handler.append_data(msg) + |> http2_handler.call(conn, handler) + |> result.map(Http2) + } + } + + case result { + Ok(value) -> glisten.continue(value) + Error(Ok(_nil)) -> glisten.stop() + Error(Error(reason)) -> glisten.stop_abnormal(reason) + } + } +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http.gleam new file mode 100644 index 0000000..d1b7bee --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http.gleam @@ -0,0 +1,680 @@ +import gleam/bit_array +import gleam/bytes_tree.{type BytesTree} +import gleam/dict.{type Dict} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/erlang/atom.{type Atom} +import gleam/erlang/charlist.{type Charlist} +import gleam/erlang/process.{type Selector} +import gleam/http +import gleam/http/request.{type Request} +import gleam/http/response.{type Response, Response} +import gleam/int +import gleam/list +import gleam/option.{type Option} +import gleam/otp/actor +import gleam/otp/factory_supervisor as factory +import gleam/pair +import gleam/result +import gleam/string +import glisten.{type Socket} +import glisten/transport.{type Transport} +import gramps/websocket +import mist/internal/buffer.{type Buffer, Buffer} +import mist/internal/clock +import mist/internal/encoder +import mist/internal/file + +pub type ResponseData { + Websocket + Bytes(BytesTree) + Chunked + File(descriptor: file.FileDescriptor, offset: Int, length: Int) + ServerSentEvents +} + +pub type Connection { + Connection( + body: Body, + socket: Socket, + transport: Transport, + factory_name: process.Name( + factory.Message( + fn() -> Result(actor.Started(process.Pid), actor.StartError), + process.Pid, + ), + ), + ) +} + +pub type Handler = + fn(Request(Connection)) -> response.Response(ResponseData) + +pub type PacketType { + Http + HttphBin + HttpBin +} + +pub type HttpUri { + AbsPath(BitArray) +} + +pub type HttpPacket { + HttpRequest(Dynamic, HttpUri, #(Int, Int)) + HttpHeader(Int, Atom, BitArray, BitArray) +} + +pub type DecodedPacket { + BinaryData(HttpPacket, BitArray) + EndOfHeaders(BitArray) + MoreData(Option(Int)) + Http2Upgrade(BitArray) +} + +pub type DecodeError { + MalformedRequest + InvalidMethod + InvalidPath + UnknownHeader + UnknownMethod + // TODO: better name? + InvalidBody + DiscardPacket + NoHostHeader + InvalidHttpVersion +} + +pub fn from_header(value: BitArray) -> String { + let assert Ok(value) = bit_array.to_string(value) + + string.lowercase(value) +} + +pub fn parse_headers( + bs: BitArray, + socket: Socket, + transport: Transport, + headers: Dict(String, String), +) -> Result(#(Dict(String, String), BitArray), DecodeError) { + case decode_packet(HttphBin, bs, []) { + Ok(BinaryData(HttpHeader(_, _field, field, value), rest)) -> { + let field = from_header(field) + let assert Ok(value) = bit_array.to_string(value) + headers + |> dict.insert(field, value) + |> parse_headers(rest, socket, transport, _) + } + Ok(EndOfHeaders(rest)) -> Ok(#(headers, rest)) + Ok(MoreData(size)) -> { + let amount_to_read = option.unwrap(size, 0) + use next <- result.try(read_data( + socket, + transport, + Buffer(amount_to_read, bs), + UnknownHeader, + )) + parse_headers(next, socket, transport, headers) + } + _other -> Error(UnknownHeader) + } +} + +pub fn read_data( + socket: Socket, + transport: Transport, + buffer: Buffer, + error: DecodeError, +) -> Result(BitArray, DecodeError) { + // TODO: don't hard-code these, probably + let to_read = int.min(buffer.remaining, 1_000_000) + let timeout = 15_000 + use data <- result.try( + socket + |> transport.receive_timeout(transport, _, to_read, timeout) + |> result.replace_error(error), + ) + let next_buffer = + Buffer(remaining: int.max(0, buffer.remaining - to_read), data: << + buffer.data:bits, + data:bits, + >>) + + case next_buffer.remaining > 0 { + True -> read_data(socket, transport, next_buffer, error) + False -> Ok(next_buffer.data) + } +} + +const crnl = <<13:int, 10:int>> + +pub type Chunk { + Chunk(data: BitArray, buffer: Buffer) + Complete +} + +pub fn parse_chunk(string: BitArray) -> Chunk { + case binary_split(string, <<"\r\n":utf8>>) { + [<<"0":utf8>>, _] -> Complete + [chunk_size, rest] -> { + let assert Ok(chunk_size) = bit_array.to_string(chunk_size) + case int.base_parse(chunk_size, 16) { + Ok(size) -> { + let size = size * 8 + case rest { + <> -> { + Chunk(data: next_chunk, buffer: buffer.new(rest)) + } + _ -> { + Chunk(data: <<>>, buffer: buffer.new(string)) + } + } + } + Error(_) -> { + Chunk(data: <<>>, buffer: buffer.new(string)) + } + } + } + + _ -> { + Chunk(data: <<>>, buffer: buffer.new(string)) + } + } +} + +// TODO: use `parse_chunk` for this +fn read_chunk( + socket: Socket, + transport: Transport, + buffer: Buffer, + body: BytesTree, +) -> Result(BytesTree, DecodeError) { + case buffer.data, binary_match(buffer.data, crnl) { + _, Ok(#(offset, _)) -> { + let assert << + chunk:bytes-size(offset), + _return:int, + _newline:int, + rest:bytes, + >> = buffer.data + use chunk_size <- result.try( + chunk + |> bit_array.to_string + |> result.map(charlist.from_string) + |> result.replace_error(InvalidBody), + ) + use size <- result.try( + string_to_int(chunk_size, 16) + |> result.replace_error(InvalidBody), + ) + case size { + 0 -> Ok(body) + size -> + case rest { + <> -> + read_chunk( + socket, + transport, + Buffer(0, rest), + bytes_tree.append(body, next_chunk), + ) + _ -> { + use next <- result.try(read_data( + socket, + transport, + Buffer(0, buffer.data), + InvalidBody, + )) + read_chunk(socket, transport, Buffer(0, next), body) + } + } + } + } + <<>> as data, _ | data, Error(Nil) -> { + use next <- result.try(read_data( + socket, + transport, + Buffer(0, data), + InvalidBody, + )) + read_chunk(socket, transport, Buffer(0, next), body) + } + } +} + +pub type HttpVersion { + Http1 + Http11 +} + +pub fn version_to_string(version: HttpVersion) { + case version { + Http1 -> "1.0" + Http11 -> "1.1" + } +} + +pub type ParsedRequest { + Http1Request(request: request.Request(Connection), version: HttpVersion) + Upgrade(BitArray) +} + +@external(erlang, "mist_ffi", "decode_atom") +fn decode_atom(value: Dynamic) -> Result(atom.Atom, Nil) + +fn decode_http_method(value: Dynamic) -> Result(http.Method, Nil) { + let options = atom.create("OPTIONS") + let get = atom.create("GET") + let head = atom.create("HEAD") + let post = atom.create("POST") + let put = atom.create("PUT") + let delete = atom.create("DELETE") + let trace = atom.create("TRACE") + + case decode_atom(value) { + Ok(method) if method == options -> Ok(http.Options) + Ok(method) if method == get -> Ok(http.Get) + Ok(method) if method == head -> Ok(http.Head) + Ok(method) if method == post -> Ok(http.Post) + Ok(method) if method == put -> Ok(http.Put) + Ok(method) if method == delete -> Ok(http.Delete) + Ok(method) if method == trace -> Ok(http.Trace) + _ -> { + case decode.run(value, decode.string) { + Ok(str) -> http.parse_method(str) + _ -> Error(Nil) + } + } + } +} + +/// Turns the TCP message into an HTTP request +pub fn parse_request( + bs: BitArray, + conn: Connection, +) -> Result(ParsedRequest, DecodeError) { + case decode_packet(HttpBin, bs, []) { + Ok(BinaryData(HttpRequest(http_method, AbsPath(path), version), rest)) -> { + use method <- result.try( + http_method + |> decode_http_method + |> result.replace_error(UnknownMethod), + ) + use #(headers, rest) <- result.try(parse_headers( + rest, + conn.socket, + conn.transport, + dict.new(), + )) + use path <- result.try( + path + |> bit_array.to_string + |> result.replace_error(InvalidPath), + ) + use #(path, query) <- result.try( + get_path_and_query(path) + |> result.replace_error(InvalidPath), + ) + let scheme = case conn.transport { + transport.Ssl(..) -> http.Https + transport.Tcp(..) -> http.Http + } + use host_header <- result.try( + dict.get(headers, "host") + |> result.replace_error(NoHostHeader), + ) + let #(hostname, port) = + host_header + |> string.split_once(":") + |> result.unwrap(#(host_header, "")) + + let port = case int.parse(port) { + Ok(port) -> port + Error(_) -> + case scheme { + http.Https -> 443 + http.Http -> 80 + } + } + + let req = + request.Request( + body: Connection(..conn, body: Initial(rest)), + headers: dict.to_list(headers), + host: hostname, + method: method, + path: path, + port: option.Some(port), + query: option.from_result(query), + scheme: scheme, + ) + case version { + #(1, 0) -> Ok(Http1Request(request: req, version: Http1)) + #(1, 1) -> Ok(Http1Request(request: req, version: Http11)) + _ -> Error(InvalidHttpVersion) + } + } + // "\r\nSM\r\n\r\n" + Ok(Http2Upgrade(<< + 13:int, + 10:int, + 83:int, + 77:int, + 13:int, + 10:int, + 13:int, + 10:int, + data:bits, + >>)) -> { + Ok(Upgrade(data)) + } + Ok(MoreData(size)) -> { + let amount_to_read = option.unwrap(size, 0) + use next <- result.try(read_data( + conn.socket, + conn.transport, + Buffer(amount_to_read, bs), + MalformedRequest, + )) + parse_request(next, conn) + } + _ -> Error(DiscardPacket) + } +} + +pub type Body { + Initial(BitArray) + Stream( + selector: Selector(BitArray), + data: BitArray, + remaining: Int, + attempts: Int, + ) +} + +pub fn read_body( + req: Request(Connection), +) -> Result(Request(BitArray), DecodeError) { + let transport = case req.scheme { + http.Https -> transport.Ssl + http.Http -> transport.Tcp + } + case request.get_header(req, "transfer-encoding"), req.body.body { + Ok("chunked"), Initial(rest) -> { + use _nil <- result.try(handle_continue(req)) + + use chunk <- result.try(read_chunk( + req.body.socket, + transport, + Buffer(remaining: 0, data: rest), + bytes_tree.new(), + )) + Ok(request.set_body(req, bytes_tree.to_bit_array(chunk))) + } + _, Initial(rest) -> { + use _nil <- result.try(handle_continue(req)) + let body_size = + req.headers + |> list.find(fn(tup) { pair.first(tup) == "content-length" }) + |> result.map(pair.second) + |> result.try(int.parse) + |> result.unwrap(0) + let remaining = body_size - bit_array.byte_size(rest) + case body_size, remaining { + 0, 0 -> Ok(<<>>) + 0, _n -> Ok(rest) + // is this pipelining? check for GET? + _n, 0 -> Ok(rest) + _size, _rem -> + read_data( + req.body.socket, + transport, + Buffer(remaining, rest), + InvalidBody, + ) + } + |> result.map(request.set_body(req, _)) + |> result.replace_error(InvalidBody) + } + _, + Stream( + selector: selector, + data: data, + remaining: remaining, + attempts: attempts, + ) + if remaining > 0 + -> { + let res = + selector + |> process.selector_receive(1000) + |> result.replace_error(InvalidBody) + use next <- result.try(res) + let got = bit_array.byte_size(next) + let left = int.max(remaining - got, 0) + let new_data = bit_array.append(data, next) + case left { + 0 -> Ok(request.set_body(req, new_data)) + _rem -> + read_body(request.set_body( + req, + Connection( + ..req.body, + body: Stream(selector, new_data, left, attempts + 1), + ), + )) + } + } + _, Stream(data: data, ..) -> { + Ok(request.set_body(req, data)) + } + } +} + +const websocket_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +pub type ShaHash { + Sha +} + +fn parse_websocket_key(key: String) -> String { + key + |> string.append(websocket_key) + |> crypto_hash(Sha, _) + |> base64_encode +} + +pub fn upgrade_socket( + req: Request(Connection), + extensions: List(String), +) -> Result(Response(BytesTree), Request(Connection)) { + use _upgrade <- result.try( + request.get_header(req, "upgrade") + |> result.replace_error(req), + ) + use key <- result.try( + request.get_header(req, "sec-websocket-key") + |> result.replace_error(req), + ) + use _version <- result.try( + request.get_header(req, "sec-websocket-version") + |> result.replace_error(req), + ) + + let permessage_deflate = websocket.has_deflate(extensions) + + let accept_key = parse_websocket_key(key) + + let resp = + response.new(101) + |> response.set_body(bytes_tree.new()) + |> response.prepend_header("upgrade", "websocket") + |> response.prepend_header("connection", "Upgrade") + |> response.prepend_header("sec-websocket-accept", accept_key) + + case permessage_deflate { + True -> + Ok(response.prepend_header( + resp, + "sec-websocket-extensions", + "permessage-deflate", + )) + False -> Ok(resp) + } +} + +// TODO: improve this error type +pub fn upgrade( + socket: Socket, + transport: Transport, + extensions: List(String), + req: Request(Connection), +) -> Result(Nil, Nil) { + use resp <- result.try( + upgrade_socket(req, extensions) + |> result.replace_error(Nil), + ) + + use _sent <- result.try( + resp + |> add_default_headers(req.method == http.Head) + |> maybe_keep_alive + |> encoder.to_bytes_tree("1.1") + |> transport.send(transport, socket, _) + |> result.replace_error(Nil), + ) + + Ok(Nil) +} + +pub fn add_date_header(resp: Response(any)) -> Response(any) { + case response.get_header(resp, "date") { + Error(_nil) -> response.set_header(resp, "date", clock.get_date()) + _ -> resp + } +} + +pub fn connection_close(resp: Response(any)) -> Response(any) { + response.set_header(resp, "connection", "close") +} + +pub fn keep_alive(resp: Response(any)) -> Response(any) { + response.set_header(resp, "connection", "keep-alive") +} + +pub fn maybe_keep_alive(resp: Response(any)) -> Response(any) { + case response.get_header(resp, "connection") { + Ok(_) -> resp + _ -> response.set_header(resp, "connection", "keep-alive") + } +} + +fn maybe_drop_body( + resp: Response(BytesTree), + is_head_request: Bool, +) -> Response(BytesTree) { + case is_head_request { + True -> response.set_body(resp, bytes_tree.new()) + False -> resp + } +} + +pub fn add_content_length( + when when: Bool, + length length: Int, +) -> fn(Response(any)) -> Response(any) { + fn(resp: Response(any)) { + case when { + True -> { + let #(_existing, headers) = + resp.headers + |> list.key_pop("content-length") + |> result.lazy_unwrap(fn() { #("", resp.headers) }) + + Response(..resp, headers: headers) + |> response.set_header("content-length", int.to_string(length)) + } + False -> resp + } + } +} + +pub fn add_default_headers( + resp: Response(BytesTree), + is_head_response: Bool, +) -> Response(BytesTree) { + let body_size = bytes_tree.byte_size(resp.body) + let #(_existing_content_length, headers) = + resp.headers + |> list.key_pop("content-length") + |> result.lazy_unwrap(fn() { #("", resp.headers) }) + + let resp = case resp.status, body_size { + // explicitly drop + n, _ if n >= 100 && n <= 199 -> Response(..resp, headers:) + // explicitly drop + n, _ if n == 204 -> Response(..resp, headers:) + // don't add, don't drop + n, 0 if n == 304 -> resp + // don't add, don't drop + _, 0 if is_head_response == True -> resp + // explicitly overwrite + _, _ -> + response.set_header(resp, "content-length", int.to_string(body_size)) + } + + resp + |> add_date_header + |> maybe_drop_body(is_head_response) +} + +fn is_continue(req: Request(Connection)) -> Bool { + req.headers + |> list.find(fn(tup) { + pair.first(tup) == "expect" && pair.second(tup) == "100-continue" + }) + |> result.is_ok +} + +pub fn handle_continue(req: Request(Connection)) -> Result(Nil, DecodeError) { + case is_continue(req) { + True -> { + response.new(100) + |> response.set_body(bytes_tree.new()) + |> encoder.to_bytes_tree("1.1") + |> transport.send(req.body.transport, req.body.socket, _) + |> result.replace_error(MalformedRequest) + } + False -> Ok(Nil) + } +} + +@external(erlang, "mist_ffi", "decode_packet") +fn decode_packet( + packet_type packet_type: PacketType, + packet packet: BitArray, + options options: List(a), +) -> Result(DecodedPacket, DecodeError) + +@external(erlang, "crypto", "hash") +pub fn crypto_hash(hash hash: ShaHash, data data: String) -> String + +@external(erlang, "base64", "encode") +pub fn base64_encode(data data: String) -> String + +@external(erlang, "mist_ffi", "binary_match") +fn binary_match( + source source: BitArray, + pattern pattern: BitArray, +) -> Result(#(Int, Int), Nil) + +@external(erlang, "mist_ffi", "string_to_int") +fn string_to_int(string string: Charlist, base base: Int) -> Result(Int, Nil) + +@external(erlang, "binary", "split") +fn binary_split(source: BitArray, pattern: BitArray) -> List(BitArray) + +@external(erlang, "mist_ffi", "get_path_and_query") +fn get_path_and_query( + str: String, +) -> Result(#(String, Result(String, Nil)), #(value, term)) diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http/handler.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http/handler.gleam new file mode 100644 index 0000000..d889241 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http/handler.gleam @@ -0,0 +1,197 @@ +import exception +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/process.{type Subject} +import gleam/http as ghttp +import gleam/http/request.{type Request} +import gleam/http/response +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string +import glisten/internal/handler.{Close, Internal} +import glisten/socket.{type Socket, type SocketReason, Badarg} +import glisten/transport.{type Transport} +import logging +import mist/internal/encoder +import mist/internal/file +import mist/internal/http.{ + type Connection, type Handler, type ResponseData, Bytes, Chunked, File, + ServerSentEvents, Websocket, +} + +pub type State { + State(idle_timer: Option(process.Timer)) +} + +pub fn initial_state() -> State { + State(idle_timer: None) +} + +pub fn call( + req: Request(Connection), + handler: Handler, + sender: Subject(handler.Message(user_message)), + version: http.HttpVersion, +) -> Result(State, Result(Nil, String)) { + exception.rescue(fn() { handler(req) }) + |> result.map_error(log_and_error( + _, + req.body.socket, + req.body.transport, + req, + version, + )) + |> result.try(fn(resp) { + case resp { + response.Response(body: Websocket, ..) + | response.Response(body: ServerSentEvents, ..) + | response.Response(body: Chunked, ..) -> { + Error(Ok(Nil)) + } + response.Response(body: body, ..) as resp -> { + case body { + Bytes(body) -> + handle_bytes_tree_body(resp, body, req.body, req, version) + File(..) -> handle_file_body(resp, body, req.body, version) + _ -> panic as "This shouldn't ever happen 🤞" + } + |> result.replace_error(Ok(Nil)) + |> result.try(close_or_set_timer(_, req.body, sender)) + } + } + }) +} + +fn log_and_error( + error: exception.Exception, + socket: Socket, + transport: Transport, + req: Request(Connection), + version: http.HttpVersion, +) -> Result(Nil, String) { + let error_string = string.inspect(error) + logging.log(logging.Error, error_string) + let resp = + response.new(500) + |> response.set_body( + bytes_tree.from_bit_array(<<"Internal Server Error":utf8>>), + ) + |> response.prepend_header("content-length", "21") + |> http.add_default_headers(req.method == ghttp.Head) + + let resp = case version { + http.Http1 -> http.connection_close(resp) + _ -> http.maybe_keep_alive(resp) + } + + let _ = + resp + |> encoder.to_bytes_tree(http.version_to_string(version)) + |> transport.send(transport, socket, _) + + let _ = transport.close(transport, socket) + Error(error_string) +} + +fn close_or_set_timer( + resp: response.Response(BytesTree), + conn: Connection, + sender: Subject(handler.Message(user_message)), +) -> Result(State, Result(Nil, String)) { + // If the handler explicitly says to close the connection, we should + // probably listen to them + case response.get_header(resp, "connection") { + Ok("close") -> { + let _ = transport.close(conn.transport, conn.socket) + Error(Ok(Nil)) + } + _ -> { + // TODO: this should be a configuration + let timer = process.send_after(sender, 10_000, Internal(Close)) + Ok(State(idle_timer: Some(timer))) + } + } +} + +fn handle_file_body( + resp: response.Response(ResponseData), + body: ResponseData, + conn: Connection, + http_version: http.HttpVersion, +) -> Result(response.Response(BytesTree), SocketReason) { + let assert File(file_descriptor, offset, length) = body + let resp = + resp + |> response.set_body(bytes_tree.new()) + |> http.add_date_header + |> response.prepend_header("content-length", int.to_string(length)) + + let resp = case http_version { + http.Http1 -> http.connection_close(resp) + _ -> http.maybe_keep_alive(resp) + } + + let return = + resp + |> fn(r: response.Response(BytesTree)) { + encoder.response_builder( + resp.status, + r.headers, + http.version_to_string(http_version), + ) + } + |> transport.send(conn.transport, conn.socket, _) + |> result.try(fn(_) { + file.sendfile( + conn.transport, + file_descriptor, + conn.socket, + offset, + length, + [], + ) + |> result.map_error(fn(err) { + logging.log( + logging.Error, + "Failed to send file: " <> string.inspect(err), + ) + Badarg + }) + }) + |> result.replace(resp) + + case file.close(file_descriptor) { + Ok(_nil) -> Nil + Error(reason) -> { + logging.log( + logging.Error, + "Failed to close file: " <> file.error_to_string(reason), + ) + } + } + + return +} + +fn handle_bytes_tree_body( + resp: response.Response(ResponseData), + body: BytesTree, + conn: Connection, + req: Request(Connection), + version: http.HttpVersion, +) -> Result(response.Response(BytesTree), SocketReason) { + let resp = + resp + |> response.set_body(body) + |> http.add_default_headers(req.method == ghttp.Head) + + let resp = case version { + http.Http1 -> http.connection_close(resp) + _ -> http.maybe_keep_alive(resp) + } + + resp + |> encoder.to_bytes_tree(http.version_to_string(version)) + |> transport.send(conn.transport, conn.socket, _) + |> result.replace(resp) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http2.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http2.gleam new file mode 100644 index 0000000..ca8458b --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http2.gleam @@ -0,0 +1,175 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/http.{type Header} as _ghttp +import gleam/http/response.{type Response} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import glisten/socket.{type Socket, type SocketReason} +import glisten/transport.{type Transport} +import logging +import mist/internal/http.{type Connection} +import mist/internal/http2/frame.{ + type Frame, type PushState, type Setting, type StreamIdentifier, Complete, + Data, Header, +} + +pub type Http2Settings { + Http2Settings( + header_table_size: Int, + server_push: PushState, + max_concurrent_streams: Int, + initial_window_size: Int, + max_frame_size: Int, + max_header_list_size: Option(Int), + ) +} + +pub fn default_settings() -> Http2Settings { + Http2Settings( + header_table_size: 4096, + server_push: frame.Disabled, + max_concurrent_streams: 100, + initial_window_size: 65_535, + max_frame_size: 16_384, + max_header_list_size: None, + ) +} + +pub fn update_settings( + current: Http2Settings, + settings: List(Setting), +) -> Http2Settings { + list.fold(settings, current, fn(settings, setting) { + case setting { + frame.HeaderTableSize(size) -> + Http2Settings(..settings, header_table_size: size) + frame.ServerPush(push) -> Http2Settings(..settings, server_push: push) + frame.MaxConcurrentStreams(max) -> + Http2Settings(..settings, max_concurrent_streams: max) + frame.InitialWindowSize(size) -> + Http2Settings(..settings, initial_window_size: size) + frame.MaxFrameSize(size) -> + Http2Settings(..settings, max_frame_size: size) + frame.MaxHeaderListSize(size) -> + Http2Settings(..settings, max_header_list_size: Some(size)) + } + }) +} + +fn send_headers( + context: HpackContext, + conn: Connection, + headers: List(Header), + end_stream: Bool, + stream_identifier: StreamIdentifier(Frame), +) -> Result(HpackContext, String) { + hpack_encode(context, headers) + |> result.try(fn(pair) { + let #(headers, new_context) = pair + let header_frame = + Header( + data: Complete(headers), + end_stream: end_stream, + identifier: stream_identifier, + priority: None, + ) + let encoded = frame.encode(header_frame) + case + transport.send( + conn.transport, + conn.socket, + bytes_tree.from_bit_array(encoded), + ) + { + Ok(_nil) -> Ok(new_context) + Error(_reason) -> Error("Failed to send HTTP/2 headers") + } + }) +} + +fn send_data( + conn: Connection, + data: BitArray, + stream_identifier: StreamIdentifier(Frame), + end_stream: Bool, +) -> Result(Nil, String) { + let data_frame = + Data(data: data, end_stream: end_stream, identifier: stream_identifier) + let encoded = frame.encode(data_frame) + + transport.send( + conn.transport, + conn.socket, + bytes_tree.from_bit_array(encoded), + ) + |> result.map_error(fn(err) { + logging.log( + logging.Debug, + "failed to send :( " <> socket.reason_to_string(err), + ) + "Failed to send HTTP/2 data" + }) +} + +// TODO: handle max frame size +pub fn send_frame( + frame_to_send: Frame, + socket: Socket, + transport: Transport, +) -> Result(Nil, SocketReason) { + let data = frame.encode(frame_to_send) + + transport.send(transport, socket, bytes_tree.from_bit_array(data)) +} + +pub fn send_bytes_tree( + resp: Response(BytesTree), + conn: Connection, + context: HpackContext, + id: StreamIdentifier(Frame), +) -> Result(HpackContext, String) { + let resp = + resp + |> http.add_default_headers(False) + + let headers = [#(":status", int.to_string(resp.status)), ..resp.headers] + + case bytes_tree.byte_size(resp.body) { + 0 -> send_headers(context, conn, headers, True, id) + _ -> { + send_headers(context, conn, headers, False, id) + |> result.try(fn(context) { + // TODO: this should be broken up by window size + // TODO: fix end_stream + send_data(conn, bytes_tree.to_bit_array(resp.body), id, True) + |> result.replace(context) + }) + } + } +} + +pub type HpackContext + +@external(erlang, "hpack", "new_context") +pub fn hpack_new_context(size: Int) -> HpackContext + +@external(erlang, "mist_ffi", "hpack_new_max_table_size") +pub fn hpack_max_table_size(context: HpackContext, size: Int) -> HpackContext + +pub type HpackError { + Compression + BadHeaderPacket(BitArray) +} + +@external(erlang, "mist_ffi", "hpack_decode") +pub fn hpack_decode( + context: HpackContext, + bin: BitArray, +) -> Result(#(List(Header), HpackContext), HpackError) + +@external(erlang, "mist_ffi", "hpack_encode") +pub fn hpack_encode( + context: HpackContext, + headers: List(Header), +) -> Result(#(BitArray, HpackContext), error) diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http2/flow_control.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/flow_control.gleam new file mode 100644 index 0000000..a902eb5 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/flow_control.gleam @@ -0,0 +1,37 @@ +import gleam/int + +pub fn compute_receive_window( + receive_window_size: Int, + data_size: Int, +) -> #(Int, Int) { + let new_receive_window_size = receive_window_size - data_size + + let max_window_increment = int.bitwise_shift_left(1, 31) - 1 + let max_window_size = max_window_increment + let min_window_size = int.bitwise_shift_left(1, 30) + + case new_receive_window_size > min_window_size { + True -> { + #(new_receive_window_size, 0) + } + False -> { + let updated_receive_window_size = + int.min(new_receive_window_size + max_window_increment, max_window_size) + let increment = updated_receive_window_size - new_receive_window_size + + #(updated_receive_window_size, increment) + } + } +} + +pub fn update_send_window( + current_send_window: Int, + increment: Int, +) -> Result(Int, String) { + let max_window_size = int.bitwise_shift_left(1, 31) - 1 + let update = current_send_window + increment + case update > max_window_size { + True -> Error("Invalid update increment") + False -> Ok(update) + } +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http2/frame.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/frame.gleam new file mode 100644 index 0000000..bd4d92e --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/frame.gleam @@ -0,0 +1,725 @@ +import gleam/bit_array +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import logging + +pub opaque type StreamIdentifier(phantom) { + StreamIdentifier(Int) +} + +pub fn stream_identifier(value: Int) -> StreamIdentifier(Frame) { + StreamIdentifier(value) +} + +pub fn get_stream_identifier(identifier: StreamIdentifier(phantom)) -> Int { + let StreamIdentifier(value) = identifier + value +} + +pub type HeaderPriority { + HeaderPriority( + exclusive: Bool, + stream_dependency: StreamIdentifier(Frame), + weight: Int, + ) +} + +pub type Data { + Complete(BitArray) + Continued(BitArray) +} + +pub type PushState { + Enabled + Disabled +} + +pub type Setting { + HeaderTableSize(Int) + ServerPush(PushState) + MaxConcurrentStreams(Int) + InitialWindowSize(Int) + MaxFrameSize(Int) + MaxHeaderListSize(Int) +} + +pub type Frame { + Data(data: BitArray, end_stream: Bool, identifier: StreamIdentifier(Frame)) + + Header( + data: Data, + end_stream: Bool, + identifier: StreamIdentifier(Frame), + priority: Option(HeaderPriority), + ) + + Priority( + exclusive: Bool, + identifier: StreamIdentifier(Frame), + stream_dependency: StreamIdentifier(Frame), + weight: Int, + ) + + Termination(error: ConnectionError, identifier: StreamIdentifier(Frame)) + + Settings(ack: Bool, settings: List(Setting)) + + PushPromise( + data: Data, + identifier: StreamIdentifier(Frame), + promised_stream_id: StreamIdentifier(Frame), + ) + + Ping(ack: Bool, data: BitArray) + + GoAway( + data: BitArray, + error: ConnectionError, + last_stream_id: StreamIdentifier(Frame), + ) + + WindowUpdate(amount: Int, identifier: StreamIdentifier(Frame)) + + Continuation(data: Data, identifier: StreamIdentifier(Frame)) +} + +pub type ConnectionError { + NoError + ProtocolError + InternalError + FlowControlError + SettingsTimeout + StreamClosed + FrameSizeError + RefusedStream + Cancel + CompressionError + ConnectError + EnhanceYourCalm + InadequateSecurity + Http11Required + Unsupported(Int) +} + +pub fn decode(frame: BitArray) -> Result(#(Frame, BitArray), ConnectionError) { + case frame { + << + length:size(24), + frame_type:size(8), + flags:bits-size(8), + _reserved:size(1), + identifier:size(31), + payload:bytes-size(length), + rest:bits, + >> -> { + case frame_type { + 0 -> parse_data(identifier, flags, length, payload) + 1 -> parse_header(identifier, flags, length, payload) + 2 -> parse_priority(identifier, flags, length, payload) + 3 -> parse_termination(identifier, flags, length, payload) + 4 -> parse_settings(identifier, flags, length, payload) + 5 -> parse_push_promise(identifier, flags, length, payload) + 6 -> parse_ping(identifier, flags, length, payload) + 7 -> parse_go_away(identifier, flags, length, payload) + 8 -> parse_window_update(identifier, flags, length, payload) + 9 -> parse_continuation(identifier, flags, length, payload) + _ -> Error(ProtocolError) + } + |> result.map(fn(frame) { #(frame, rest) }) + } + << + length:size(24), + _frame_type:size(8), + _flags:size(8), + _reserved:size(1), + _identifier:size(31), + rest:bits, + >> -> { + case bit_array.byte_size(rest) < length { + True -> Error(NoError) + False -> Error(ProtocolError) + } + } + _ -> Error(ProtocolError) + } +} + +fn parse_data( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case <> { + << + _unused:size(4), + padding:size(1), + _unused:size(2), + end_stream:size(1), + pad_length:size(padding)-unit(8), + data_and_padding:bits, + >> + if identifier != 0 + -> { + let data_length = case padding { + 1 -> length - pad_length + 0 -> length + _ -> panic as "Somehow a bit was neither 0 nor 1" + } + case data_and_padding { + <> -> { + Ok(Data( + data: data, + end_stream: end_stream == 1, + identifier: stream_identifier(identifier), + )) + } + _ -> Error(ProtocolError) + } + } + _ -> Error(ProtocolError) + } +} + +fn parse_header( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case <> { + << + _unused:size(2), + priority:size(1), + _unused:size(1), + padded:size(1), + end_headers:size(1), + _unused:size(1), + end_stream:size(1), + pad_length:size(padded)-unit(8), + exclusive:size(priority), + stream_dependency:size(priority)-unit(31), + weight:size(priority)-unit(8), + data_and_padding:bits, + >> + if identifier != 0 && pad_length < length + -> { + let data_length = case padded, priority { + 1, 1 -> length - pad_length - 6 + 1, 0 -> length - pad_length - 1 + 0, 1 -> length - 5 + 0, 0 -> length + _, _ -> panic as "Somehow a bit was set to neither 0 nor 1" + } + + case data_and_padding { + <> -> { + Ok( + Header( + data: case end_headers { + 1 -> Complete(data) + 0 -> Continued(data) + _ -> panic as "Somehow a bit was set to neither 0 nor 1" + }, + end_stream: { end_stream == 1 }, + identifier: stream_identifier(identifier), + priority: case priority == 1 { + True -> + Some(HeaderPriority( + exclusive: { exclusive == 1 }, + stream_dependency: stream_identifier(stream_dependency), + weight: weight, + )) + False -> None + }, + ), + ) + } + _ -> { + logging.log(logging.Debug, "oh noes!") + Error(ProtocolError) + } + } + } + _ -> { + Error(ProtocolError) + } + } +} + +fn parse_priority( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case length, <> { + 5, + << + _unused:size(8), + exclusive:size(1), + dependency:size(31), + weight:size(8), + >> + if identifier != 0 + -> { + Ok(Priority( + exclusive: exclusive == 1, + identifier: stream_identifier(identifier), + stream_dependency: stream_identifier(dependency), + weight: weight, + )) + } + 5, _ -> Error(ProtocolError) + _, _ -> Error(FrameSizeError) + } +} + +fn parse_termination( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case length, <> { + 4, <<_unused:size(8), error:size(32)>> if identifier != 0 -> { + Ok(Termination( + error: get_error(error), + identifier: stream_identifier(identifier), + )) + } + 4, _ -> Error(ProtocolError) + _, _ -> Error(FrameSizeError) + } +} + +fn parse_settings( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case length % 6, <> { + 0, <<_unused:size(7), ack:size(1), settings:bytes-size(length)>> + if identifier == 0 + -> { + use settings <- result.try(get_settings(settings, [])) + Ok(Settings(ack: ack == 1, settings: settings)) + } + + 0, _ -> Error(ProtocolError) + _, _ -> Error(FrameSizeError) + } +} + +fn parse_push_promise( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case <> { + << + _unused:size(4), + padded:size(1), + end_headers:size(1), + _unused:size(2), + pad_length:size(padded)-unit(8), + _reserved:size(1), + promised_identifier:size(31), + data:bytes-size(length), + _padding:bytes-size(pad_length), + >> + if identifier != 0 + -> { + Ok(PushPromise( + data: case end_headers == 1 { + True -> Complete(data) + False -> Continued(data) + }, + identifier: stream_identifier(identifier), + promised_stream_id: stream_identifier(promised_identifier), + )) + } + _ -> Error(ProtocolError) + } +} + +fn parse_ping( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case length, <> { + 8, <<_unused:size(7), ack:size(1), data:bits-size(64)>> if identifier == 0 -> { + Ok(Ping(ack: ack == 1, data: data)) + } + 8, _ -> Error(ProtocolError) + _, _ -> Error(FrameSizeError) + } +} + +fn parse_go_away( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case <> { + << + _unused:size(8), + _reserved:size(1), + last_stream_id:size(31), + error:size(32), + data:bytes-size(length), + >> + if identifier == 0 + -> { + Ok(GoAway( + data: data, + error: get_error(error), + last_stream_id: stream_identifier(last_stream_id), + )) + } + _ -> Error(ProtocolError) + } +} + +fn parse_window_update( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case length, <> { + 4, <<_unused:size(8), _reserved:size(1), window_size:size(31)>> + if window_size != 0 + -> { + Ok(WindowUpdate( + amount: window_size, + identifier: stream_identifier(identifier), + )) + } + 4, _ -> Error(FrameSizeError) + _, _ -> Error(ProtocolError) + } +} + +fn parse_continuation( + identifier: Int, + flags: BitArray, + length: Int, + payload: BitArray, +) -> Result(Frame, ConnectionError) { + case <> { + << + _unused:size(5), + end_headers:size(1), + _unused:size(2), + data:bytes-size(length), + >> + if identifier != 0 + -> { + Ok(Continuation( + data: case end_headers == 1 { + True -> Complete(data) + False -> Continued(data) + }, + identifier: stream_identifier(identifier), + )) + } + _ -> Error(ProtocolError) + } +} + +pub fn encode(frame: Frame) -> BitArray { + case frame { + Data(data, end_stream, StreamIdentifier(identifier)) -> { + let length = bit_array.byte_size(data) + let end = from_bool(end_stream) + << + length:size(24), + 0:size(8), + 0:size(4), + 0:size(1), + 0:size(2), + end:size(1), + 0:size(1), + identifier:size(31), + data:bits, + >> + } + Header(data, end_stream, StreamIdentifier(identifier), priority) -> { + let #(end_header, data) = encode_data(data) + let length = bit_array.byte_size(data) + let end = from_bool(end_stream) + let priority_flags = encode_priority(priority) + let has_priority = from_bool(option.is_some(priority)) + << + length:size(24), + 1:size(8), + 0:size(2), + has_priority:size(1), + 0:size(1), + 0:size(1), + end_header:size(1), + 0:size(1), + end:size(1), + 0:size(1), + identifier:size(31), + priority_flags:bits, + data:bits, + >> + } + Priority( + exclusive, + StreamIdentifier(identifier), + StreamIdentifier(dependency), + weight, + ) -> { + let exclusive = from_bool(exclusive) + << + 5:size(24), + 2:size(2), + 0:size(8), + 0:size(1), + identifier:size(31), + exclusive:size(1), + dependency:size(31), + weight:size(8), + >> + } + Termination(error, StreamIdentifier(identifier)) -> { + let error_code = encode_error(error) + << + 4:size(24), + 3:size(8), + 0:size(8), + 0:size(1), + identifier:size(31), + error_code:size(32), + >> + } + Settings(ack, settings) -> { + let ack = from_bool(ack) + let settings = encode_settings(settings) + let length = bit_array.byte_size(settings) + << + length:size(24), + 4:size(8), + 0:size(7), + ack:size(1), + 0:size(1), + 0:size(31), + settings:bits, + >> + } + PushPromise( + data, + StreamIdentifier(identifier), + StreamIdentifier(promised_identifier), + ) -> { + let #(end_headers, data) = encode_data(data) + << + 0:size(24), + 5:size(8), + 0:size(4), + 0:size(0), + end_headers:size(1), + 0:size(2), + 0:size(1), + identifier:size(31), + 0:size(1), + promised_identifier:size(31), + data:bits, + >> + } + Ping(ack, data) -> { + let ack = from_bool(ack) + << + 0:size(24), + 6:size(8), + 0:size(7), + ack:size(1), + 0:size(1), + 0:size(31), + data:bits, + >> + } + GoAway(data, error, StreamIdentifier(last_stream_id)) -> { + let error = encode_error(error) + let payload_size = bit_array.byte_size(data) + << + payload_size:size(24), + 7:size(8), + 0:size(8), + 0:size(1), + 0:size(31), + 0:size(1), + last_stream_id:size(31), + error:size(32), + data:bits, + >> + } + WindowUpdate(amount, StreamIdentifier(identifier)) -> { + << + 4:size(24), + 8:size(8), + 0:size(8), + 0:size(1), + identifier:size(31), + 0:size(1), + amount:size(31), + >> + } + Continuation(data, StreamIdentifier(identifier)) -> { + let #(end_headers, data) = encode_data(data) + let payload_size = bit_array.byte_size(data) + << + payload_size:size(24), + 9:size(8), + 0:size(5), + end_headers:size(1), + 0:size(2), + 0:size(1), + identifier:size(31), + data:bits, + >> + } + } +} + +fn get_error(value: Int) -> ConnectionError { + case value { + 0 -> NoError + 1 -> ProtocolError + 2 -> InternalError + 3 -> FlowControlError + 4 -> SettingsTimeout + 5 -> StreamClosed + 6 -> FrameSizeError + 7 -> RefusedStream + 8 -> Cancel + 9 -> CompressionError + 10 -> ConnectError + 11 -> EnhanceYourCalm + 12 -> InadequateSecurity + 13 -> Http11Required + n -> Unsupported(n) + } +} + +fn get_settings( + data: BitArray, + acc: List(Setting), +) -> Result(List(Setting), ConnectionError) { + case data { + <<>> -> Ok(acc) + <> -> { + case get_setting(identifier, value) { + Ok(setting) -> get_settings(rest, [setting, ..acc]) + Error(err) -> Error(err) + } + } + _ -> Error(ProtocolError) + } +} + +fn get_setting(identifier: Int, value: Int) -> Result(Setting, ConnectionError) { + case identifier { + 1 -> Ok(HeaderTableSize(value)) + 2 -> + Ok( + ServerPush(case value { + 0 -> Disabled + 1 -> Enabled + _ -> panic as "Somehow a bit was neither 0 nor 1" + }), + ) + 3 -> Ok(MaxConcurrentStreams(value)) + 4 -> { + case value { + n if n > 2_147_483_647 -> Error(FlowControlError) + _ -> Ok(InitialWindowSize(value)) + } + } + 5 -> { + case value { + n if n > 16_777_215 -> Error(ProtocolError) + _ -> Ok(MaxFrameSize(value)) + } + } + 6 -> Ok(MaxHeaderListSize(value)) + _ -> Error(ProtocolError) + } +} + +fn from_bool(bool: Bool) -> Int { + case bool { + True -> 1 + False -> 0 + } +} + +fn encode_priority(priority: Option(HeaderPriority)) -> BitArray { + case priority { + Some(HeaderPriority(exclusive, StreamIdentifier(dependency), weight)) -> { + let exclusive = from_bool(exclusive) + <> + } + None -> <<>> + } +} + +fn encode_data(data: Data) -> #(Int, BitArray) { + case data { + Complete(data) -> #(1, data) + Continued(data) -> #(0, data) + } +} + +fn encode_error(error: ConnectionError) -> Int { + case error { + NoError -> 0 + ProtocolError -> 1 + InternalError -> 2 + FlowControlError -> 3 + SettingsTimeout -> 4 + StreamClosed -> 5 + FrameSizeError -> 6 + RefusedStream -> 7 + Cancel -> 8 + CompressionError -> 9 + ConnectError -> 10 + EnhanceYourCalm -> 11 + InadequateSecurity -> 12 + Http11Required -> 13 + // TODO + Unsupported(..) -> 69 + } +} + +fn encode_settings(settings: List(Setting)) -> BitArray { + list.fold(settings, <<>>, fn(acc, setting) { + case setting { + HeaderTableSize(value) -> + bit_array.append(acc, <<1:size(16), value:size(32)>>) + ServerPush(Enabled) -> bit_array.append(acc, <<2:size(16), 1:size(32)>>) + ServerPush(Disabled) -> bit_array.append(acc, <<2:size(16), 0:size(32)>>) + MaxConcurrentStreams(value) -> + bit_array.append(acc, <<3:size(16), value:size(32)>>) + InitialWindowSize(value) -> + bit_array.append(acc, <<4:size(16), value:size(32)>>) + MaxFrameSize(value) -> + bit_array.append(acc, <<5:size(16), value:size(32)>>) + MaxHeaderListSize(value) -> + bit_array.append(acc, <<6:size(16), value:size(32)>>) + } + }) +} + +pub fn settings_ack() -> Frame { + Settings(ack: True, settings: []) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http2/handler.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/handler.gleam new file mode 100644 index 0000000..aefd591 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/handler.gleam @@ -0,0 +1,302 @@ +import gleam/bit_array +import gleam/dict.{type Dict} +import gleam/erlang/process.{type Subject} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string +import logging +import mist/internal/buffer.{type Buffer} +import mist/internal/http.{type Connection, type Handler, Connection, Initial} +import mist/internal/http2.{type HpackContext, type Http2Settings, Http2Settings} +import mist/internal/http2/flow_control +import mist/internal/http2/frame.{ + type Frame, type StreamIdentifier, Complete, Continued, +} +import mist/internal/http2/stream.{type SendMessage, Ready} + +pub type PendingSend { + PendingSend +} + +pub type State { + State( + fragment: Option(Frame), + frame_buffer: Buffer, + pending_sends: List(PendingSend), + receive_hpack_context: HpackContext, + self: Subject(SendMessage), + send_hpack_context: HpackContext, + send_window_size: Int, + receive_window_size: Int, + settings: Http2Settings, + streams: Dict(StreamIdentifier(Frame), stream.State), + ) +} + +pub fn send_hpack_context(state: State, context: HpackContext) -> State { + State(..state, send_hpack_context: context) +} + +pub fn receive_hpack_context(state: State, context: HpackContext) -> State { + State(..state, receive_hpack_context: context) +} + +pub fn append_data(state: State, data: BitArray) -> State { + State(..state, frame_buffer: buffer.append(state.frame_buffer, data)) +} + +pub fn upgrade( + data: BitArray, + conn: Connection, + self: Subject(SendMessage), +) -> Result(State, String) { + let initial_settings = http2.default_settings() + let settings_frame = frame.Settings(ack: False, settings: []) + + let sent = + http2.send_frame(settings_frame, conn.socket, conn.transport) + |> result.replace_error("Failed to send settings frame") + + use _nil <- result.map(sent) + State( + fragment: None, + frame_buffer: buffer.new(data), + pending_sends: [], + receive_hpack_context: http2.hpack_new_context( + initial_settings.header_table_size, + ), + receive_window_size: 65_535, + self: self, + send_hpack_context: http2.hpack_new_context( + initial_settings.header_table_size, + ), + send_window_size: 65_535, + settings: initial_settings, + streams: dict.new(), + ) +} + +pub fn call( + state: State, + conn: Connection, + handler: Handler, +) -> Result(State, Result(Nil, String)) { + case frame.decode(state.frame_buffer.data) { + Ok(#(frame, rest)) -> { + let new_state = State(..state, frame_buffer: buffer.new(rest)) + case handle_frame(frame, new_state, conn, handler) { + Ok(updated) -> call(updated, conn, handler) + Error(reason) -> Error(Error(reason)) + } + } + Error(frame.NoError) -> Ok(state) + Error(_connection_error) -> { + // TODO: + // - send GOAWAY with last good stream ID + // - close the connection + Ok(state) + } + } +} + +// TODO: this should use the frame error types to actually do some shit with +// the stream(s) +fn handle_frame( + frame: Frame, + state: State, + conn: Connection, + handler: Handler, +) -> Result(State, String) { + case state.fragment, frame { + Some(frame.Header( + identifier: id1, + data: Continued(existing), + end_stream: end_stream, + priority: priority, + )), + frame.Continuation(data: Complete(data), identifier: id2) + if id1 == id2 + -> { + let complete_frame = + frame.Header( + identifier: id1, + data: Complete(<>), + end_stream: end_stream, + priority: priority, + ) + handle_frame( + complete_frame, + State(..state, fragment: None), + conn, + handler, + ) + } + Some(frame.Header( + identifier: id1, + data: Continued(existing), + end_stream: end_stream, + priority: priority, + )), + frame.Continuation(data: Continued(data), identifier: id2) + if id1 == id2 + -> { + let next = + frame.Header( + identifier: id1, + data: Continued(<>), + end_stream: end_stream, + priority: priority, + ) + Ok(State(..state, fragment: Some(next))) + } + None, frame.WindowUpdate(amount, identifier) -> { + case frame.get_stream_identifier(identifier) { + 0 -> { + // do_pending_sends(state) + Ok( + State( + ..state, + settings: Http2Settings( + ..state.settings, + initial_window_size: amount, + ), + ), + ) + } + _stream_id -> { + state.streams + |> dict.get(identifier) + |> result.replace_error("Window update for non-existent stream") + |> result.try(fn(stream) { + case + flow_control.update_send_window(stream.send_window_size, amount) + { + Ok(update) -> { + let new_stream = + stream.State(..stream, send_window_size: update) + Ok( + State( + ..state, + streams: dict.insert(state.streams, identifier, new_stream), + ), + ) + } + _err -> Error("Failed to update send window") + } + }) + } + } + } + None, frame.Header(Complete(data), end_stream, identifier, _priority) -> { + let conn = Connection(..conn, body: Initial(<<>>)) + let assert Ok(#(headers, context)) = + http2.hpack_decode(state.receive_hpack_context, data) + + let pending_content_length = + headers + |> list.key_find("content-length") + |> result.try(int.parse) + |> option.from_result + + let assert Ok(new_stream) = + stream.new( + identifier, + handler, + headers, + conn, + state.self, + // fn(resp) { process.send(state.self, Send(identifier, resp)) }, + end_stream, + ) + process.send(new_stream.data, Ready) + + let stream_state = + stream.State( + id: identifier, + state: stream.Open, + subject: new_stream.data, + receive_window_size: state.settings.initial_window_size, + send_window_size: state.settings.initial_window_size, + pending_content_length: pending_content_length, + ) + let streams = dict.insert(state.streams, identifier, stream_state) + Ok(State(..state, receive_hpack_context: context, streams: streams)) + } + None, frame.Data(identifier: identifier, data: data, end_stream: end_stream) + -> { + let data_size = bit_array.byte_size(data) + let #(conn_receive_window_size, conn_window_increment) = + flow_control.compute_receive_window( + state.receive_window_size, + data_size, + ) + + state.streams + |> dict.get(identifier) + |> result.map(stream.receive_data(_, data_size)) + // TODO: this whole business should much more gracefully handle + // individual stream errors rather than just blowin up + |> result.replace_error("Stream failed to receive data") + // TODO: handle end of stream? + |> result.map(fn(update) { + let #(new_stream, increment) = update + let _ = case conn_window_increment > 0 { + True -> { + http2.send_frame( + frame.WindowUpdate( + identifier: frame.stream_identifier(0), + amount: conn_window_increment, + ), + conn.socket, + conn.transport, + ) + } + False -> Ok(Nil) + } + let _ = case increment > 0 { + True -> { + http2.send_frame( + frame.WindowUpdate(identifier: identifier, amount: increment), + conn.socket, + conn.transport, + ) + } + False -> Ok(Nil) + } + process.send( + new_stream.subject, + stream.Data(bits: data, end: end_stream), + ) + State( + ..state, + streams: dict.insert(state.streams, identifier, new_stream), + receive_window_size: conn_receive_window_size, + ) + }) + } + None, frame.Priority(..) -> { + Ok(state) + } + None, frame.Settings(ack: True, ..) -> { + Ok(state) + } + // TODO: update any settings from this + _, frame.Settings(..) -> { + http2.send_frame(frame.settings_ack(), conn.socket, conn.transport) + |> result.replace(state) + |> result.replace_error("Failed to respond to settings ACK") + } + None, frame.GoAway(..) -> { + logging.log(logging.Debug, "byteeee~~") + // TODO: Normal exit + Error("Going away...") + } + // TODO: obviously fill these out + _, frame -> { + logging.log(logging.Debug, "Ignoring frame: " <> string.inspect(frame)) + Ok(state) + } + } +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/http2/stream.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/stream.gleam new file mode 100644 index 0000000..59dc72c --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/http2/stream.gleam @@ -0,0 +1,208 @@ +import gleam/erlang/process.{type Selector, type Subject} +import gleam/http.{type Header} as ghttp +import gleam/http/request.{type Request} +import gleam/http/response.{type Response} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/pair +import gleam/result +import gleam/string +import gleam/uri +import mist/internal/http.{ + type Connection, type Handler, type ResponseData, Connection, Stream, +} +import mist/internal/http2/flow_control +import mist/internal/http2/frame.{type Frame, type StreamIdentifier} + +pub type Message { + Ready + Data(bits: BitArray, end: Bool) + Done +} + +pub type SendMessage { + Send(identifier: StreamIdentifier(Frame), resp: Response(ResponseData)) +} + +pub type StreamState { + Open + RemoteClosed + LocalClosed + Closed +} + +pub type State { + State( + id: StreamIdentifier(Frame), + state: StreamState, + subject: Subject(Message), + receive_window_size: Int, + send_window_size: Int, + pending_content_length: Option(Int), + ) +} + +pub type InternalState { + InternalState( + data_selector: Selector(Message), + data_subject: Subject(Message), + end: Bool, + pending_response: Option(Response(ResponseData)), + to_remove: BitArray, + ) +} + +pub fn new( + identifier: StreamIdentifier(Frame), + handler: Handler, + headers: List(Header), + connection: Connection, + sender: Subject(SendMessage), + end: Bool, +) -> Result(actor.Started(Subject(Message)), actor.StartError) { + actor.new_with_initialiser(1000, fn(subject) { + let data_selector = + process.new_selector() + |> process.select(subject) + InternalState(data_selector, subject, end, None, <<>>) + |> actor.initialised + |> actor.selecting(data_selector) + |> actor.returning(subject) + |> Ok + }) + |> actor.on_message(fn(state, msg) { + case msg, state.end { + Ready, _ -> { + let content_length = + headers + |> list.key_find("content-length") + |> result.try(int.parse) + |> result.unwrap(0) + let conn = + Connection( + ..connection, + body: Stream( + selector: process.map_selector(state.data_selector, fn(val) { + let assert Data(bits, ..) = val + bits + }), + attempts: 0, + data: <<>>, + remaining: content_length, + ), + ) + + let result = + request.new() + |> request.set_body(conn) + |> make_request(headers, _) + + case result { + Ok(value) -> { + let resp = handler(value) + process.send(state.data_subject, Done) + actor.continue(InternalState(..state, pending_response: Some(resp))) + } + Error(err) -> + actor.stop_abnormal( + "Failed to respond to request: " <> string.inspect(err), + ) + } + } + Done, True -> { + let assert Some(resp) = state.pending_response + process.send(sender, Send(identifier, resp)) + actor.continue(state) + } + Data(bits: bits, end: True), _ -> { + process.send(state.data_subject, Done) + actor.continue( + InternalState(..state, end: True, to_remove: << + state.to_remove:bits, + bits:bits, + >>), + ) + } + Data(bits: bits, ..), _ -> { + actor.continue( + InternalState(..state, to_remove: <>), + ) + } + _msg, _ -> { + // TODO: probably just discard this? + actor.continue(state) + } + } + }) + |> actor.start +} + +pub fn make_request( + headers: List(Header), + req: Request(Connection), +) -> Result(Request(Connection), Nil) { + case headers { + [] -> Ok(req) + [#("method", method), ..rest] -> { + method + |> ghttp.parse_method + |> result.replace_error(Nil) + |> result.map(request.set_method(req, _)) + |> result.try(make_request(rest, _)) + } + [#("scheme", scheme), ..rest] -> { + scheme + |> ghttp.scheme_from_string + |> result.replace_error(Nil) + |> result.map(request.set_scheme(req, _)) + |> result.try(make_request(rest, _)) + } + // TODO + [#("authority", _authority), ..rest] -> make_request(rest, req) + [#("path", path), ..rest] -> { + path + |> string.split_once(on: "?") + |> result.map(fn(split) { + pair.map_second(split, fn(query) { + query + |> uri.parse_query + |> result.map(Some) + |> result.unwrap(None) + }) + }) + |> result.unwrap(#(path, None)) + |> fn(tup: #(String, Option(List(#(String, String))))) { + case tup.1 { + Some(query) -> + req + |> request.set_path(tup.0) + |> request.set_query(query) + _ -> request.set_path(req, tup.0) + } + |> make_request(rest, _) + } + } + [#(key, value), ..rest] -> + req + |> request.set_header(key, value) + |> make_request(rest, _) + } +} + +pub fn receive_data(state: State, size: Int) -> #(State, Int) { + let #(new_window_size, increment) = + flow_control.compute_receive_window(state.receive_window_size, size) + + let new_state = + State( + ..state, + receive_window_size: new_window_size, + pending_content_length: option.map(state.pending_content_length, fn(val) { + val - size + }), + ) + + #(new_state, increment) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/next.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/next.gleam new file mode 100644 index 0000000..ea63f6c --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/next.gleam @@ -0,0 +1,8 @@ +import gleam/erlang/process.{type Selector} +import gleam/option.{type Option} + +pub type Next(state, message) { + Continue(state: state, selector: Option(Selector(message))) + NormalStop + AbnormalStop(reason: String) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist/internal/websocket.gleam b/examples/http_interactions/build/packages/mist/src/mist/internal/websocket.gleam new file mode 100644 index 0000000..bc5ba0e --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist/internal/websocket.gleam @@ -0,0 +1,400 @@ +import exception +import gleam/bit_array +import gleam/dynamic/decode +import gleam/erlang/atom +import gleam/erlang/process.{type Selector} +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/result +import glisten.{type Socket} +import glisten/socket/options +import glisten/transport.{type Transport} +import gramps/websocket.{type Frame, CloseFrame, Control, PingFrame} +import gramps/websocket/compression.{type Compression, type Context} +import logging +import mist/internal/next.{type Next, AbnormalStop, Continue, NormalStop} + +pub type ValidMessage(user_message) { + SocketMessage(BitArray) + SocketClosedMessage + UserMessage(user_message) +} + +pub type WebsocketMessage(user_message) { + Valid(ValidMessage(user_message)) + Invalid +} + +pub type WebsocketConnection { + WebsocketConnection( + socket: Socket, + transport: Transport, + deflate: Option(Context), + ) +} + +pub type HandlerMessage(user_message) { + Internal(Frame) + User(user_message) +} + +pub type WebsocketState(state) { + WebsocketState( + buffer: BitArray, + user: state, + permessage_deflate: Option(Compression), + ) +} + +pub type Handler(state, message) = + fn(state, HandlerMessage(message), WebsocketConnection) -> + Next(state, message) + +// TODO: this is pulled straight from glisten, prob should share it +fn message_selector() -> Selector(WebsocketMessage(user_message)) { + process.new_selector() + |> process.select_record(atom.create("tcp"), 2, fn(record) { + { + use data <- decode.field(2, decode.bit_array) + decode.success(SocketMessage(data)) + } + |> decode.run(record, _) + |> result.replace_error(Nil) + |> result.map(Valid) + |> result.unwrap(Invalid) + }) + |> process.select_record(atom.create("ssl"), 2, fn(record) { + { + use data <- decode.field(2, decode.bit_array) + decode.success(SocketMessage(data)) + } + |> decode.run(record, _) + |> result.replace_error(Nil) + |> result.map(Valid) + |> result.unwrap(Invalid) + }) + |> process.select_record(atom.create("ssl_closed"), 1, fn(_nil) { + Valid(SocketClosedMessage) + }) + |> process.select_record(atom.create("tcp_closed"), 1, fn(_nil) { + Valid(SocketClosedMessage) + }) +} + +pub fn initialize_connection( + on_init: fn(WebsocketConnection) -> #(state, Option(Selector(user_message))), + on_close: fn(state) -> Nil, + handler: Handler(state, user_message), + socket: Socket, + transport: Transport, + extensions: List(String), +) -> Result(actor.Started(process.Pid), actor.StartError) { + let takeovers = websocket.get_context_takeovers(extensions) + actor.new_with_initialiser(500, fn(subject) { + let compression = case websocket.has_deflate(extensions) { + True -> Some(compression.init(takeovers)) + False -> None + } + let connection = + WebsocketConnection( + socket:, + transport:, + deflate: option.map(compression, fn(compression) { compression.deflate }), + ) + let #(initial_state, user_selector) = on_init(connection) + let selector = case user_selector { + Some(user_selector) -> + user_selector + |> process.map_selector(UserMessage) + |> process.map_selector(Valid) + |> process.merge_selector(message_selector()) + _ -> message_selector() + } + WebsocketState( + buffer: <<>>, + user: initial_state, + permessage_deflate: compression, + ) + |> actor.initialised + |> actor.selecting(selector) + |> actor.returning(subject) + |> Ok + }) + |> actor.on_message(fn(state, msg) { + let connection = + WebsocketConnection( + socket:, + transport:, + deflate: option.map(state.permessage_deflate, fn(compression) { + compression.deflate + }), + ) + case msg { + Valid(SocketMessage(data)) -> { + let #(frames, rest) = + websocket.decode_many_frames( + <>, + option.map(state.permessage_deflate, fn(compression) { + compression.inflate + }), + [], + ) + frames + |> websocket.aggregate_frames(None, []) + |> result.map(fn(frames) { + let next = + apply_frames( + frames, + handler, + connection, + Continue(state.user, None), + on_close, + ) + case next { + Continue(user_state, selector) -> { + let next = + actor.continue( + WebsocketState(..state, buffer: rest, user: user_state), + ) + case selector { + Some(selector) -> actor.with_selector(next, selector) + _ -> next + } + } + NormalStop -> { + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + actor.stop() + } + AbnormalStop(reason) -> { + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + actor.stop_abnormal(reason) + } + } + }) + |> result.lazy_unwrap(fn() { + logging.log(logging.Error, "Received a malformed WebSocket frame") + on_close(state.user) + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + actor.stop_abnormal("WebSocket received a malformed message") + }) + } + Valid(UserMessage(msg)) -> { + exception.rescue(fn() { handler(state.user, User(msg), connection) }) + |> result.map(fn(cont) { + case cont { + Continue(user_state, selector) -> { + let selector = + selector + |> map_user_selector + |> option.map(fn(with_user) { + process.merge_selector(message_selector(), with_user) + }) + let next = + actor.continue(WebsocketState(..state, user: user_state)) + case selector { + Some(selector) -> actor.with_selector(next, selector) + _ -> next + } + } + NormalStop -> { + let _ = + transport.send( + connection.transport, + connection.socket, + websocket.encode_close_frame(websocket.Normal(<<>>), None), + ) + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + on_close(state.user) + actor.stop() + } + AbnormalStop(reason) -> { + let _ = + transport.send( + connection.transport, + connection.socket, + websocket.encode_close_frame( + websocket.CustomCloseReason( + 4000, + bit_array.from_string(reason), + ), + None, + ), + ) + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + on_close(state.user) + actor.stop_abnormal(reason) + } + } + }) + |> result.map_error(fn(_err) { + logging.log(logging.Error, "Caught error in websocket handler") + }) + |> result.lazy_unwrap(fn() { + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + on_close(state.user) + actor.stop_abnormal("Crash in user websocket handler") + }) + } + Valid(SocketClosedMessage) -> { + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + on_close(state.user) + actor.stop() + } + // TODO: do we need to send something back for this? + Invalid -> { + logging.log(logging.Error, "Received a malformed WebSocket frame") + let _ = + option.map(state.permessage_deflate, fn(contexts) { + compression.close(contexts.deflate) + compression.close(contexts.inflate) + }) + on_close(state.user) + actor.stop_abnormal("WebSocket received a malformed message") + } + } + }) + |> actor.start + |> result.map(fn(subj) { + let assert Ok(websocket_pid) = process.subject_owner(subj.data) + actor.Started(websocket_pid, websocket_pid) + }) +} + +fn apply_frames( + frames: List(Frame), + handler: Handler(state, user_message), + connection: WebsocketConnection, + next: Next(state, WebsocketMessage(user_message)), + on_close: fn(state) -> Nil, +) -> Next(state, WebsocketMessage(user_message)) { + case frames, next { + _, AbnormalStop(reason) -> AbnormalStop(reason) + _, NormalStop -> NormalStop + [], next -> { + set_active(connection.transport, connection.socket) + next + } + [Control(CloseFrame(reason)), ..], Continue(state, _selector) -> { + let _ = + transport.send( + connection.transport, + connection.socket, + websocket.encode_close_frame(reason, None), + ) + on_close(state) + NormalStop + } + [Control(PingFrame(payload)), ..rest], + Continue(state, _selector) as continue + -> { + transport.send( + connection.transport, + connection.socket, + websocket.encode_pong_frame(payload, None), + ) + |> result.map(fn(_nil) { + set_active(connection.transport, connection.socket) + apply_frames(rest, handler, connection, continue, on_close) + }) + |> result.lazy_unwrap(fn() { + on_close(state) + AbnormalStop("Failed to send pong frame") + }) + } + [frame, ..rest], Continue(state, prev_selector) -> { + case + exception.rescue(fn() { handler(state, Internal(frame), connection) }) + { + Ok(Continue(state, selector)) -> { + let next_selector = + selector + |> map_user_selector + |> option.or(prev_selector) + |> option.map(fn(with_user) { + process.merge_selector(message_selector(), with_user) + }) + + apply_frames( + rest, + handler, + connection, + Continue(state, next_selector), + on_close, + ) + } + Ok(AbnormalStop(reason)) -> { + let _ = + transport.send( + connection.transport, + connection.socket, + websocket.encode_close_frame( + websocket.CustomCloseReason(4000, bit_array.from_string(reason)), + None, + ), + ) + on_close(state) + AbnormalStop(reason) + } + Ok(NormalStop) -> { + let _ = + transport.send( + connection.transport, + connection.socket, + websocket.encode_close_frame(websocket.Normal(<<>>), None), + ) + on_close(state) + NormalStop + } + Error(_reason) -> { + logging.log(logging.Error, "Caught error in websocket handler") + on_close(state) + AbnormalStop("Crash in user websocket handler") + } + } + } + } +} + +pub fn set_active(transport: Transport, socket: Socket) -> Nil { + let assert Ok(_) = + transport.set_opts(transport, socket, [options.ActiveMode(options.Once)]) + + Nil +} + +fn map_user_selector( + selector: Option(Selector(user_message)), +) -> Option(Selector(WebsocketMessage(user_message))) { + option.map( + selector, + process.map_selector(_, fn(msg) { Valid(UserMessage(msg)) }), + ) +} diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@buffer.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@buffer.erl new file mode 100644 index 0000000..4c32a92 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@buffer.erl @@ -0,0 +1,64 @@ +-module(mist@internal@buffer). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/buffer.gleam"). +-export([empty/0, new/1, append/2, slice/2, with_capacity/2, size/1]). +-export_type([buffer/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type buffer() :: {buffer, integer(), bitstring()}. + +-file("src/mist/internal/buffer.gleam", 8). +?DOC(false). +-spec empty() -> buffer(). +empty() -> + {buffer, 0, <<>>}. + +-file("src/mist/internal/buffer.gleam", 12). +?DOC(false). +-spec new(bitstring()) -> buffer(). +new(Data) -> + {buffer, 0, Data}. + +-file("src/mist/internal/buffer.gleam", 16). +?DOC(false). +-spec append(buffer(), bitstring()) -> buffer(). +append(Buffer, Data) -> + Data_size = erlang:byte_size(Data), + Remaining = gleam@int:max(erlang:element(2, Buffer) - Data_size, 0), + {buffer, + Remaining, + <<(erlang:element(3, Buffer))/bitstring, Data/bitstring>>}. + +-file("src/mist/internal/buffer.gleam", 22). +?DOC(false). +-spec slice(buffer(), integer()) -> {bitstring(), bitstring()}. +slice(Buffer, Bits) -> + Bytes = Bits * 8, + case erlang:element(3, Buffer) of + <> -> + {Value, Rest}; + + _ -> + {erlang:element(3, Buffer), <<>>} + end. + +-file("src/mist/internal/buffer.gleam", 30). +?DOC(false). +-spec with_capacity(buffer(), integer()) -> buffer(). +with_capacity(Buffer, Size) -> + {buffer, Size, erlang:element(3, Buffer)}. + +-file("src/mist/internal/buffer.gleam", 34). +?DOC(false). +-spec size(integer()) -> buffer(). +size(Remaining) -> + {buffer, Remaining, <<>>}. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@clock.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@clock.erl new file mode 100644 index 0000000..8688e27 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@clock.erl @@ -0,0 +1,227 @@ +-module(mist@internal@clock). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/clock.gleam"). +-export([stop/1, start/2, get_date/0]). +-export_type([clock_message/0, clock_table/0, table_key/0, ets_opts/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type clock_message() :: set_time. + +-type clock_table() :: mist_clock. + +-type table_key() :: date_header. + +-type ets_opts() :: set | + protected | + named_table | + {read_concurrency, boolean()}. + +-file("src/mist/internal/clock.gleam", 56). +?DOC(false). +-spec stop(any()) -> gleam@erlang@atom:atom_(). +stop(_) -> + erlang:binary_to_atom(<<"ok"/utf8>>). + +-file("src/mist/internal/clock.gleam", 89). +?DOC(false). +-spec weekday_to_short_string(integer()) -> binary(). +weekday_to_short_string(Weekday) -> + case Weekday of + 1 -> + <<"Mon"/utf8>>; + + 2 -> + <<"Tue"/utf8>>; + + 3 -> + <<"Wed"/utf8>>; + + 4 -> + <<"Thu"/utf8>>; + + 5 -> + <<"Fri"/utf8>>; + + 6 -> + <<"Sat"/utf8>>; + + 7 -> + <<"Sun"/utf8>>; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"erlang weekday outside of 1-7 range"/utf8>>, + file => <>, + module => <<"mist/internal/clock"/utf8>>, + function => <<"weekday_to_short_string"/utf8>>, + line => 98}) + end. + +-file("src/mist/internal/clock.gleam", 102). +?DOC(false). +-spec month_to_short_string(integer()) -> binary(). +month_to_short_string(Month) -> + case Month of + 1 -> + <<"Jan"/utf8>>; + + 2 -> + <<"Feb"/utf8>>; + + 3 -> + <<"Mar"/utf8>>; + + 4 -> + <<"Apr"/utf8>>; + + 5 -> + <<"May"/utf8>>; + + 6 -> + <<"Jun"/utf8>>; + + 7 -> + <<"Jul"/utf8>>; + + 8 -> + <<"Aug"/utf8>>; + + 9 -> + <<"Sep"/utf8>>; + + 10 -> + <<"Oct"/utf8>>; + + 11 -> + <<"Nov"/utf8>>; + + 12 -> + <<"Dec"/utf8>>; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"erlang month outside of 1-12 range"/utf8>>, + file => <>, + module => <<"mist/internal/clock"/utf8>>, + function => <<"month_to_short_string"/utf8>>, + line => 116}) + end. + +-file("src/mist/internal/clock.gleam", 73). +?DOC(false). +-spec date() -> binary(). +date() -> + {Weekday, {Year, Month, Day}, {Hour, Minute, Second}} = mist_ffi:now(), + Weekday@1 = weekday_to_short_string(Weekday), + Year@1 = begin + _pipe = erlang:integer_to_binary(Year), + gleam@string:pad_start(_pipe, 4, <<"0"/utf8>>) + end, + Month@1 = month_to_short_string(Month), + Day@1 = begin + _pipe@1 = erlang:integer_to_binary(Day), + gleam@string:pad_start(_pipe@1, 2, <<"0"/utf8>>) + end, + Hour@1 = begin + _pipe@2 = erlang:integer_to_binary(Hour), + gleam@string:pad_start(_pipe@2, 2, <<"0"/utf8>>) + end, + Minute@1 = begin + _pipe@3 = erlang:integer_to_binary(Minute), + gleam@string:pad_start(_pipe@3, 2, <<"0"/utf8>>) + end, + Second@1 = begin + _pipe@4 = erlang:integer_to_binary(Second), + gleam@string:pad_start(_pipe@4, 2, <<"0"/utf8>>) + end, + <<<<((<>))/binary, + ((<<<<<<<<<>/binary, Month@1/binary>>/binary, + " "/utf8>>/binary, + Year@1/binary>>/binary, + " "/utf8>>))/binary>>/binary, + ((<<<<<<<<<>/binary, Minute@1/binary>>/binary, + ":"/utf8>>/binary, + Second@1/binary>>/binary, + " GMT"/utf8>>))/binary>>. + +-file("src/mist/internal/clock.gleam", 28). +?DOC(false). +-spec start(any(), any()) -> {ok, gleam@erlang@process:pid_()} | + {error, gleam@otp@actor:start_error()}. +start(_, _) -> + _pipe@5 = gleam@otp@actor:new_with_initialiser( + 500, + fun(Subject) -> + ets:new( + mist_clock, + [set, protected, named_table, {read_concurrency, true}] + ), + gleam@erlang@process:send(Subject, set_time), + _pipe = Subject, + _pipe@1 = gleam@otp@actor:initialised(_pipe), + _pipe@3 = gleam@otp@actor:selecting( + _pipe@1, + begin + _pipe@2 = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe@2, Subject) + end + ), + _pipe@4 = gleam@otp@actor:returning(_pipe@3, Subject), + {ok, _pipe@4} + end + ), + _pipe@6 = gleam@otp@actor:on_message(_pipe@5, fun(State, Msg) -> case Msg of + set_time -> + ets:insert(mist_clock, {date_header, date()}), + gleam@erlang@process:send_after(State, 1000, set_time), + gleam@otp@actor:continue(State) + end end), + _pipe@7 = gleam@otp@actor:start(_pipe@6), + gleam@result:map( + _pipe@7, + fun(Started) -> + Pid@1 = case gleam@erlang@process:subject_owner( + erlang:element(3, Started) + ) of + {ok, Pid} -> Pid; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/clock"/utf8>>, + function => <<"start"/utf8>>, + line => 51, + value => _assert_fail, + start => 1098, + 'end' => 1154, + pattern_start => 1109, + pattern_end => 1116}) + end, + Pid@1 + end + ). + +-file("src/mist/internal/clock.gleam", 60). +?DOC(false). +-spec get_date() -> binary(). +get_date() -> + case mist_ffi:ets_lookup_element(mist_clock, date_header, 2) of + {ok, Value} -> + Value; + + _ -> + logging:log( + warning, + <<"Failed to lookup date, re-calculating"/utf8>> + ), + date() + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@encoder.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@encoder.erl new file mode 100644 index 0000000..770abd8 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@encoder.erl @@ -0,0 +1,234 @@ +-module(mist@internal@encoder). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/encoder.gleam"). +-export([status_to_bit_array/1, encode_headers/1, response_builder/3, to_bytes_tree/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/mist/internal/encoder.gleam", 34). +?DOC(false). +-spec status_to_bit_array(integer()) -> bitstring(). +status_to_bit_array(Status) -> + case Status of + 100 -> + <<"Continue"/utf8>>; + + 101 -> + <<"Switching Protocols"/utf8>>; + + 103 -> + <<"Early Hints"/utf8>>; + + 200 -> + <<"OK"/utf8>>; + + 201 -> + <<"Created"/utf8>>; + + 202 -> + <<"Accepted"/utf8>>; + + 203 -> + <<"Non-Authoritative Information"/utf8>>; + + 204 -> + <<"No Content"/utf8>>; + + 205 -> + <<"Reset Content"/utf8>>; + + 206 -> + <<"Partial Content"/utf8>>; + + 300 -> + <<"Multiple Choices"/utf8>>; + + 301 -> + <<"Moved Permanently"/utf8>>; + + 302 -> + <<"Found"/utf8>>; + + 303 -> + <<"See Other"/utf8>>; + + 304 -> + <<"Not Modified"/utf8>>; + + 307 -> + <<"Temporary Redirect"/utf8>>; + + 308 -> + <<"Permanent Redirect"/utf8>>; + + 400 -> + <<"Bad Request"/utf8>>; + + 401 -> + <<"Unauthorized"/utf8>>; + + 402 -> + <<"Payment Required"/utf8>>; + + 403 -> + <<"Forbidden"/utf8>>; + + 404 -> + <<"Not Found"/utf8>>; + + 405 -> + <<"Method Not Allowed"/utf8>>; + + 406 -> + <<"Not Acceptable"/utf8>>; + + 407 -> + <<"Proxy Authentication Required"/utf8>>; + + 408 -> + <<"Request Timeout"/utf8>>; + + 409 -> + <<"Conflict"/utf8>>; + + 410 -> + <<"Gone"/utf8>>; + + 411 -> + <<"Length Required"/utf8>>; + + 412 -> + <<"Precondition Failed"/utf8>>; + + 413 -> + <<"Payload Too Large"/utf8>>; + + 414 -> + <<"URI Too Long"/utf8>>; + + 415 -> + <<"Unsupported Media Type"/utf8>>; + + 416 -> + <<"Range Not Satisfiable"/utf8>>; + + 417 -> + <<"Expectation Failed"/utf8>>; + + 418 -> + <<"I'm a teapot"/utf8>>; + + 422 -> + <<"Unprocessable Entity"/utf8>>; + + 425 -> + <<"Too Early"/utf8>>; + + 426 -> + <<"Upgrade Required"/utf8>>; + + 428 -> + <<"Precondition Required"/utf8>>; + + 429 -> + <<"Too Many Requests"/utf8>>; + + 431 -> + <<"Request Header Fields Too Large"/utf8>>; + + 451 -> + <<"Unavailable For Legal Reasons"/utf8>>; + + 500 -> + <<"Internal Server Error"/utf8>>; + + 501 -> + <<"Not Implemented"/utf8>>; + + 502 -> + <<"Bad Gateway"/utf8>>; + + 503 -> + <<"Service Unavailable"/utf8>>; + + 504 -> + <<"Gateway Timeout"/utf8>>; + + 505 -> + <<"HTTP Version Not Supported"/utf8>>; + + 506 -> + <<"Variant Also Negotiates"/utf8>>; + + 507 -> + <<"Insufficient Storage"/utf8>>; + + 508 -> + <<"Loop Detected"/utf8>>; + + 510 -> + <<"Not Extended"/utf8>>; + + 511 -> + <<"Network Authentication Required"/utf8>>; + + _ -> + <<"Unknown HTTP Status"/utf8>> + end. + +-file("src/mist/internal/encoder.gleam", 95). +?DOC(false). +-spec encode_headers(list({binary(), binary()})) -> gleam@bytes_tree:bytes_tree(). +encode_headers(Headers) -> + gleam@list:fold( + Headers, + gleam@bytes_tree:new(), + fun(Builder, Tup) -> + {Header, Value} = Tup, + _pipe = Builder, + _pipe@1 = gleam@bytes_tree:append_string(_pipe, Header), + _pipe@2 = gleam@bytes_tree:append(_pipe@1, <<": "/utf8>>), + _pipe@3 = gleam@bytes_tree:append_string(_pipe@2, Value), + gleam@bytes_tree:append(_pipe@3, <<"\r\n"/utf8>>) + end + ). + +-file("src/mist/internal/encoder.gleam", 14). +?DOC(false). +-spec response_builder(integer(), list({binary(), binary()}), binary()) -> gleam@bytes_tree:bytes_tree(). +response_builder(Status, Headers, Version) -> + Status_string = begin + _pipe = Status, + _pipe@1 = erlang:integer_to_binary(_pipe), + _pipe@2 = gleam_stdlib:wrap_list(_pipe@1), + _pipe@3 = gleam@bytes_tree:append(_pipe@2, <<" "/utf8>>), + gleam@bytes_tree:append(_pipe@3, status_to_bit_array(Status)) + end, + _pipe@4 = gleam@bytes_tree:new(), + _pipe@5 = gleam@bytes_tree:append( + _pipe@4, + <<"HTTP/"/utf8, Version/binary, " "/utf8>> + ), + _pipe@6 = gleam_stdlib:iodata_append(_pipe@5, Status_string), + _pipe@7 = gleam@bytes_tree:append(_pipe@6, <<"\r\n"/utf8>>), + _pipe@8 = gleam_stdlib:iodata_append(_pipe@7, encode_headers(Headers)), + gleam@bytes_tree:append(_pipe@8, <<"\r\n"/utf8>>). + +-file("src/mist/internal/encoder.gleam", 8). +?DOC(false). +-spec to_bytes_tree( + gleam@http@response:response(gleam@bytes_tree:bytes_tree()), + binary() +) -> gleam@bytes_tree:bytes_tree(). +to_bytes_tree(Resp, Version) -> + _pipe = erlang:element(2, Resp), + _pipe@1 = response_builder(_pipe, erlang:element(3, Resp), Version), + gleam_stdlib:iodata_append(_pipe@1, erlang:element(4, Resp)). diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@file.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@file.erl new file mode 100644 index 0000000..3619bad --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@file.erl @@ -0,0 +1,116 @@ +-module(mist@internal@file). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/file.gleam"). +-export([error_to_string/1, sendfile/6, open/1, stat/1, close/1]). +-export_type([file_descriptor/0, file_error/0, send_error/0, file/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type file_descriptor() :: any(). + +-type file_error() :: is_dir | no_access | no_entry | unknown_file_error. + +-type send_error() :: {file_err, file_error()} | + {socket_err, glisten@socket:socket_reason()}. + +-type file() :: {file, file_descriptor(), integer()}. + +-file("src/mist/internal/file.gleam", 15). +?DOC(false). +-spec error_to_string(file_error()) -> binary(). +error_to_string(Error) -> + case Error of + is_dir -> + <<"IsDir"/utf8>>; + + no_access -> + <<"NoAccess"/utf8>>; + + no_entry -> + <<"NoEntry"/utf8>>; + + unknown_file_error -> + <<"UnknownFileError"/utf8>> + end. + +-file("src/mist/internal/file.gleam", 42). +?DOC(false). +-spec sendfile( + glisten@transport:transport(), + file_descriptor(), + glisten@socket:socket(), + integer(), + integer(), + list(any()) +) -> {ok, nil} | {error, send_error()}. +sendfile(Transport, File_descriptor, Socket, Offset, Bytes, Options) -> + case Transport of + tcp -> + _pipe = file:sendfile( + File_descriptor, + Socket, + Offset, + Bytes, + Options + ), + _pipe@1 = gleam@result:map_error( + _pipe, + fun(Field@0) -> {socket_err, Field@0} end + ), + gleam@result:replace(_pipe@1, nil); + + ssl = Transport@1 -> + _pipe@2 = file:pread(File_descriptor, Offset, Bytes), + _pipe@3 = gleam@result:map_error( + _pipe@2, + fun(Field@0) -> {file_err, Field@0} end + ), + gleam@result:'try'( + _pipe@3, + fun(Bits) -> + _pipe@4 = glisten@transport:send( + Transport@1, + Socket, + gleam@bytes_tree:from_bit_array(Bits) + ), + gleam@result:map_error( + _pipe@4, + fun(Field@0) -> {socket_err, Field@0} end + ) + end + ) + end. + +-file("src/mist/internal/file.gleam", 84). +?DOC(false). +-spec open(bitstring()) -> {ok, file_descriptor()} | {error, file_error()}. +open(File) -> + mist_ffi:file_open(File). + +-file("src/mist/internal/file.gleam", 33). +?DOC(false). +-spec stat(bitstring()) -> {ok, file()} | {error, file_error()}. +stat(Filename) -> + _pipe = Filename, + _pipe@1 = mist_ffi:file_open(_pipe), + gleam@result:map( + _pipe@1, + fun(Fd) -> + File_size = filelib:file_size(Filename), + {file, Fd, File_size} + end + ). + +-file("src/mist/internal/file.gleam", 90). +?DOC(false). +-spec close(file_descriptor()) -> {ok, nil} | {error, file_error()}. +close(File) -> + mist_ffi:file_close(File). diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@handler.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@handler.erl new file mode 100644 index 0000000..def3297 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@handler.erl @@ -0,0 +1,246 @@ +-module(mist@internal@handler). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/handler.gleam"). +-export([new_state/1, init/1, with_func/2]). +-export_type([handler_error/0, state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type handler_error() :: {invalid_request, mist@internal@http:decode_error()} | + not_found. + +-type state() :: {http1, + mist@internal@http@handler:state(), + gleam@erlang@process:subject(mist@internal@http2@stream:send_message())} | + {http2, mist@internal@http2@handler:state()}. + +-file("src/mist/internal/handler.gleam", 31). +?DOC(false). +-spec new_state( + gleam@erlang@process:subject(mist@internal@http2@stream:send_message()) +) -> state(). +new_state(Subj) -> + {http1, mist@internal@http@handler:initial_state(), Subj}. + +-file("src/mist/internal/handler.gleam", 35). +?DOC(false). +-spec init(any()) -> {state(), + gleam@option:option(gleam@erlang@process:selector(mist@internal@http2@stream:send_message()))}. +init(_) -> + Subj = gleam@erlang@process:new_subject(), + Selector = begin + _pipe = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe, Subj) + end, + {new_state(Subj), {some, Selector}}. + +-file("src/mist/internal/handler.gleam", 44). +?DOC(false). +-spec with_func( + fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist@internal@http:response_data())), + gleam@erlang@process:name(gleam@otp@factory_supervisor:message(fun(() -> {ok, + gleam@otp@actor:started(gleam@erlang@process:pid_())} | + {error, gleam@otp@actor:start_error()}), gleam@erlang@process:pid_())) +) -> fun((state(), glisten:message(mist@internal@http2@stream:send_message()), glisten:connection(mist@internal@http2@stream:send_message())) -> glisten:next(state(), glisten:message(mist@internal@http2@stream:send_message()))). +with_func(Handler, Factory_name) -> + fun(State, Msg, Conn) -> + Sender = erlang:element(4, Conn), + Conn@1 = {connection, + {initial, <<>>}, + erlang:element(2, Conn), + erlang:element(3, Conn), + Factory_name}, + Result = case {Msg, State} of + {{user, {send, _, _}}, {http1, _, _}} -> + {error, + {error, + <<"Attempted to send HTTP/2 response without upgrade"/utf8>>}}; + + {{user, {send, Id, Resp}}, {http2, State@1}} -> + _pipe@2 = case erlang:element(4, Resp) of + {bytes, Bytes} -> + _pipe = Resp, + _pipe@1 = gleam@http@response:set_body(_pipe, Bytes), + mist@internal@http2:send_bytes_tree( + _pipe@1, + Conn@1, + erlang:element(7, State@1), + Id + ); + + {file, _, _, _} -> + {error, <<"File sending unsupported over HTTP/2"/utf8>>}; + + websocket -> + {error, <<"WebSocket unsupported for HTTP/2"/utf8>>}; + + chunked -> + {error, + <<"Chunked encoding not supported for HTTP/2"/utf8>>}; + + server_sent_events -> + {error, + <<"Server-Sent Events unsupported for HTTP/2"/utf8>>} + end, + _pipe@3 = gleam@result:map( + _pipe@2, + fun(Context) -> + {http2, + mist@internal@http2@handler:send_hpack_context( + State@1, + Context + )} + end + ), + gleam@result:map_error( + _pipe@3, + fun(Err) -> + logging:log( + debug, + <<"Error sending HTTP/2 data: "/utf8, Err/binary>> + ), + {error, Err} + end + ); + + {{packet, Msg@1}, {http1, State@2, Self}} -> + _ = case erlang:element(2, State@2) of + {some, T} -> + gleam@erlang@process:cancel_timer(T); + + _ -> + timer_not_found + end, + _pipe@4 = Msg@1, + _pipe@5 = mist@internal@http:parse_request(_pipe@4, Conn@1), + _pipe@10 = gleam@result:map_error( + _pipe@5, + fun(Err@1) -> case Err@1 of + discard_packet -> + {ok, nil}; + + malformed_request -> + Msg@2 = <<"Received malformed HTTP request"/utf8>>, + logging:log(warning, Msg@2), + {error, Msg@2}; + + invalid_method -> + Msg@3 = <<"Received invalid HTTP method"/utf8>>, + logging:log(warning, Msg@3), + {error, Msg@3}; + + invalid_path -> + Msg@4 = <<"Received invalid HTTP path"/utf8>>, + logging:log(warning, Msg@4), + {error, Msg@4}; + + unknown_header -> + Msg@5 = <<"Received unknown HTTP header"/utf8>>, + logging:log(warning, Msg@5), + {error, Msg@5}; + + unknown_method -> + Msg@6 = <<"Received unknown HTTP method"/utf8>>, + logging:log(warning, Msg@6), + {error, Msg@6}; + + invalid_body -> + Msg@7 = <<"Received invalid HTTP body"/utf8>>, + logging:log(warning, Msg@7), + {error, Msg@7}; + + no_host_header -> + Msg@8 = <<"Missing HTTP `host` header"/utf8>>, + logging:log(warning, Msg@8), + _ = begin + _pipe@6 = gleam@http@response:new(400), + _pipe@7 = gleam@http@response:prepend_header( + _pipe@6, + <<"connection"/utf8>>, + <<"close"/utf8>> + ), + _pipe@8 = gleam@http@response:set_body( + _pipe@7, + gleam@bytes_tree:new() + ), + _pipe@9 = mist@internal@encoder:to_bytes_tree( + _pipe@8, + <<"1.1"/utf8>> + ), + glisten@transport:send( + erlang:element(4, Conn@1), + erlang:element(3, Conn@1), + _pipe@9 + ) + end, + {ok, nil}; + + invalid_http_version -> + Msg@9 = <<"Received invalid HTTP version"/utf8>>, + logging:log(warning, Msg@9), + {error, Msg@9} + end end + ), + gleam@result:'try'(_pipe@10, fun(Req) -> case Req of + {http1_request, Req@1, Version} -> + _pipe@11 = mist@internal@http@handler:call( + Req@1, + Handler, + Sender, + Version + ), + gleam@result:map( + _pipe@11, + fun(New_state) -> + {http1, New_state, Self} + end + ); + + {upgrade, Data} -> + _pipe@12 = mist@internal@http2@handler:upgrade( + Data, + Conn@1, + Self + ), + _pipe@13 = gleam@result:map( + _pipe@12, + fun(Field@0) -> {http2, Field@0} end + ), + gleam@result:map_error( + _pipe@13, + fun(Field@0) -> {error, Field@0} end + ) + end end); + + {{packet, Msg@10}, {http2, State@3}} -> + _pipe@14 = State@3, + _pipe@15 = mist@internal@http2@handler:append_data( + _pipe@14, + Msg@10 + ), + _pipe@16 = mist@internal@http2@handler:call( + _pipe@15, + Conn@1, + Handler + ), + gleam@result:map(_pipe@16, fun(Field@0) -> {http2, Field@0} end) + end, + case Result of + {ok, Value} -> + glisten:continue(Value); + + {error, {ok, _}} -> + glisten:stop(); + + {error, {error, Reason}} -> + glisten:stop_abnormal(Reason) + end + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http.erl new file mode 100644 index 0000000..708871e --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http.erl @@ -0,0 +1,1033 @@ +-module(mist@internal@http). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http.gleam"). +-export([from_header/1, read_data/4, version_to_string/1, add_date_header/1, connection_close/1, keep_alive/1, maybe_keep_alive/1, add_content_length/2, add_default_headers/2, handle_continue/1, parse_headers/4, crypto_hash/2, base64_encode/1, parse_chunk/1, parse_request/2, read_body/1, upgrade_socket/2, upgrade/4]). +-export_type([response_data/0, connection/0, packet_type/0, http_uri/0, http_packet/0, decoded_packet/0, decode_error/0, chunk/0, http_version/0, parsed_request/0, body/0, sha_hash/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type response_data() :: websocket | + {bytes, gleam@bytes_tree:bytes_tree()} | + chunked | + {file, mist@internal@file:file_descriptor(), integer(), integer()} | + server_sent_events. + +-type connection() :: {connection, + body(), + glisten@socket:socket(), + glisten@transport:transport(), + gleam@erlang@process:name(gleam@otp@factory_supervisor:message(fun(() -> {ok, + gleam@otp@actor:started(gleam@erlang@process:pid_())} | + {error, gleam@otp@actor:start_error()}), gleam@erlang@process:pid_()))}. + +-type packet_type() :: http | httph_bin | http_bin. + +-type http_uri() :: {abs_path, bitstring()}. + +-type http_packet() :: {http_request, + gleam@dynamic:dynamic_(), + http_uri(), + {integer(), integer()}} | + {http_header, + integer(), + gleam@erlang@atom:atom_(), + bitstring(), + bitstring()}. + +-type decoded_packet() :: {binary_data, http_packet(), bitstring()} | + {end_of_headers, bitstring()} | + {more_data, gleam@option:option(integer())} | + {http2_upgrade, bitstring()}. + +-type decode_error() :: malformed_request | + invalid_method | + invalid_path | + unknown_header | + unknown_method | + invalid_body | + discard_packet | + no_host_header | + invalid_http_version. + +-type chunk() :: {chunk, bitstring(), mist@internal@buffer:buffer()} | complete. + +-type http_version() :: http1 | http11. + +-type parsed_request() :: {http1_request, + gleam@http@request:request(connection()), + http_version()} | + {upgrade, bitstring()}. + +-type body() :: {initial, bitstring()} | + {stream, + gleam@erlang@process:selector(bitstring()), + bitstring(), + integer(), + integer()}. + +-type sha_hash() :: sha. + +-file("src/mist/internal/http.gleam", 88). +?DOC(false). +-spec from_header(bitstring()) -> binary(). +from_header(Value) -> + Value@2 = case gleam@bit_array:to_string(Value) of + {ok, Value@1} -> Value@1; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http"/utf8>>, + function => <<"from_header"/utf8>>, + line => 89, + value => _assert_fail, + start => 1916, + 'end' => 1965, + pattern_start => 1927, + pattern_end => 1936}) + end, + string:lowercase(Value@2). + +-file("src/mist/internal/http.gleam", 123). +?DOC(false). +-spec read_data( + glisten@socket:socket(), + glisten@transport:transport(), + mist@internal@buffer:buffer(), + decode_error() +) -> {ok, bitstring()} | {error, decode_error()}. +read_data(Socket, Transport, Buffer, Error) -> + To_read = gleam@int:min(erlang:element(2, Buffer), 1000000), + Timeout = 15000, + gleam@result:'try'( + begin + _pipe = Socket, + _pipe@1 = glisten@transport:receive_timeout( + Transport, + _pipe, + To_read, + Timeout + ), + gleam@result:replace_error(_pipe@1, Error) + end, + fun(Data) -> + Next_buffer = {buffer, + gleam@int:max(0, erlang:element(2, Buffer) - To_read), + <<(erlang:element(3, Buffer))/bitstring, Data/bitstring>>}, + case erlang:element(2, Next_buffer) > 0 of + true -> + read_data(Socket, Transport, Next_buffer, Error); + + false -> + {ok, erlang:element(3, Next_buffer)} + end + end + ). + +-file("src/mist/internal/http.gleam", 250). +?DOC(false). +-spec version_to_string(http_version()) -> binary(). +version_to_string(Version) -> + case Version of + http1 -> + <<"1.0"/utf8>>; + + http11 -> + <<"1.1"/utf8>> + end. + +-file("src/mist/internal/http.gleam", 265). +?DOC(false). +-spec decode_http_method(gleam@dynamic:dynamic_()) -> {ok, gleam@http:method()} | + {error, nil}. +decode_http_method(Value) -> + Options = erlang:binary_to_atom(<<"OPTIONS"/utf8>>), + Get = erlang:binary_to_atom(<<"GET"/utf8>>), + Head = erlang:binary_to_atom(<<"HEAD"/utf8>>), + Post = erlang:binary_to_atom(<<"POST"/utf8>>), + Put = erlang:binary_to_atom(<<"PUT"/utf8>>), + Delete = erlang:binary_to_atom(<<"DELETE"/utf8>>), + Trace = erlang:binary_to_atom(<<"TRACE"/utf8>>), + case mist_ffi:decode_atom(Value) of + {ok, Method} when Method =:= Options -> + {ok, options}; + + {ok, Method@1} when Method@1 =:= Get -> + {ok, get}; + + {ok, Method@2} when Method@2 =:= Head -> + {ok, head}; + + {ok, Method@3} when Method@3 =:= Post -> + {ok, post}; + + {ok, Method@4} when Method@4 =:= Put -> + {ok, put}; + + {ok, Method@5} when Method@5 =:= Delete -> + {ok, delete}; + + {ok, Method@6} when Method@6 =:= Trace -> + {ok, trace}; + + _ -> + case gleam@dynamic@decode:run( + Value, + {decoder, fun gleam@dynamic@decode:decode_string/1} + ) of + {ok, Str} -> + gleam@http:parse_method(Str); + + _ -> + {error, nil} + end + end. + +-file("src/mist/internal/http.gleam", 550). +?DOC(false). +-spec add_date_header(gleam@http@response:response(JLY)) -> gleam@http@response:response(JLY). +add_date_header(Resp) -> + case gleam@http@response:get_header(Resp, <<"date"/utf8>>) of + {error, _} -> + gleam@http@response:set_header( + Resp, + <<"date"/utf8>>, + mist@internal@clock:get_date() + ); + + _ -> + Resp + end. + +-file("src/mist/internal/http.gleam", 557). +?DOC(false). +-spec connection_close(gleam@http@response:response(JMB)) -> gleam@http@response:response(JMB). +connection_close(Resp) -> + gleam@http@response:set_header( + Resp, + <<"connection"/utf8>>, + <<"close"/utf8>> + ). + +-file("src/mist/internal/http.gleam", 561). +?DOC(false). +-spec keep_alive(gleam@http@response:response(JME)) -> gleam@http@response:response(JME). +keep_alive(Resp) -> + gleam@http@response:set_header( + Resp, + <<"connection"/utf8>>, + <<"keep-alive"/utf8>> + ). + +-file("src/mist/internal/http.gleam", 565). +?DOC(false). +-spec maybe_keep_alive(gleam@http@response:response(JMH)) -> gleam@http@response:response(JMH). +maybe_keep_alive(Resp) -> + case gleam@http@response:get_header(Resp, <<"connection"/utf8>>) of + {ok, _} -> + Resp; + + _ -> + gleam@http@response:set_header( + Resp, + <<"connection"/utf8>>, + <<"keep-alive"/utf8>> + ) + end. + +-file("src/mist/internal/http.gleam", 572). +?DOC(false). +-spec maybe_drop_body( + gleam@http@response:response(gleam@bytes_tree:bytes_tree()), + boolean() +) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree()). +maybe_drop_body(Resp, Is_head_request) -> + case Is_head_request of + true -> + gleam@http@response:set_body(Resp, gleam@bytes_tree:new()); + + false -> + Resp + end. + +-file("src/mist/internal/http.gleam", 582). +?DOC(false). +-spec add_content_length(boolean(), integer()) -> fun((gleam@http@response:response(JMM)) -> gleam@http@response:response(JMM)). +add_content_length(When, Length) -> + fun(Resp) -> case When of + true -> + {_, Headers} = begin + _pipe = erlang:element(3, Resp), + _pipe@1 = gleam@list:key_pop( + _pipe, + <<"content-length"/utf8>> + ), + gleam@result:lazy_unwrap( + _pipe@1, + fun() -> {<<""/utf8>>, erlang:element(3, Resp)} end + ) + end, + _pipe@2 = {response, + erlang:element(2, Resp), + Headers, + erlang:element(4, Resp)}, + gleam@http@response:set_header( + _pipe@2, + <<"content-length"/utf8>>, + erlang:integer_to_binary(Length) + ); + + false -> + Resp + end end. + +-file("src/mist/internal/http.gleam", 602). +?DOC(false). +-spec add_default_headers( + gleam@http@response:response(gleam@bytes_tree:bytes_tree()), + boolean() +) -> gleam@http@response:response(gleam@bytes_tree:bytes_tree()). +add_default_headers(Resp, Is_head_response) -> + Body_size = erlang:iolist_size(erlang:element(4, Resp)), + {_, Headers} = begin + _pipe = erlang:element(3, Resp), + _pipe@1 = gleam@list:key_pop(_pipe, <<"content-length"/utf8>>), + gleam@result:lazy_unwrap( + _pipe@1, + fun() -> {<<""/utf8>>, erlang:element(3, Resp)} end + ) + end, + Resp@1 = case {erlang:element(2, Resp), Body_size} of + {N, _} when (N >= 100) andalso (N =< 199) -> + {response, + erlang:element(2, Resp), + Headers, + erlang:element(4, Resp)}; + + {N@1, _} when N@1 =:= 204 -> + {response, + erlang:element(2, Resp), + Headers, + erlang:element(4, Resp)}; + + {N@2, 0} when N@2 =:= 304 -> + Resp; + + {_, 0} when Is_head_response =:= true -> + Resp; + + {_, _} -> + gleam@http@response:set_header( + Resp, + <<"content-length"/utf8>>, + erlang:integer_to_binary(Body_size) + ) + end, + _pipe@2 = Resp@1, + _pipe@3 = add_date_header(_pipe@2), + maybe_drop_body(_pipe@3, Is_head_response). + +-file("src/mist/internal/http.gleam", 631). +?DOC(false). +-spec is_continue(gleam@http@request:request(connection())) -> boolean(). +is_continue(Req) -> + _pipe = erlang:element(3, Req), + _pipe@1 = gleam@list:find( + _pipe, + fun(Tup) -> + (gleam@pair:first(Tup) =:= <<"expect"/utf8>>) andalso (gleam@pair:second( + Tup + ) + =:= <<"100-continue"/utf8>>) + end + ), + gleam@result:is_ok(_pipe@1). + +-file("src/mist/internal/http.gleam", 639). +?DOC(false). +-spec handle_continue(gleam@http@request:request(connection())) -> {ok, nil} | + {error, decode_error()}. +handle_continue(Req) -> + case is_continue(Req) of + true -> + _pipe = gleam@http@response:new(100), + _pipe@1 = gleam@http@response:set_body( + _pipe, + gleam@bytes_tree:new() + ), + _pipe@2 = mist@internal@encoder:to_bytes_tree( + _pipe@1, + <<"1.1"/utf8>> + ), + _pipe@3 = glisten@transport:send( + erlang:element(4, erlang:element(4, Req)), + erlang:element(3, erlang:element(4, Req)), + _pipe@2 + ), + gleam@result:replace_error(_pipe@3, malformed_request); + + false -> + {ok, nil} + end. + +-file("src/mist/internal/http.gleam", 94). +?DOC(false). +-spec parse_headers( + bitstring(), + glisten@socket:socket(), + glisten@transport:transport(), + gleam@dict:dict(binary(), binary()) +) -> {ok, {gleam@dict:dict(binary(), binary()), bitstring()}} | + {error, decode_error()}. +parse_headers(Bs, Socket, Transport, Headers) -> + case mist_ffi:decode_packet(httph_bin, Bs, []) of + {ok, {binary_data, {http_header, _, _, Field, Value}, Rest}} -> + Field@1 = from_header(Field), + Value@2 = case gleam@bit_array:to_string(Value) of + {ok, Value@1} -> Value@1; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http"/utf8>>, + function => <<"parse_headers"/utf8>>, + line => 103, + value => _assert_fail, + start => 2322, + 'end' => 2371, + pattern_start => 2333, + pattern_end => 2342}) + end, + _pipe = Headers, + _pipe@1 = gleam@dict:insert(_pipe, Field@1, Value@2), + parse_headers(Rest, Socket, Transport, _pipe@1); + + {ok, {end_of_headers, Rest@1}} -> + {ok, {Headers, Rest@1}}; + + {ok, {more_data, Size}} -> + Amount_to_read = gleam@option:unwrap(Size, 0), + gleam@result:'try'( + read_data( + Socket, + Transport, + {buffer, Amount_to_read, Bs}, + unknown_header + ), + fun(Next) -> parse_headers(Next, Socket, Transport, Headers) end + ); + + _ -> + {error, unknown_header} + end. + +-file("src/mist/internal/http.gleam", 660). +?DOC(false). +-spec crypto_hash(sha_hash(), binary()) -> binary(). +crypto_hash(Hash, Data) -> + crypto:hash(Hash, Data). + +-file("src/mist/internal/http.gleam", 663). +?DOC(false). +-spec base64_encode(binary()) -> binary(). +base64_encode(Data) -> + base64:encode(Data). + +-file("src/mist/internal/http.gleam", 156). +?DOC(false). +-spec parse_chunk(bitstring()) -> chunk(). +parse_chunk(String) -> + case binary:split(String, <<"\r\n"/utf8>>) of + [<<"0"/utf8>>, _] -> + complete; + + [Chunk_size, Rest] -> + Chunk_size@2 = case gleam@bit_array:to_string(Chunk_size) of + {ok, Chunk_size@1} -> Chunk_size@1; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http"/utf8>>, + function => <<"parse_chunk"/utf8>>, + line => 160, + value => _assert_fail, + start => 3796, + 'end' => 3855, + pattern_start => 3807, + pattern_end => 3821}) + end, + case gleam@int:base_parse(Chunk_size@2, 16) of + {ok, Size} -> + Size@1 = Size * 8, + case Rest of + <> -> + {chunk, + Next_chunk, + mist@internal@buffer:new(Rest@1)}; + + _ -> + {chunk, <<>>, mist@internal@buffer:new(String)} + end; + + {error, _} -> + {chunk, <<>>, mist@internal@buffer:new(String)} + end; + + _ -> + {chunk, <<>>, mist@internal@buffer:new(String)} + end. + +-file("src/mist/internal/http.gleam", 292). +?DOC(false). +-spec parse_request(bitstring(), connection()) -> {ok, parsed_request()} | + {error, decode_error()}. +parse_request(Bs, Conn) -> + case mist_ffi:decode_packet(http_bin, Bs, []) of + {ok, + {binary_data, + {http_request, Http_method, {abs_path, Path}, Version}, + Rest}} -> + gleam@result:'try'( + begin + _pipe = Http_method, + _pipe@1 = decode_http_method(_pipe), + gleam@result:replace_error(_pipe@1, unknown_method) + end, + fun(Method) -> + gleam@result:'try'( + parse_headers( + Rest, + erlang:element(3, Conn), + erlang:element(4, Conn), + maps:new() + ), + fun(_use0) -> + {Headers, Rest@1} = _use0, + gleam@result:'try'( + begin + _pipe@2 = Path, + _pipe@3 = gleam@bit_array:to_string(_pipe@2), + gleam@result:replace_error( + _pipe@3, + invalid_path + ) + end, + fun(Path@1) -> + gleam@result:'try'( + begin + _pipe@4 = mist_ffi:get_path_and_query( + Path@1 + ), + gleam@result:replace_error( + _pipe@4, + invalid_path + ) + end, + fun(_use0@1) -> + {Path@2, Query} = _use0@1, + Scheme = case erlang:element( + 4, + Conn + ) of + ssl -> + https; + + tcp -> + http + end, + gleam@result:'try'( + begin + _pipe@5 = gleam_stdlib:map_get( + Headers, + <<"host"/utf8>> + ), + gleam@result:replace_error( + _pipe@5, + no_host_header + ) + end, + fun(Host_header) -> + {Hostname, Port} = begin + _pipe@6 = Host_header, + _pipe@7 = gleam@string:split_once( + _pipe@6, + <<":"/utf8>> + ), + gleam@result:unwrap( + _pipe@7, + {Host_header, + <<""/utf8>>} + ) + end, + Port@2 = case gleam_stdlib:parse_int( + Port + ) of + {ok, Port@1} -> + Port@1; + + {error, _} -> + case Scheme of + https -> + 443; + + http -> + 80 + end + end, + Req = {request, + Method, + maps:to_list(Headers), + {connection, + {initial, Rest@1}, + erlang:element( + 3, + Conn + ), + erlang:element( + 4, + Conn + ), + erlang:element( + 5, + Conn + )}, + Scheme, + Hostname, + {some, Port@2}, + Path@2, + gleam@option:from_result( + Query + )}, + case Version of + {1, 0} -> + {ok, + {http1_request, + Req, + http1}}; + + {1, 1} -> + {ok, + {http1_request, + Req, + http11}}; + + _ -> + {error, + invalid_http_version} + end + end + ) + end + ) + end + ) + end + ) + end + ); + + {ok, + {http2_upgrade, + <<13/integer, + 10/integer, + 83/integer, + 77/integer, + 13/integer, + 10/integer, + 13/integer, + 10/integer, + Data/bitstring>>}} -> + {ok, {upgrade, Data}}; + + {ok, {more_data, Size}} -> + Amount_to_read = gleam@option:unwrap(Size, 0), + gleam@result:'try'( + read_data( + erlang:element(3, Conn), + erlang:element(4, Conn), + {buffer, Amount_to_read, Bs}, + malformed_request + ), + fun(Next) -> parse_request(Next, Conn) end + ); + + _ -> + {error, discard_packet} + end. + +-file("src/mist/internal/http.gleam", 186). +?DOC(false). +-spec read_chunk( + glisten@socket:socket(), + glisten@transport:transport(), + mist@internal@buffer:buffer(), + gleam@bytes_tree:bytes_tree() +) -> {ok, gleam@bytes_tree:bytes_tree()} | {error, decode_error()}. +read_chunk(Socket, Transport, Buffer, Body) -> + case {erlang:element(3, Buffer), + mist_ffi:binary_match( + erlang:element(3, Buffer), + <<13/integer, 10/integer>> + )} of + {_, {ok, {Offset, _}}} -> + {Chunk@1, Rest@1} = case erlang:element(3, Buffer) of + <> -> { + Chunk, + Rest}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http"/utf8>>, + function => <<"read_chunk"/utf8>>, + line => 194, + value => _assert_fail, + start => 4679, + 'end' => 4812, + pattern_start => 4690, + pattern_end => 4798}) + end, + gleam@result:'try'( + begin + _pipe = Chunk@1, + _pipe@1 = gleam@bit_array:to_string(_pipe), + _pipe@2 = gleam@result:map( + _pipe@1, + fun unicode:characters_to_list/1 + ), + gleam@result:replace_error(_pipe@2, invalid_body) + end, + fun(Chunk_size) -> + gleam@result:'try'( + begin + _pipe@3 = mist_ffi:string_to_int(Chunk_size, 16), + gleam@result:replace_error(_pipe@3, invalid_body) + end, + fun(Size) -> case Size of + 0 -> + {ok, Body}; + + Size@1 -> + case Rest@1 of + <> -> + read_chunk( + Socket, + Transport, + {buffer, 0, Rest@2}, + gleam@bytes_tree:append( + Body, + Next_chunk + ) + ); + + _ -> + gleam@result:'try'( + read_data( + Socket, + Transport, + {buffer, + 0, + erlang:element( + 3, + Buffer + )}, + invalid_body + ), + fun(Next) -> + read_chunk( + Socket, + Transport, + {buffer, 0, Next}, + Body + ) + end + ) + end + end end + ) + end + ); + + {<<>> = Data, _} -> + gleam@result:'try'( + read_data(Socket, Transport, {buffer, 0, Data}, invalid_body), + fun(Next@1) -> + read_chunk(Socket, Transport, {buffer, 0, Next@1}, Body) + end + ); + + {Data, {error, nil}} -> + gleam@result:'try'( + read_data(Socket, Transport, {buffer, 0, Data}, invalid_body), + fun(Next@1) -> + read_chunk(Socket, Transport, {buffer, 0, Next@1}, Body) + end + ) + end. + +-file("src/mist/internal/http.gleam", 395). +?DOC(false). +-spec read_body(gleam@http@request:request(connection())) -> {ok, + gleam@http@request:request(bitstring())} | + {error, decode_error()}. +read_body(Req) -> + Transport = case erlang:element(5, Req) of + https -> + ssl; + + http -> + tcp + end, + case {gleam@http@request:get_header(Req, <<"transfer-encoding"/utf8>>), + erlang:element(2, erlang:element(4, Req))} of + {{ok, <<"chunked"/utf8>>}, {initial, Rest}} -> + gleam@result:'try'( + handle_continue(Req), + fun(_) -> + gleam@result:'try'( + read_chunk( + erlang:element(3, erlang:element(4, Req)), + Transport, + {buffer, 0, Rest}, + gleam@bytes_tree:new() + ), + fun(Chunk) -> + {ok, + gleam@http@request:set_body( + Req, + erlang:list_to_bitstring(Chunk) + )} + end + ) + end + ); + + {_, {initial, Rest@1}} -> + gleam@result:'try'( + handle_continue(Req), + fun(_) -> + Body_size = begin + _pipe = erlang:element(3, Req), + _pipe@1 = gleam@list:find( + _pipe, + fun(Tup) -> + gleam@pair:first(Tup) =:= <<"content-length"/utf8>> + end + ), + _pipe@2 = gleam@result:map( + _pipe@1, + fun gleam@pair:second/1 + ), + _pipe@3 = gleam@result:'try'( + _pipe@2, + fun gleam_stdlib:parse_int/1 + ), + gleam@result:unwrap(_pipe@3, 0) + end, + Remaining = Body_size - erlang:byte_size(Rest@1), + _pipe@4 = case {Body_size, Remaining} of + {0, 0} -> + {ok, <<>>}; + + {0, _} -> + {ok, Rest@1}; + + {_, 0} -> + {ok, Rest@1}; + + {_, _} -> + read_data( + erlang:element(3, erlang:element(4, Req)), + Transport, + {buffer, Remaining, Rest@1}, + invalid_body + ) + end, + _pipe@5 = gleam@result:map( + _pipe@4, + fun(_capture) -> + gleam@http@request:set_body(Req, _capture) + end + ), + gleam@result:replace_error(_pipe@5, invalid_body) + end + ); + + {_, {stream, Selector, Data, Remaining@1, Attempts}} when Remaining@1 > 0 -> + Res = begin + _pipe@6 = Selector, + _pipe@7 = gleam_erlang_ffi:select(_pipe@6, 1000), + gleam@result:replace_error(_pipe@7, invalid_body) + end, + gleam@result:'try'( + Res, + fun(Next) -> + Got = erlang:byte_size(Next), + Left = gleam@int:max(Remaining@1 - Got, 0), + New_data = gleam@bit_array:append(Data, Next), + case Left of + 0 -> + {ok, gleam@http@request:set_body(Req, New_data)}; + + _ -> + read_body( + gleam@http@request:set_body( + Req, + begin + _record = erlang:element(4, Req), + {connection, + {stream, + Selector, + New_data, + Left, + Attempts + 1}, + erlang:element(3, _record), + erlang:element(4, _record), + erlang:element(5, _record)} + end + ) + ) + end + end + ); + + {_, {stream, _, Data@1, _, _}} -> + {ok, gleam@http@request:set_body(Req, Data@1)} + end. + +-file("src/mist/internal/http.gleam", 480). +?DOC(false). +-spec parse_websocket_key(binary()) -> binary(). +parse_websocket_key(Key) -> + _pipe = Key, + _pipe@1 = gleam@string:append( + _pipe, + <<"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"/utf8>> + ), + _pipe@2 = crypto:hash(sha, _pipe@1), + base64:encode(_pipe@2). + +-file("src/mist/internal/http.gleam", 487). +?DOC(false). +-spec upgrade_socket(gleam@http@request:request(connection()), list(binary())) -> {ok, + gleam@http@response:response(gleam@bytes_tree:bytes_tree())} | + {error, gleam@http@request:request(connection())}. +upgrade_socket(Req, Extensions) -> + gleam@result:'try'( + begin + _pipe = gleam@http@request:get_header(Req, <<"upgrade"/utf8>>), + gleam@result:replace_error(_pipe, Req) + end, + fun(_) -> + gleam@result:'try'( + begin + _pipe@1 = gleam@http@request:get_header( + Req, + <<"sec-websocket-key"/utf8>> + ), + gleam@result:replace_error(_pipe@1, Req) + end, + fun(Key) -> + gleam@result:'try'( + begin + _pipe@2 = gleam@http@request:get_header( + Req, + <<"sec-websocket-version"/utf8>> + ), + gleam@result:replace_error(_pipe@2, Req) + end, + fun(_) -> + Permessage_deflate = gramps@websocket:has_deflate( + Extensions + ), + Accept_key = parse_websocket_key(Key), + Resp = begin + _pipe@3 = gleam@http@response:new(101), + _pipe@4 = gleam@http@response:set_body( + _pipe@3, + gleam@bytes_tree:new() + ), + _pipe@5 = gleam@http@response:prepend_header( + _pipe@4, + <<"upgrade"/utf8>>, + <<"websocket"/utf8>> + ), + _pipe@6 = gleam@http@response:prepend_header( + _pipe@5, + <<"connection"/utf8>>, + <<"Upgrade"/utf8>> + ), + gleam@http@response:prepend_header( + _pipe@6, + <<"sec-websocket-accept"/utf8>>, + Accept_key + ) + end, + case Permessage_deflate of + true -> + {ok, + gleam@http@response:prepend_header( + Resp, + <<"sec-websocket-extensions"/utf8>>, + <<"permessage-deflate"/utf8>> + )}; + + false -> + {ok, Resp} + end + end + ) + end + ) + end + ). + +-file("src/mist/internal/http.gleam", 527). +?DOC(false). +-spec upgrade( + glisten@socket:socket(), + glisten@transport:transport(), + list(binary()), + gleam@http@request:request(connection()) +) -> {ok, nil} | {error, nil}. +upgrade(Socket, Transport, Extensions, Req) -> + gleam@result:'try'( + begin + _pipe = upgrade_socket(Req, Extensions), + gleam@result:replace_error(_pipe, nil) + end, + fun(Resp) -> + gleam@result:'try'( + begin + _pipe@1 = Resp, + _pipe@2 = add_default_headers( + _pipe@1, + erlang:element(2, Req) =:= head + ), + _pipe@3 = maybe_keep_alive(_pipe@2), + _pipe@4 = mist@internal@encoder:to_bytes_tree( + _pipe@3, + <<"1.1"/utf8>> + ), + _pipe@5 = glisten@transport:send(Transport, Socket, _pipe@4), + gleam@result:replace_error(_pipe@5, nil) + end, + fun(_) -> {ok, nil} end + ) + end + ). diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http2.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http2.erl new file mode 100644 index 0000000..a1c76e1 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http2.erl @@ -0,0 +1,242 @@ +-module(mist@internal@http2). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http2.gleam"). +-export([default_settings/0, update_settings/2, send_frame/3, hpack_new_context/1, hpack_max_table_size/2, hpack_decode/2, hpack_encode/2, send_bytes_tree/4]). +-export_type([http2_settings/0, hpack_context/0, hpack_error/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type http2_settings() :: {http2_settings, + integer(), + mist@internal@http2@frame:push_state(), + integer(), + integer(), + integer(), + gleam@option:option(integer())}. + +-type hpack_context() :: any(). + +-type hpack_error() :: compression | {bad_header_packet, bitstring()}. + +-file("src/mist/internal/http2.gleam", 28). +?DOC(false). +-spec default_settings() -> http2_settings(). +default_settings() -> + {http2_settings, 4096, disabled, 100, 65535, 16384, none}. + +-file("src/mist/internal/http2.gleam", 39). +?DOC(false). +-spec update_settings( + http2_settings(), + list(mist@internal@http2@frame:setting()) +) -> http2_settings(). +update_settings(Current, Settings) -> + gleam@list:fold( + Settings, + Current, + fun(Settings@1, Setting) -> case Setting of + {header_table_size, Size} -> + {http2_settings, + Size, + erlang:element(3, Settings@1), + erlang:element(4, Settings@1), + erlang:element(5, Settings@1), + erlang:element(6, Settings@1), + erlang:element(7, Settings@1)}; + + {server_push, Push} -> + {http2_settings, + erlang:element(2, Settings@1), + Push, + erlang:element(4, Settings@1), + erlang:element(5, Settings@1), + erlang:element(6, Settings@1), + erlang:element(7, Settings@1)}; + + {max_concurrent_streams, Max} -> + {http2_settings, + erlang:element(2, Settings@1), + erlang:element(3, Settings@1), + Max, + erlang:element(5, Settings@1), + erlang:element(6, Settings@1), + erlang:element(7, Settings@1)}; + + {initial_window_size, Size@1} -> + {http2_settings, + erlang:element(2, Settings@1), + erlang:element(3, Settings@1), + erlang:element(4, Settings@1), + Size@1, + erlang:element(6, Settings@1), + erlang:element(7, Settings@1)}; + + {max_frame_size, Size@2} -> + {http2_settings, + erlang:element(2, Settings@1), + erlang:element(3, Settings@1), + erlang:element(4, Settings@1), + erlang:element(5, Settings@1), + Size@2, + erlang:element(7, Settings@1)}; + + {max_header_list_size, Size@3} -> + {http2_settings, + erlang:element(2, Settings@1), + erlang:element(3, Settings@1), + erlang:element(4, Settings@1), + erlang:element(5, Settings@1), + erlang:element(6, Settings@1), + {some, Size@3}} + end end + ). + +-file("src/mist/internal/http2.gleam", 91). +?DOC(false). +-spec send_data( + mist@internal@http:connection(), + bitstring(), + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + boolean() +) -> {ok, nil} | {error, binary()}. +send_data(Conn, Data, Stream_identifier, End_stream) -> + Data_frame = {data, Data, End_stream, Stream_identifier}, + Encoded = mist@internal@http2@frame:encode(Data_frame), + _pipe = glisten@transport:send( + erlang:element(4, Conn), + erlang:element(3, Conn), + gleam@bytes_tree:from_bit_array(Encoded) + ), + gleam@result:map_error( + _pipe, + fun(Err) -> + logging:log( + debug, + <<"failed to send :( "/utf8, + (glisten@socket:reason_to_string(Err))/binary>> + ), + <<"Failed to send HTTP/2 data"/utf8>> + end + ). + +-file("src/mist/internal/http2.gleam", 116). +?DOC(false). +-spec send_frame( + mist@internal@http2@frame:frame(), + glisten@socket:socket(), + glisten@transport:transport() +) -> {ok, nil} | {error, glisten@socket:socket_reason()}. +send_frame(Frame_to_send, Socket, Transport) -> + Data = mist@internal@http2@frame:encode(Frame_to_send), + glisten@transport:send( + Transport, + Socket, + gleam@bytes_tree:from_bit_array(Data) + ). + +-file("src/mist/internal/http2.gleam", 155). +?DOC(false). +-spec hpack_new_context(integer()) -> hpack_context(). +hpack_new_context(Size) -> + hpack:new_context(Size). + +-file("src/mist/internal/http2.gleam", 158). +?DOC(false). +-spec hpack_max_table_size(hpack_context(), integer()) -> hpack_context(). +hpack_max_table_size(Context, Size) -> + mist_ffi:hpack_new_max_table_size(Context, Size). + +-file("src/mist/internal/http2.gleam", 166). +?DOC(false). +-spec hpack_decode(hpack_context(), bitstring()) -> {ok, + {list({binary(), binary()}), hpack_context()}} | + {error, hpack_error()}. +hpack_decode(Context, Bin) -> + mist_ffi:hpack_decode(Context, Bin). + +-file("src/mist/internal/http2.gleam", 172). +?DOC(false). +-spec hpack_encode(hpack_context(), list({binary(), binary()})) -> {ok, + {bitstring(), hpack_context()}} | + {error, any()}. +hpack_encode(Context, Headers) -> + mist_ffi:hpack_encode(Context, Headers). + +-file("src/mist/internal/http2.gleam", 60). +?DOC(false). +-spec send_headers( + hpack_context(), + mist@internal@http:connection(), + list({binary(), binary()}), + boolean(), + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +) -> {ok, hpack_context()} | {error, binary()}. +send_headers(Context, Conn, Headers, End_stream, Stream_identifier) -> + _pipe = mist_ffi:hpack_encode(Context, Headers), + gleam@result:'try'( + _pipe, + fun(Pair) -> + {Headers@1, New_context} = Pair, + Header_frame = {header, + {complete, Headers@1}, + End_stream, + Stream_identifier, + none}, + Encoded = mist@internal@http2@frame:encode(Header_frame), + case glisten@transport:send( + erlang:element(4, Conn), + erlang:element(3, Conn), + gleam@bytes_tree:from_bit_array(Encoded) + ) of + {ok, _} -> + {ok, New_context}; + + {error, _} -> + {error, <<"Failed to send HTTP/2 headers"/utf8>>} + end + end + ). + +-file("src/mist/internal/http2.gleam", 126). +?DOC(false). +-spec send_bytes_tree( + gleam@http@response:response(gleam@bytes_tree:bytes_tree()), + mist@internal@http:connection(), + hpack_context(), + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()) +) -> {ok, hpack_context()} | {error, binary()}. +send_bytes_tree(Resp, Conn, Context, Id) -> + Resp@1 = begin + _pipe = Resp, + mist@internal@http:add_default_headers(_pipe, false) + end, + Headers = [{<<":status"/utf8>>, + erlang:integer_to_binary(erlang:element(2, Resp@1))} | + erlang:element(3, Resp@1)], + case erlang:iolist_size(erlang:element(4, Resp@1)) of + 0 -> + send_headers(Context, Conn, Headers, true, Id); + + _ -> + _pipe@1 = send_headers(Context, Conn, Headers, false, Id), + gleam@result:'try'( + _pipe@1, + fun(Context@1) -> + _pipe@2 = send_data( + Conn, + erlang:list_to_bitstring(erlang:element(4, Resp@1)), + Id, + true + ), + gleam@result:replace(_pipe@2, Context@1) + end + ) + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http2@flow_control.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@flow_control.erl new file mode 100644 index 0000000..7a902ed --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@flow_control.erl @@ -0,0 +1,50 @@ +-module(mist@internal@http2@flow_control). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http2/flow_control.gleam"). +-export([compute_receive_window/2, update_send_window/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/mist/internal/http2/flow_control.gleam", 3). +?DOC(false). +-spec compute_receive_window(integer(), integer()) -> {integer(), integer()}. +compute_receive_window(Receive_window_size, Data_size) -> + New_receive_window_size = Receive_window_size - Data_size, + Max_window_increment = erlang:'bsl'(1, 31) - 1, + Max_window_size = Max_window_increment, + Min_window_size = erlang:'bsl'(1, 30), + case New_receive_window_size > Min_window_size of + true -> + {New_receive_window_size, 0}; + + false -> + Updated_receive_window_size = gleam@int:min( + New_receive_window_size + Max_window_increment, + Max_window_size + ), + Increment = Updated_receive_window_size - New_receive_window_size, + {Updated_receive_window_size, Increment} + end. + +-file("src/mist/internal/http2/flow_control.gleam", 27). +?DOC(false). +-spec update_send_window(integer(), integer()) -> {ok, integer()} | + {error, binary()}. +update_send_window(Current_send_window, Increment) -> + Max_window_size = erlang:'bsl'(1, 31) - 1, + Update = Current_send_window + Increment, + case Update > Max_window_size of + true -> + {error, <<"Invalid update increment"/utf8>>}; + + false -> + {ok, Update} + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http2@frame.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@frame.erl new file mode 100644 index 0000000..bba23ab --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@frame.erl @@ -0,0 +1,800 @@ +-module(mist@internal@http2@frame). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http2/frame.gleam"). +-export([stream_identifier/1, get_stream_identifier/1, decode/1, encode/1, settings_ack/0]). +-export_type([stream_identifier/1, header_priority/0, data/0, push_state/0, setting/0, frame/0, connection_error/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-opaque stream_identifier(KID) :: {stream_identifier, integer()} | + {gleam_phantom, KID}. + +-type header_priority() :: {header_priority, + boolean(), + stream_identifier(frame()), + integer()}. + +-type data() :: {complete, bitstring()} | {continued, bitstring()}. + +-type push_state() :: enabled | disabled. + +-type setting() :: {header_table_size, integer()} | + {server_push, push_state()} | + {max_concurrent_streams, integer()} | + {initial_window_size, integer()} | + {max_frame_size, integer()} | + {max_header_list_size, integer()}. + +-type frame() :: {data, bitstring(), boolean(), stream_identifier(frame())} | + {header, + data(), + boolean(), + stream_identifier(frame()), + gleam@option:option(header_priority())} | + {priority, + boolean(), + stream_identifier(frame()), + stream_identifier(frame()), + integer()} | + {termination, connection_error(), stream_identifier(frame())} | + {settings, boolean(), list(setting())} | + {push_promise, + data(), + stream_identifier(frame()), + stream_identifier(frame())} | + {ping, boolean(), bitstring()} | + {go_away, bitstring(), connection_error(), stream_identifier(frame())} | + {window_update, integer(), stream_identifier(frame())} | + {continuation, data(), stream_identifier(frame())}. + +-type connection_error() :: no_error | + protocol_error | + internal_error | + flow_control_error | + settings_timeout | + stream_closed | + frame_size_error | + refused_stream | + cancel | + compression_error | + connect_error | + enhance_your_calm | + inadequate_security | + http11_required | + {unsupported, integer()}. + +-file("src/mist/internal/http2/frame.gleam", 11). +?DOC(false). +-spec stream_identifier(integer()) -> stream_identifier(frame()). +stream_identifier(Value) -> + {stream_identifier, Value}. + +-file("src/mist/internal/http2/frame.gleam", 15). +?DOC(false). +-spec get_stream_identifier(stream_identifier(any())) -> integer(). +get_stream_identifier(Identifier) -> + {stream_identifier, Value} = Identifier, + Value. + +-file("src/mist/internal/http2/frame.gleam", 148). +?DOC(false). +-spec parse_data(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_data(Identifier, Flags, Length, Payload) -> + case <> of + <<_:4, + Padding:1, + _:2, + End_stream:1, + Pad_length:Padding/unit:8, + Data_and_padding/bitstring>> when Identifier =/= 0 -> + Data_length = case Padding of + 1 -> + Length - Pad_length; + + 0 -> + Length; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"Somehow a bit was neither 0 nor 1"/utf8>>, + file => <>, + module => <<"mist/internal/http2/frame"/utf8>>, + function => <<"parse_data"/utf8>>, + line => 168}) + end, + case Data_and_padding of + <> -> + {ok, + {data, + Data, + End_stream =:= 1, + stream_identifier(Identifier)}}; + + _ -> + {error, protocol_error} + end; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 185). +?DOC(false). +-spec parse_header(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_header(Identifier, Flags, Length, Payload) -> + case <> of + <<_:2, + Priority:1, + _:1, + Padded:1, + End_headers:1, + _:1, + End_stream:1, + Pad_length:Padded/unit:8, + Exclusive:Priority, + Stream_dependency:Priority/unit:31, + Weight:Priority/unit:8, + Data_and_padding/bitstring>> when (Identifier =/= 0) andalso (Pad_length < Length) -> + Data_length = case {Padded, Priority} of + {1, 1} -> + (Length - Pad_length) - 6; + + {1, 0} -> + (Length - Pad_length) - 1; + + {0, 1} -> + Length - 5; + + {0, 0} -> + Length; + + {_, _} -> + erlang:error(#{gleam_error => panic, + message => <<"Somehow a bit was set to neither 0 nor 1"/utf8>>, + file => <>, + module => <<"mist/internal/http2/frame"/utf8>>, + function => <<"parse_header"/utf8>>, + line => 213}) + end, + case Data_and_padding of + <> -> + {ok, {header, case End_headers of + 1 -> + {complete, Data}; + + 0 -> + {continued, Data}; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"Somehow a bit was set to neither 0 nor 1"/utf8>>, + file => <>, + module => <<"mist/internal/http2/frame"/utf8>>, + function => <<"parse_header"/utf8>>, + line => 223}) + end, (End_stream =:= 1), stream_identifier( + Identifier + ), case Priority =:= 1 of + true -> + {some, + {header_priority, + (Exclusive =:= 1), + stream_identifier(Stream_dependency), + Weight}}; + + false -> + none + end}}; + + _ -> + logging:log(debug, <<"oh noes!"/utf8>>), + {error, protocol_error} + end; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 251). +?DOC(false). +-spec parse_priority(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_priority(Identifier, Flags, Length, Payload) -> + case {Length, <>} of + {5, <<_:8, Exclusive:1, Dependency:31, Weight:8>>} when Identifier =/= 0 -> + {ok, + {priority, + Exclusive =:= 1, + stream_identifier(Identifier), + stream_identifier(Dependency), + Weight}}; + + {5, _} -> + {error, protocol_error}; + + {_, _} -> + {error, frame_size_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 316). +?DOC(false). +-spec parse_push_promise(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_push_promise(Identifier, Flags, Length, Payload) -> + case <> of + <<_:4, + Padded:1, + End_headers:1, + _:2, + Pad_length:Padded/unit:8, + _:1, + Promised_identifier:31, + Data:Length/binary, + _:Pad_length/binary>> when Identifier =/= 0 -> + {ok, {push_promise, case End_headers =:= 1 of + true -> + {complete, Data}; + + false -> + {continued, Data} + end, stream_identifier(Identifier), stream_identifier( + Promised_identifier + )}}; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 349). +?DOC(false). +-spec parse_ping(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_ping(Identifier, Flags, Length, Payload) -> + case {Length, <>} of + {8, <<_:7, Ack:1, Data:64/bitstring>>} when Identifier =:= 0 -> + {ok, {ping, Ack =:= 1, Data}}; + + {8, _} -> + {error, protocol_error}; + + {_, _} -> + {error, frame_size_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 390). +?DOC(false). +-spec parse_window_update(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_window_update(Identifier, Flags, Length, Payload) -> + case {Length, <>} of + {4, <<_:8, _:1, Window_size:31>>} when Window_size =/= 0 -> + {ok, {window_update, Window_size, stream_identifier(Identifier)}}; + + {4, _} -> + {error, frame_size_error}; + + {_, _} -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 410). +?DOC(false). +-spec parse_continuation(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_continuation(Identifier, Flags, Length, Payload) -> + case <> of + <<_:5, End_headers:1, _:2, Data:Length/binary>> when Identifier =/= 0 -> + {ok, {continuation, case End_headers =:= 1 of + true -> + {complete, Data}; + + false -> + {continued, Data} + end, stream_identifier(Identifier)}}; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 594). +?DOC(false). +-spec get_error(integer()) -> connection_error(). +get_error(Value) -> + case Value of + 0 -> + no_error; + + 1 -> + protocol_error; + + 2 -> + internal_error; + + 3 -> + flow_control_error; + + 4 -> + settings_timeout; + + 5 -> + stream_closed; + + 6 -> + frame_size_error; + + 7 -> + refused_stream; + + 8 -> + cancel; + + 9 -> + compression_error; + + 10 -> + connect_error; + + 11 -> + enhance_your_calm; + + 12 -> + inadequate_security; + + 13 -> + http11_required; + + N -> + {unsupported, N} + end. + +-file("src/mist/internal/http2/frame.gleam", 279). +?DOC(false). +-spec parse_termination(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_termination(Identifier, Flags, Length, Payload) -> + case {Length, <>} of + {4, <<_:8, Error:32>>} when Identifier =/= 0 -> + {ok, {termination, get_error(Error), stream_identifier(Identifier)}}; + + {4, _} -> + {error, protocol_error}; + + {_, _} -> + {error, frame_size_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 364). +?DOC(false). +-spec parse_go_away(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_go_away(Identifier, Flags, Length, Payload) -> + case <> of + <<_:8, _:1, Last_stream_id:31, Error:32, Data:Length/binary>> when Identifier =:= 0 -> + {ok, + {go_away, + Data, + get_error(Error), + stream_identifier(Last_stream_id)}}; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 630). +?DOC(false). +-spec get_setting(integer(), integer()) -> {ok, setting()} | + {error, connection_error()}. +get_setting(Identifier, Value) -> + case Identifier of + 1 -> + {ok, {header_table_size, Value}}; + + 2 -> + {ok, {server_push, case Value of + 0 -> + disabled; + + 1 -> + enabled; + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"Somehow a bit was neither 0 nor 1"/utf8>>, + file => <>, + module => <<"mist/internal/http2/frame"/utf8>>, + function => <<"get_setting"/utf8>>, + line => 638}) + end}}; + + 3 -> + {ok, {max_concurrent_streams, Value}}; + + 4 -> + case Value of + N when N > 2147483647 -> + {error, flow_control_error}; + + _ -> + {ok, {initial_window_size, Value}} + end; + + 5 -> + case Value of + N@1 when N@1 > 16777215 -> + {error, protocol_error}; + + _ -> + {ok, {max_frame_size, Value}} + end; + + 6 -> + {ok, {max_header_list_size, Value}}; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 614). +?DOC(false). +-spec get_settings(bitstring(), list(setting())) -> {ok, list(setting())} | + {error, connection_error()}. +get_settings(Data, Acc) -> + case Data of + <<>> -> + {ok, Acc}; + + <> -> + case get_setting(Identifier, Value) of + {ok, Setting} -> + get_settings(Rest, [Setting | Acc]); + + {error, Err} -> + {error, Err} + end; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 297). +?DOC(false). +-spec parse_settings(integer(), bitstring(), integer(), bitstring()) -> {ok, + frame()} | + {error, connection_error()}. +parse_settings(Identifier, Flags, Length, Payload) -> + case {Length rem 6, <>} of + {0, <<_:7, Ack:1, Settings:Length/binary>>} when Identifier =:= 0 -> + gleam@result:'try'( + get_settings(Settings, []), + fun(Settings@1) -> {ok, {settings, Ack =:= 1, Settings@1}} end + ); + + {0, _} -> + {error, protocol_error}; + + {_, _} -> + {error, frame_size_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 105). +?DOC(false). +-spec decode(bitstring()) -> {ok, {frame(), bitstring()}} | + {error, connection_error()}. +decode(Frame) -> + case Frame of + <> -> + _pipe = case Frame_type of + 0 -> + parse_data(Identifier, Flags, Length, Payload); + + 1 -> + parse_header(Identifier, Flags, Length, Payload); + + 2 -> + parse_priority(Identifier, Flags, Length, Payload); + + 3 -> + parse_termination(Identifier, Flags, Length, Payload); + + 4 -> + parse_settings(Identifier, Flags, Length, Payload); + + 5 -> + parse_push_promise(Identifier, Flags, Length, Payload); + + 6 -> + parse_ping(Identifier, Flags, Length, Payload); + + 7 -> + parse_go_away(Identifier, Flags, Length, Payload); + + 8 -> + parse_window_update(Identifier, Flags, Length, Payload); + + 9 -> + parse_continuation(Identifier, Flags, Length, Payload); + + _ -> + {error, protocol_error} + end, + gleam@result:map(_pipe, fun(Frame@1) -> {Frame@1, Rest} end); + + <> -> + case erlang:byte_size(Rest@1) < Length@1 of + true -> + {error, no_error}; + + false -> + {error, protocol_error} + end; + + _ -> + {error, protocol_error} + end. + +-file("src/mist/internal/http2/frame.gleam", 659). +?DOC(false). +-spec from_bool(boolean()) -> integer(). +from_bool(Bool) -> + case Bool of + true -> + 1; + + false -> + 0 + end. + +-file("src/mist/internal/http2/frame.gleam", 666). +?DOC(false). +-spec encode_priority(gleam@option:option(header_priority())) -> bitstring(). +encode_priority(Priority) -> + case Priority of + {some, + {header_priority, + Exclusive, + {stream_identifier, Dependency}, + Weight}} -> + Exclusive@1 = from_bool(Exclusive), + <>; + + none -> + <<>> + end. + +-file("src/mist/internal/http2/frame.gleam", 676). +?DOC(false). +-spec encode_data(data()) -> {integer(), bitstring()}. +encode_data(Data) -> + case Data of + {complete, Data@1} -> + {1, Data@1}; + + {continued, Data@2} -> + {0, Data@2} + end. + +-file("src/mist/internal/http2/frame.gleam", 683). +?DOC(false). +-spec encode_error(connection_error()) -> integer(). +encode_error(Error) -> + case Error of + no_error -> + 0; + + protocol_error -> + 1; + + internal_error -> + 2; + + flow_control_error -> + 3; + + settings_timeout -> + 4; + + stream_closed -> + 5; + + frame_size_error -> + 6; + + refused_stream -> + 7; + + cancel -> + 8; + + compression_error -> + 9; + + connect_error -> + 10; + + enhance_your_calm -> + 11; + + inadequate_security -> + 12; + + http11_required -> + 13; + + {unsupported, _} -> + 69 + end. + +-file("src/mist/internal/http2/frame.gleam", 704). +?DOC(false). +-spec encode_settings(list(setting())) -> bitstring(). +encode_settings(Settings) -> + gleam@list:fold(Settings, <<>>, fun(Acc, Setting) -> case Setting of + {header_table_size, Value} -> + gleam@bit_array:append(Acc, <<1:16, Value:32>>); + + {server_push, enabled} -> + gleam@bit_array:append(Acc, <<2:16, 1:32>>); + + {server_push, disabled} -> + gleam@bit_array:append(Acc, <<2:16, 0:32>>); + + {max_concurrent_streams, Value@1} -> + gleam@bit_array:append(Acc, <<3:16, Value@1:32>>); + + {initial_window_size, Value@2} -> + gleam@bit_array:append(Acc, <<4:16, Value@2:32>>); + + {max_frame_size, Value@3} -> + gleam@bit_array:append(Acc, <<5:16, Value@3:32>>); + + {max_header_list_size, Value@4} -> + gleam@bit_array:append(Acc, <<6:16, Value@4:32>>) + end end). + +-file("src/mist/internal/http2/frame.gleam", 437). +?DOC(false). +-spec encode(frame()) -> bitstring(). +encode(Frame) -> + case Frame of + {data, Data, End_stream, {stream_identifier, Identifier}} -> + Length = erlang:byte_size(Data), + End = from_bool(End_stream), + <>; + + {header, + Data@1, + End_stream@1, + {stream_identifier, Identifier@1}, + Priority} -> + {End_header, Data@2} = encode_data(Data@1), + Length@1 = erlang:byte_size(Data@2), + End@1 = from_bool(End_stream@1), + Priority_flags = encode_priority(Priority), + Has_priority = from_bool(gleam@option:is_some(Priority)), + <>; + + {priority, + Exclusive, + {stream_identifier, Identifier@2}, + {stream_identifier, Dependency}, + Weight} -> + Exclusive@1 = from_bool(Exclusive), + <<5:24, + 2:2, + 0:8, + 0:1, + Identifier@2:31, + Exclusive@1:1, + Dependency:31, + Weight:8>>; + + {termination, Error, {stream_identifier, Identifier@3}} -> + Error_code = encode_error(Error), + <<4:24, 3:8, 0:8, 0:1, Identifier@3:31, Error_code:32>>; + + {settings, Ack, Settings} -> + Ack@1 = from_bool(Ack), + Settings@1 = encode_settings(Settings), + Length@2 = erlang:byte_size(Settings@1), + <>; + + {push_promise, + Data@3, + {stream_identifier, Identifier@4}, + {stream_identifier, Promised_identifier}} -> + {End_headers, Data@4} = encode_data(Data@3), + <<0:24, + 5:8, + 0:4, + 0:0, + End_headers:1, + 0:2, + 0:1, + Identifier@4:31, + 0:1, + Promised_identifier:31, + Data@4/bitstring>>; + + {ping, Ack@2, Data@5} -> + Ack@3 = from_bool(Ack@2), + <<0:24, 6:8, 0:7, Ack@3:1, 0:1, 0:31, Data@5/bitstring>>; + + {go_away, Data@6, Error@1, {stream_identifier, Last_stream_id}} -> + Error@2 = encode_error(Error@1), + Payload_size = erlang:byte_size(Data@6), + <>; + + {window_update, Amount, {stream_identifier, Identifier@5}} -> + <<4:24, 8:8, 0:8, 0:1, Identifier@5:31, 0:1, Amount:31>>; + + {continuation, Data@7, {stream_identifier, Identifier@6}} -> + {End_headers@1, Data@8} = encode_data(Data@7), + Payload_size@1 = erlang:byte_size(Data@8), + <> + end. + +-file("src/mist/internal/http2/frame.gleam", 723). +?DOC(false). +-spec settings_ack() -> frame(). +settings_ack() -> + {settings, true, []}. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http2@handler.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@handler.erl new file mode 100644 index 0000000..1d72853 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@handler.erl @@ -0,0 +1,469 @@ +-module(mist@internal@http2@handler). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http2/handler.gleam"). +-export([send_hpack_context/2, receive_hpack_context/2, append_data/2, upgrade/3, call/3]). +-export_type([pending_send/0, state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type pending_send() :: pending_send. + +-type state() :: {state, + gleam@option:option(mist@internal@http2@frame:frame()), + mist@internal@buffer:buffer(), + list(pending_send()), + mist@internal@http2:hpack_context(), + gleam@erlang@process:subject(mist@internal@http2@stream:send_message()), + mist@internal@http2:hpack_context(), + integer(), + integer(), + mist@internal@http2:http2_settings(), + gleam@dict:dict(mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), mist@internal@http2@stream:state())}. + +-file("src/mist/internal/http2/handler.gleam", 38). +?DOC(false). +-spec send_hpack_context(state(), mist@internal@http2:hpack_context()) -> state(). +send_hpack_context(State, Context) -> + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + Context, + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}. + +-file("src/mist/internal/http2/handler.gleam", 42). +?DOC(false). +-spec receive_hpack_context(state(), mist@internal@http2:hpack_context()) -> state(). +receive_hpack_context(State, Context) -> + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + Context, + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}. + +-file("src/mist/internal/http2/handler.gleam", 46). +?DOC(false). +-spec append_data(state(), bitstring()) -> state(). +append_data(State, Data) -> + {state, + erlang:element(2, State), + mist@internal@buffer:append(erlang:element(3, State), Data), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}. + +-file("src/mist/internal/http2/handler.gleam", 50). +?DOC(false). +-spec upgrade( + bitstring(), + mist@internal@http:connection(), + gleam@erlang@process:subject(mist@internal@http2@stream:send_message()) +) -> {ok, state()} | {error, binary()}. +upgrade(Data, Conn, Self) -> + Initial_settings = mist@internal@http2:default_settings(), + Settings_frame = {settings, false, []}, + Sent = begin + _pipe = mist@internal@http2:send_frame( + Settings_frame, + erlang:element(3, Conn), + erlang:element(4, Conn) + ), + gleam@result:replace_error( + _pipe, + <<"Failed to send settings frame"/utf8>> + ) + end, + gleam@result:map( + Sent, + fun(_) -> + {state, + none, + mist@internal@buffer:new(Data), + [], + hpack:new_context(erlang:element(2, Initial_settings)), + Self, + hpack:new_context(erlang:element(2, Initial_settings)), + 65535, + 65535, + Initial_settings, + maps:new()} + end + ). + +-file("src/mist/internal/http2/handler.gleam", 106). +?DOC(false). +-spec handle_frame( + mist@internal@http2@frame:frame(), + state(), + mist@internal@http:connection(), + fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist@internal@http:response_data())) +) -> {ok, state()} | {error, binary()}. +handle_frame(Frame, State, Conn, Handler) -> + case {erlang:element(2, State), Frame} of + {{some, {header, {continued, Existing}, End_stream, Id1, Priority}}, + {continuation, {complete, Data}, Id2}} when Id1 =:= Id2 -> + Complete_frame = {header, + {complete, <>}, + End_stream, + Id1, + Priority}, + handle_frame( + Complete_frame, + {state, + none, + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}, + Conn, + Handler + ); + + {{some, + {header, + {continued, Existing@1}, + End_stream@1, + Id1@1, + Priority@1}}, + {continuation, {continued, Data@1}, Id2@1}} when Id1@1 =:= Id2@1 -> + Next = {header, + {continued, <>}, + End_stream@1, + Id1@1, + Priority@1}, + {ok, + {state, + {some, Next}, + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}}; + + {none, {window_update, Amount, Identifier}} -> + case mist@internal@http2@frame:get_stream_identifier(Identifier) of + 0 -> + {ok, + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + begin + _record = erlang:element(10, State), + {http2_settings, + erlang:element(2, _record), + erlang:element(3, _record), + erlang:element(4, _record), + Amount, + erlang:element(6, _record), + erlang:element(7, _record)} + end, + erlang:element(11, State)}}; + + _ -> + _pipe = erlang:element(11, State), + _pipe@1 = gleam_stdlib:map_get(_pipe, Identifier), + _pipe@2 = gleam@result:replace_error( + _pipe@1, + <<"Window update for non-existent stream"/utf8>> + ), + gleam@result:'try'( + _pipe@2, + fun(Stream) -> + case mist@internal@http2@flow_control:update_send_window( + erlang:element(6, Stream), + Amount + ) of + {ok, Update} -> + New_stream = {state, + erlang:element(2, Stream), + erlang:element(3, Stream), + erlang:element(4, Stream), + erlang:element(5, Stream), + Update, + erlang:element(7, Stream)}, + {ok, + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + gleam@dict:insert( + erlang:element(11, State), + Identifier, + New_stream + )}}; + + _ -> + {error, + <<"Failed to update send window"/utf8>>} + end + end + ) + end; + + {none, {header, {complete, Data@2}, End_stream@2, Identifier@1, _}} -> + Conn@1 = {connection, + {initial, <<>>}, + erlang:element(3, Conn), + erlang:element(4, Conn), + erlang:element(5, Conn)}, + {Headers@1, Context@1} = case mist_ffi:hpack_decode( + erlang:element(5, State), + Data@2 + ) of + {ok, {Headers, Context}} -> {Headers, Context}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http2/handler"/utf8>>, + function => <<"handle_frame"/utf8>>, + line => 194, + value => _assert_fail, + start => 5479, + 'end' => 5577, + pattern_start => 5490, + pattern_end => 5513}) + end, + Pending_content_length = begin + _pipe@3 = Headers@1, + _pipe@4 = gleam@list:key_find( + _pipe@3, + <<"content-length"/utf8>> + ), + _pipe@5 = gleam@result:'try'( + _pipe@4, + fun gleam_stdlib:parse_int/1 + ), + gleam@option:from_result(_pipe@5) + end, + New_stream@2 = case mist@internal@http2@stream:new( + Identifier@1, + Handler, + Headers@1, + Conn@1, + erlang:element(6, State), + End_stream@2 + ) of + {ok, New_stream@1} -> New_stream@1; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http2/handler"/utf8>>, + function => <<"handle_frame"/utf8>>, + line => 203, + value => _assert_fail@1, + start => 5743, + 'end' => 5996, + pattern_start => 5754, + pattern_end => 5768}) + end, + gleam@erlang@process:send(erlang:element(3, New_stream@2), ready), + Stream_state = {state, + Identifier@1, + open, + erlang:element(3, New_stream@2), + erlang:element(5, erlang:element(10, State)), + erlang:element(5, erlang:element(10, State)), + Pending_content_length}, + Streams = gleam@dict:insert( + erlang:element(11, State), + Identifier@1, + Stream_state + ), + {ok, + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + Context@1, + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + Streams}}; + + {none, {data, Data@3, End_stream@3, Identifier@2}} -> + Data_size = erlang:byte_size(Data@3), + {Conn_receive_window_size, Conn_window_increment} = mist@internal@http2@flow_control:compute_receive_window( + erlang:element(9, State), + Data_size + ), + _pipe@6 = erlang:element(11, State), + _pipe@7 = gleam_stdlib:map_get(_pipe@6, Identifier@2), + _pipe@8 = gleam@result:map( + _pipe@7, + fun(_capture) -> + mist@internal@http2@stream:receive_data(_capture, Data_size) + end + ), + _pipe@9 = gleam@result:replace_error( + _pipe@8, + <<"Stream failed to receive data"/utf8>> + ), + gleam@result:map( + _pipe@9, + fun(Update@1) -> + {New_stream@3, Increment} = Update@1, + _ = case Conn_window_increment > 0 of + true -> + mist@internal@http2:send_frame( + {window_update, + Conn_window_increment, + mist@internal@http2@frame:stream_identifier( + 0 + )}, + erlang:element(3, Conn), + erlang:element(4, Conn) + ); + + false -> + {ok, nil} + end, + _ = case Increment > 0 of + true -> + mist@internal@http2:send_frame( + {window_update, Increment, Identifier@2}, + erlang:element(3, Conn), + erlang:element(4, Conn) + ); + + false -> + {ok, nil} + end, + gleam@erlang@process:send( + erlang:element(4, New_stream@3), + {data, Data@3, End_stream@3} + ), + {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + Conn_receive_window_size, + erlang:element(10, State), + gleam@dict:insert( + erlang:element(11, State), + Identifier@2, + New_stream@3 + )} + end + ); + + {none, {priority, _, _, _, _}} -> + {ok, State}; + + {none, {settings, true, _}} -> + {ok, State}; + + {_, {settings, _, _}} -> + _pipe@10 = mist@internal@http2:send_frame( + mist@internal@http2@frame:settings_ack(), + erlang:element(3, Conn), + erlang:element(4, Conn) + ), + _pipe@11 = gleam@result:replace(_pipe@10, State), + gleam@result:replace_error( + _pipe@11, + <<"Failed to respond to settings ACK"/utf8>> + ); + + {none, {go_away, _, _, _}} -> + logging:log(debug, <<"byteeee~~"/utf8>>), + {error, <<"Going away..."/utf8>>}; + + {_, Frame@1} -> + logging:log( + debug, + <<"Ignoring frame: "/utf8, + (gleam@string:inspect(Frame@1))/binary>> + ), + {ok, State} + end. + +-file("src/mist/internal/http2/handler.gleam", 81). +?DOC(false). +-spec call( + state(), + mist@internal@http:connection(), + fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist@internal@http:response_data())) +) -> {ok, state()} | {error, {ok, nil} | {error, binary()}}. +call(State, Conn, Handler) -> + case mist@internal@http2@frame:decode( + erlang:element(3, erlang:element(3, State)) + ) of + {ok, {Frame, Rest}} -> + New_state = {state, + erlang:element(2, State), + mist@internal@buffer:new(Rest), + erlang:element(4, State), + erlang:element(5, State), + erlang:element(6, State), + erlang:element(7, State), + erlang:element(8, State), + erlang:element(9, State), + erlang:element(10, State), + erlang:element(11, State)}, + case handle_frame(Frame, New_state, Conn, Handler) of + {ok, Updated} -> + call(Updated, Conn, Handler); + + {error, Reason} -> + {error, {error, Reason}} + end; + + {error, no_error} -> + {ok, State}; + + {error, _} -> + {ok, State} + end. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http2@stream.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@stream.erl new file mode 100644 index 0000000..360a26e --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http2@stream.erl @@ -0,0 +1,288 @@ +-module(mist@internal@http2@stream). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http2/stream.gleam"). +-export([make_request/2, new/6, receive_data/2]). +-export_type([message/0, send_message/0, stream_state/0, state/0, internal_state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type message() :: ready | {data, bitstring(), boolean()} | done. + +-type send_message() :: {send, + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + gleam@http@response:response(mist@internal@http:response_data())}. + +-type stream_state() :: open | remote_closed | local_closed | closed. + +-type state() :: {state, + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + stream_state(), + gleam@erlang@process:subject(message()), + integer(), + integer(), + gleam@option:option(integer())}. + +-type internal_state() :: {internal_state, + gleam@erlang@process:selector(message()), + gleam@erlang@process:subject(message()), + boolean(), + gleam@option:option(gleam@http@response:response(mist@internal@http:response_data())), + bitstring()}. + +-file("src/mist/internal/http2/stream.gleam", 142). +?DOC(false). +-spec make_request( + list({binary(), binary()}), + gleam@http@request:request(mist@internal@http:connection()) +) -> {ok, gleam@http@request:request(mist@internal@http:connection())} | + {error, nil}. +make_request(Headers, Req) -> + case Headers of + [] -> + {ok, Req}; + + [{<<"method"/utf8>>, Method} | Rest] -> + _pipe = Method, + _pipe@1 = gleam@http:parse_method(_pipe), + _pipe@2 = gleam@result:replace_error(_pipe@1, nil), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(_capture) -> + gleam@http@request:set_method(Req, _capture) + end + ), + gleam@result:'try'( + _pipe@3, + fun(_capture@1) -> make_request(Rest, _capture@1) end + ); + + [{<<"scheme"/utf8>>, Scheme} | Rest@1] -> + _pipe@4 = Scheme, + _pipe@5 = gleam@http:scheme_from_string(_pipe@4), + _pipe@6 = gleam@result:replace_error(_pipe@5, nil), + _pipe@7 = gleam@result:map( + _pipe@6, + fun(_capture@2) -> + gleam@http@request:set_scheme(Req, _capture@2) + end + ), + gleam@result:'try'( + _pipe@7, + fun(_capture@3) -> make_request(Rest@1, _capture@3) end + ); + + [{<<"authority"/utf8>>, _} | Rest@2] -> + make_request(Rest@2, Req); + + [{<<"path"/utf8>>, Path} | Rest@3] -> + _pipe@8 = Path, + _pipe@9 = gleam@string:split_once(_pipe@8, <<"?"/utf8>>), + _pipe@13 = gleam@result:map( + _pipe@9, + fun(Split) -> + gleam@pair:map_second(Split, fun(Query) -> _pipe@10 = Query, + _pipe@11 = gleam_stdlib:parse_query(_pipe@10), + _pipe@12 = gleam@result:map( + _pipe@11, + fun(Field@0) -> {some, Field@0} end + ), + gleam@result:unwrap(_pipe@12, none) end) + end + ), + _pipe@14 = gleam@result:unwrap(_pipe@13, {Path, none}), + (fun(Tup) -> _pipe@17 = case erlang:element(2, Tup) of + {some, Query@1} -> + _pipe@15 = Req, + _pipe@16 = gleam@http@request:set_path( + _pipe@15, + erlang:element(1, Tup) + ), + gleam@http@request:set_query(_pipe@16, Query@1); + + _ -> + gleam@http@request:set_path(Req, erlang:element(1, Tup)) + end, + make_request(Rest@3, _pipe@17) end)(_pipe@14); + + [{Key, Value} | Rest@4] -> + _pipe@18 = Req, + _pipe@19 = gleam@http@request:set_header(_pipe@18, Key, Value), + make_request(Rest@4, _pipe@19) + end. + +-file("src/mist/internal/http2/stream.gleam", 57). +?DOC(false). +-spec new( + mist@internal@http2@frame:stream_identifier(mist@internal@http2@frame:frame()), + fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist@internal@http:response_data())), + list({binary(), binary()}), + mist@internal@http:connection(), + gleam@erlang@process:subject(send_message()), + boolean() +) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(message()))} | + {error, gleam@otp@actor:start_error()}. +new(Identifier, Handler, Headers, Connection, Sender, End) -> + _pipe@5 = gleam@otp@actor:new_with_initialiser( + 1000, + fun(Subject) -> + Data_selector = begin + _pipe = gleam_erlang_ffi:new_selector(), + gleam@erlang@process:select(_pipe, Subject) + end, + _pipe@1 = {internal_state, Data_selector, Subject, End, none, <<>>}, + _pipe@2 = gleam@otp@actor:initialised(_pipe@1), + _pipe@3 = gleam@otp@actor:selecting(_pipe@2, Data_selector), + _pipe@4 = gleam@otp@actor:returning(_pipe@3, Subject), + {ok, _pipe@4} + end + ), + _pipe@11 = gleam@otp@actor:on_message( + _pipe@5, + fun(State, Msg) -> case {Msg, erlang:element(4, State)} of + {ready, _} -> + Content_length = begin + _pipe@6 = Headers, + _pipe@7 = gleam@list:key_find( + _pipe@6, + <<"content-length"/utf8>> + ), + _pipe@8 = gleam@result:'try'( + _pipe@7, + fun gleam_stdlib:parse_int/1 + ), + gleam@result:unwrap(_pipe@8, 0) + end, + Conn = {connection, + {stream, + gleam_erlang_ffi:map_selector( + erlang:element(2, State), + fun(Val) -> + Bits@1 = case Val of + {data, Bits, _} -> Bits; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http2/stream"/utf8>>, + function => <<"new"/utf8>>, + line => 88, + value => _assert_fail, + start => 2215, + 'end' => 2246, + pattern_start => 2226, + pattern_end => 2240} + ) + end, + Bits@1 + end + ), + <<>>, + Content_length, + 0}, + erlang:element(3, Connection), + erlang:element(4, Connection), + erlang:element(5, Connection)}, + Result = begin + _pipe@9 = gleam@http@request:new(), + _pipe@10 = gleam@http@request:set_body(_pipe@9, Conn), + make_request(Headers, _pipe@10) + end, + case Result of + {ok, Value} -> + Resp = Handler(Value), + gleam@erlang@process:send( + erlang:element(3, State), + done + ), + gleam@otp@actor:continue( + {internal_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + {some, Resp}, + erlang:element(6, State)} + ); + + {error, Err} -> + gleam@otp@actor:stop_abnormal( + <<"Failed to respond to request: "/utf8, + (gleam@string:inspect(Err))/binary>> + ) + end; + + {done, true} -> + Resp@2 = case erlang:element(5, State) of + {some, Resp@1} -> Resp@1; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http2/stream"/utf8>>, + function => <<"new"/utf8>>, + line => 115, + value => _assert_fail@1, + start => 2947, + 'end' => 2993, + pattern_start => 2958, + pattern_end => 2968}) + end, + gleam@erlang@process:send( + Sender, + {send, Identifier, Resp@2} + ), + gleam@otp@actor:continue(State); + + {{data, Bits@2, true}, _} -> + gleam@erlang@process:send(erlang:element(3, State), done), + gleam@otp@actor:continue( + {internal_state, + erlang:element(2, State), + erlang:element(3, State), + true, + erlang:element(5, State), + <<(erlang:element(6, State))/bitstring, + Bits@2/bitstring>>} + ); + + {{data, Bits@3, _}, _} -> + gleam@otp@actor:continue( + {internal_state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + <<(erlang:element(6, State))/bitstring, + Bits@3/bitstring>>} + ); + + {_, _} -> + gleam@otp@actor:continue(State) + end end + ), + gleam@otp@actor:start(_pipe@11). + +-file("src/mist/internal/http2/stream.gleam", 194). +?DOC(false). +-spec receive_data(state(), integer()) -> {state(), integer()}. +receive_data(State, Size) -> + {New_window_size, Increment} = mist@internal@http2@flow_control:compute_receive_window( + erlang:element(5, State), + Size + ), + New_state = {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + New_window_size, + erlang:element(6, State), + gleam@option:map(erlang:element(7, State), fun(Val) -> Val - Size end)}, + {New_state, Increment}. diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@http@handler.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@http@handler.erl new file mode 100644 index 0000000..b06f45f --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@http@handler.erl @@ -0,0 +1,302 @@ +-module(mist@internal@http@handler). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/http/handler.gleam"). +-export([initial_state/0, call/4]). +-export_type([state/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type state() :: {state, gleam@option:option(gleam@erlang@process:timer())}. + +-file("src/mist/internal/http/handler.gleam", 26). +?DOC(false). +-spec initial_state() -> state(). +initial_state() -> + {state, none}. + +-file("src/mist/internal/http/handler.gleam", 65). +?DOC(false). +-spec log_and_error( + exception:exception(), + glisten@socket:socket(), + glisten@transport:transport(), + gleam@http@request:request(mist@internal@http:connection()), + mist@internal@http:http_version() +) -> {ok, nil} | {error, binary()}. +log_and_error(Error, Socket, Transport, Req, Version) -> + Error_string = gleam@string:inspect(Error), + logging:log(error, Error_string), + Resp = begin + _pipe = gleam@http@response:new(500), + _pipe@1 = gleam@http@response:set_body( + _pipe, + gleam@bytes_tree:from_bit_array(<<"Internal Server Error"/utf8>>) + ), + _pipe@2 = gleam@http@response:prepend_header( + _pipe@1, + <<"content-length"/utf8>>, + <<"21"/utf8>> + ), + mist@internal@http:add_default_headers( + _pipe@2, + erlang:element(2, Req) =:= head + ) + end, + Resp@1 = case Version of + http1 -> + mist@internal@http:connection_close(Resp); + + _ -> + mist@internal@http:maybe_keep_alive(Resp) + end, + _ = begin + _pipe@3 = Resp@1, + _pipe@4 = mist@internal@encoder:to_bytes_tree( + _pipe@3, + mist@internal@http:version_to_string(Version) + ), + glisten@transport:send(Transport, Socket, _pipe@4) + end, + _ = glisten@transport:close(Transport, Socket), + {error, Error_string}. + +-file("src/mist/internal/http/handler.gleam", 96). +?DOC(false). +-spec close_or_set_timer( + gleam@http@response:response(gleam@bytes_tree:bytes_tree()), + mist@internal@http:connection(), + gleam@erlang@process:subject(glisten@internal@handler:message(any())) +) -> {ok, state()} | {error, {ok, nil} | {error, binary()}}. +close_or_set_timer(Resp, Conn, Sender) -> + case gleam@http@response:get_header(Resp, <<"connection"/utf8>>) of + {ok, <<"close"/utf8>>} -> + _ = glisten@transport:close( + erlang:element(4, Conn), + erlang:element(3, Conn) + ), + {error, {ok, nil}}; + + _ -> + Timer = gleam@erlang@process:send_after( + Sender, + 10000, + {internal, close} + ), + {ok, {state, {some, Timer}}} + end. + +-file("src/mist/internal/http/handler.gleam", 116). +?DOC(false). +-spec handle_file_body( + gleam@http@response:response(mist@internal@http:response_data()), + mist@internal@http:response_data(), + mist@internal@http:connection(), + mist@internal@http:http_version() +) -> {ok, gleam@http@response:response(gleam@bytes_tree:bytes_tree())} | + {error, glisten@socket:socket_reason()}. +handle_file_body(Resp, Body, Conn, Http_version) -> + {File_descriptor@1, Offset@1, Length@1} = case Body of + {file, File_descriptor, Offset, Length} -> { + File_descriptor, + Offset, + Length}; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/http/handler"/utf8>>, + function => <<"handle_file_body"/utf8>>, + line => 122, + value => _assert_fail, + start => 3411, + 'end' => 3466, + pattern_start => 3422, + pattern_end => 3459}) + end, + Resp@1 = begin + _pipe = Resp, + _pipe@1 = gleam@http@response:set_body(_pipe, gleam@bytes_tree:new()), + _pipe@2 = mist@internal@http:add_date_header(_pipe@1), + gleam@http@response:prepend_header( + _pipe@2, + <<"content-length"/utf8>>, + erlang:integer_to_binary(Length@1) + ) + end, + Resp@2 = case Http_version of + http1 -> + mist@internal@http:connection_close(Resp@1); + + _ -> + mist@internal@http:maybe_keep_alive(Resp@1) + end, + Return = begin + _pipe@3 = Resp@2, + _pipe@4 = (fun(R) -> + mist@internal@encoder:response_builder( + erlang:element(2, Resp@2), + erlang:element(3, R), + mist@internal@http:version_to_string(Http_version) + ) + end)(_pipe@3), + _pipe@5 = glisten@transport:send( + erlang:element(4, Conn), + erlang:element(3, Conn), + _pipe@4 + ), + _pipe@7 = gleam@result:'try'( + _pipe@5, + fun(_) -> + _pipe@6 = mist@internal@file:sendfile( + erlang:element(4, Conn), + File_descriptor@1, + erlang:element(3, Conn), + Offset@1, + Length@1, + [] + ), + gleam@result:map_error( + _pipe@6, + fun(Err) -> + logging:log( + error, + <<"Failed to send file: "/utf8, + (gleam@string:inspect(Err))/binary>> + ), + badarg + end + ) + end + ), + gleam@result:replace(_pipe@7, Resp@2) + end, + case mist_ffi:file_close(File_descriptor@1) of + {ok, _} -> + nil; + + {error, Reason} -> + logging:log( + error, + <<"Failed to close file: "/utf8, + (mist@internal@file:error_to_string(Reason))/binary>> + ) + end, + Return. + +-file("src/mist/internal/http/handler.gleam", 176). +?DOC(false). +-spec handle_bytes_tree_body( + gleam@http@response:response(mist@internal@http:response_data()), + gleam@bytes_tree:bytes_tree(), + mist@internal@http:connection(), + gleam@http@request:request(mist@internal@http:connection()), + mist@internal@http:http_version() +) -> {ok, gleam@http@response:response(gleam@bytes_tree:bytes_tree())} | + {error, glisten@socket:socket_reason()}. +handle_bytes_tree_body(Resp, Body, Conn, Req, Version) -> + Resp@1 = begin + _pipe = Resp, + _pipe@1 = gleam@http@response:set_body(_pipe, Body), + mist@internal@http:add_default_headers( + _pipe@1, + erlang:element(2, Req) =:= head + ) + end, + Resp@2 = case Version of + http1 -> + mist@internal@http:connection_close(Resp@1); + + _ -> + mist@internal@http:maybe_keep_alive(Resp@1) + end, + _pipe@2 = Resp@2, + _pipe@3 = mist@internal@encoder:to_bytes_tree( + _pipe@2, + mist@internal@http:version_to_string(Version) + ), + _pipe@4 = glisten@transport:send( + erlang:element(4, Conn), + erlang:element(3, Conn), + _pipe@3 + ), + gleam@result:replace(_pipe@4, Resp@2). + +-file("src/mist/internal/http/handler.gleam", 30). +?DOC(false). +-spec call( + gleam@http@request:request(mist@internal@http:connection()), + fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist@internal@http:response_data())), + gleam@erlang@process:subject(glisten@internal@handler:message(any())), + mist@internal@http:http_version() +) -> {ok, state()} | {error, {ok, nil} | {error, binary()}}. +call(Req, Handler, Sender, Version) -> + _pipe = exception_ffi:rescue(fun() -> Handler(Req) end), + _pipe@1 = gleam@result:map_error( + _pipe, + fun(_capture) -> + log_and_error( + _capture, + erlang:element(3, erlang:element(4, Req)), + erlang:element(4, erlang:element(4, Req)), + Req, + Version + ) + end + ), + gleam@result:'try'(_pipe@1, fun(Resp) -> case Resp of + {response, _, _, websocket} -> + {error, {ok, nil}}; + + {response, _, _, server_sent_events} -> + {error, {ok, nil}}; + + {response, _, _, chunked} -> + {error, {ok, nil}}; + + {response, _, _, Body} = Resp@1 -> + _pipe@2 = case Body of + {bytes, Body@1} -> + handle_bytes_tree_body( + Resp@1, + Body@1, + erlang:element(4, Req), + Req, + Version + ); + + {file, _, _, _} -> + handle_file_body( + Resp@1, + Body, + erlang:element(4, Req), + Version + ); + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"This shouldn't ever happen 🤞"/utf8>>, + file => <>, + module => <<"mist/internal/http/handler"/utf8>>, + function => <<"call"/utf8>>, + line => 56}) + end, + _pipe@3 = gleam@result:replace_error(_pipe@2, {ok, nil}), + gleam@result:'try'( + _pipe@3, + fun(_capture@1) -> + close_or_set_timer( + _capture@1, + erlang:element(4, Req), + Sender + ) + end + ) + end end). diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@next.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@next.erl new file mode 100644 index 0000000..9c59220 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@next.erl @@ -0,0 +1,22 @@ +-module(mist@internal@next). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/next.gleam"). +-export_type([next/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type next(LIR, LIS) :: {continue, + LIR, + gleam@option:option(gleam@erlang@process:selector(LIS))} | + normal_stop | + {abnormal_stop, binary()}. + + diff --git a/examples/http_interactions/build/packages/mist/src/mist@internal@websocket.erl b/examples/http_interactions/build/packages/mist/src/mist@internal@websocket.erl new file mode 100644 index 0000000..9d58f47 --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist@internal@websocket.erl @@ -0,0 +1,606 @@ +-module(mist@internal@websocket). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/mist/internal/websocket.gleam"). +-export([set_active/2, initialize_connection/6]). +-export_type([valid_message/1, websocket_message/1, websocket_connection/0, handler_message/1, websocket_state/1]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type valid_message(LIW) :: {socket_message, bitstring()} | + socket_closed_message | + {user_message, LIW}. + +-type websocket_message(LIX) :: {valid, valid_message(LIX)} | invalid. + +-type websocket_connection() :: {websocket_connection, + glisten@socket:socket(), + glisten@transport:transport(), + gleam@option:option(gramps@websocket@compression:context())}. + +-type handler_message(LIY) :: {internal, gramps@websocket:frame()} | {user, LIY}. + +-type websocket_state(LIZ) :: {websocket_state, + bitstring(), + LIZ, + gleam@option:option(gramps@websocket@compression:compression())}. + +-file("src/mist/internal/websocket.gleam", 54). +?DOC(false). +-spec message_selector() -> gleam@erlang@process:selector(websocket_message(any())). +message_selector() -> + _pipe = gleam_erlang_ffi:new_selector(), + _pipe@5 = gleam@erlang@process:select_record( + _pipe, + erlang:binary_to_atom(<<"tcp"/utf8>>), + 2, + fun(Record) -> + _pipe@1 = begin + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_bit_array/1}, + fun(Data) -> + gleam@dynamic@decode:success({socket_message, Data}) + end + ) + end, + _pipe@2 = gleam@dynamic@decode:run(Record, _pipe@1), + _pipe@3 = gleam@result:replace_error(_pipe@2, nil), + _pipe@4 = gleam@result:map( + _pipe@3, + fun(Field@0) -> {valid, Field@0} end + ), + gleam@result:unwrap(_pipe@4, invalid) + end + ), + _pipe@10 = gleam@erlang@process:select_record( + _pipe@5, + erlang:binary_to_atom(<<"ssl"/utf8>>), + 2, + fun(Record@1) -> + _pipe@6 = begin + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_bit_array/1}, + fun(Data@1) -> + gleam@dynamic@decode:success({socket_message, Data@1}) + end + ) + end, + _pipe@7 = gleam@dynamic@decode:run(Record@1, _pipe@6), + _pipe@8 = gleam@result:replace_error(_pipe@7, nil), + _pipe@9 = gleam@result:map( + _pipe@8, + fun(Field@0) -> {valid, Field@0} end + ), + gleam@result:unwrap(_pipe@9, invalid) + end + ), + _pipe@11 = gleam@erlang@process:select_record( + _pipe@10, + erlang:binary_to_atom(<<"ssl_closed"/utf8>>), + 1, + fun(_) -> {valid, socket_closed_message} end + ), + gleam@erlang@process:select_record( + _pipe@11, + erlang:binary_to_atom(<<"tcp_closed"/utf8>>), + 1, + fun(_) -> {valid, socket_closed_message} end + ). + +-file("src/mist/internal/websocket.gleam", 386). +?DOC(false). +-spec set_active(glisten@transport:transport(), glisten@socket:socket()) -> nil. +set_active(Transport, Socket) -> + case glisten@transport:set_opts(Transport, Socket, [{active_mode, once}]) of + {ok, _} -> nil; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/websocket"/utf8>>, + function => <<"set_active"/utf8>>, + line => 387, + value => _assert_fail, + start => 12009, + 'end' => 12105, + pattern_start => 12020, + pattern_end => 12025}) + end, + nil. + +-file("src/mist/internal/websocket.gleam", 393). +?DOC(false). +-spec map_user_selector(gleam@option:option(gleam@erlang@process:selector(LKD))) -> gleam@option:option(gleam@erlang@process:selector(websocket_message(LKD))). +map_user_selector(Selector) -> + gleam@option:map( + Selector, + fun(_capture) -> + gleam_erlang_ffi:map_selector( + _capture, + fun(Msg) -> {valid, {user_message, Msg}} end + ) + end + ). + +-file("src/mist/internal/websocket.gleam", 291). +?DOC(false). +-spec apply_frames( + list(gramps@websocket:frame()), + fun((LJT, handler_message(LJU), websocket_connection()) -> mist@internal@next:next(LJT, LJU)), + websocket_connection(), + mist@internal@next:next(LJT, websocket_message(LJU)), + fun((LJT) -> nil) +) -> mist@internal@next:next(LJT, websocket_message(LJU)). +apply_frames(Frames, Handler, Connection, Next, On_close) -> + case {Frames, Next} of + {_, {abnormal_stop, Reason}} -> + {abnormal_stop, Reason}; + + {_, normal_stop} -> + normal_stop; + + {[], Next@1} -> + set_active( + erlang:element(3, Connection), + erlang:element(2, Connection) + ), + Next@1; + + {[{control, {close_frame, Reason@1}} | _], {continue, State, _}} -> + _ = glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + gramps@websocket:encode_close_frame(Reason@1, none) + ), + On_close(State), + normal_stop; + + {[{control, {ping_frame, Payload}} | Rest], + {continue, State@1, _} = Continue} -> + _pipe = glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + gramps@websocket:encode_pong_frame(Payload, none) + ), + _pipe@1 = gleam@result:map( + _pipe, + fun(_) -> + set_active( + erlang:element(3, Connection), + erlang:element(2, Connection) + ), + apply_frames(Rest, Handler, Connection, Continue, On_close) + end + ), + gleam@result:lazy_unwrap( + _pipe@1, + fun() -> + On_close(State@1), + {abnormal_stop, <<"Failed to send pong frame"/utf8>>} + end + ); + + {[Frame | Rest@1], {continue, State@2, Prev_selector}} -> + case exception_ffi:rescue( + fun() -> Handler(State@2, {internal, Frame}, Connection) end + ) of + {ok, {continue, State@3, Selector}} -> + Next_selector = begin + _pipe@2 = Selector, + _pipe@3 = map_user_selector(_pipe@2), + _pipe@4 = gleam@option:'or'(_pipe@3, Prev_selector), + gleam@option:map( + _pipe@4, + fun(With_user) -> + gleam_erlang_ffi:merge_selector( + message_selector(), + With_user + ) + end + ) + end, + apply_frames( + Rest@1, + Handler, + Connection, + {continue, State@3, Next_selector}, + On_close + ); + + {ok, {abnormal_stop, Reason@2}} -> + _ = glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + gramps@websocket:encode_close_frame( + {custom_close_reason, + 4000, + gleam_stdlib:identity(Reason@2)}, + none + ) + ), + On_close(State@2), + {abnormal_stop, Reason@2}; + + {ok, normal_stop} -> + _ = glisten@transport:send( + erlang:element(3, Connection), + erlang:element(2, Connection), + gramps@websocket:encode_close_frame( + {normal, <<>>}, + none + ) + ), + On_close(State@2), + normal_stop; + + {error, _} -> + logging:log( + error, + <<"Caught error in websocket handler"/utf8>> + ), + On_close(State@2), + {abnormal_stop, <<"Crash in user websocket handler"/utf8>>} + end + end. + +-file("src/mist/internal/websocket.gleam", 84). +?DOC(false). +-spec initialize_connection( + fun((websocket_connection()) -> {LJI, + gleam@option:option(gleam@erlang@process:selector(LJJ))}), + fun((LJI) -> nil), + fun((LJI, handler_message(LJJ), websocket_connection()) -> mist@internal@next:next(LJI, LJJ)), + glisten@socket:socket(), + glisten@transport:transport(), + list(binary()) +) -> {ok, gleam@otp@actor:started(gleam@erlang@process:pid_())} | + {error, gleam@otp@actor:start_error()}. +initialize_connection(On_init, On_close, Handler, Socket, Transport, Extensions) -> + Takeovers = gramps@websocket:get_context_takeovers(Extensions), + _pipe@7 = gleam@otp@actor:new_with_initialiser( + 500, + fun(Subject) -> + Compression = case gramps@websocket:has_deflate(Extensions) of + true -> + {some, gramps@websocket@compression:init(Takeovers)}; + + false -> + none + end, + Connection = {websocket_connection, + Socket, + Transport, + gleam@option:map( + Compression, + fun(Compression@1) -> erlang:element(3, Compression@1) end + )}, + {Initial_state, User_selector} = On_init(Connection), + Selector = case User_selector of + {some, User_selector@1} -> + _pipe = User_selector@1, + _pipe@1 = gleam_erlang_ffi:map_selector( + _pipe, + fun(Field@0) -> {user_message, Field@0} end + ), + _pipe@2 = gleam_erlang_ffi:map_selector( + _pipe@1, + fun(Field@0) -> {valid, Field@0} end + ), + gleam_erlang_ffi:merge_selector(_pipe@2, message_selector()); + + _ -> + message_selector() + end, + _pipe@3 = {websocket_state, <<>>, Initial_state, Compression}, + _pipe@4 = gleam@otp@actor:initialised(_pipe@3), + _pipe@5 = gleam@otp@actor:selecting(_pipe@4, Selector), + _pipe@6 = gleam@otp@actor:returning(_pipe@5, Subject), + {ok, _pipe@6} + end + ), + _pipe@16 = gleam@otp@actor:on_message( + _pipe@7, + fun(State, Msg) -> + Connection@1 = {websocket_connection, + Socket, + Transport, + gleam@option:map( + erlang:element(4, State), + fun(Compression@2) -> erlang:element(3, Compression@2) end + )}, + case Msg of + {valid, {socket_message, Data}} -> + {Frames, Rest} = gramps@websocket:decode_many_frames( + <<(erlang:element(2, State))/bitstring, Data/bitstring>>, + gleam@option:map( + erlang:element(4, State), + fun(Compression@3) -> + erlang:element(2, Compression@3) + end + ), + [] + ), + _pipe@8 = Frames, + _pipe@9 = gramps@websocket:aggregate_frames( + _pipe@8, + none, + [] + ), + _pipe@10 = gleam@result:map( + _pipe@9, + fun(Frames@1) -> + Next = apply_frames( + Frames@1, + Handler, + Connection@1, + {continue, erlang:element(3, State), none}, + On_close + ), + case Next of + {continue, User_state, Selector@1} -> + Next@1 = gleam@otp@actor:continue( + {websocket_state, + Rest, + User_state, + erlang:element(4, State)} + ), + case Selector@1 of + {some, Selector@2} -> + gleam@otp@actor:with_selector( + Next@1, + Selector@2 + ); + + _ -> + Next@1 + end; + + normal_stop -> + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts) + ) + end + ), + gleam@otp@actor:stop(); + + {abnormal_stop, Reason} -> + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@1) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@1) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@1) + ) + end + ), + gleam@otp@actor:stop_abnormal(Reason) + end + end + ), + gleam@result:lazy_unwrap( + _pipe@10, + fun() -> + logging:log( + error, + <<"Received a malformed WebSocket frame"/utf8>> + ), + On_close(erlang:element(3, State)), + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@2) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@2) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@2) + ) + end + ), + gleam@otp@actor:stop_abnormal( + <<"WebSocket received a malformed message"/utf8>> + ) + end + ); + + {valid, {user_message, Msg@1}} -> + _pipe@11 = exception_ffi:rescue( + fun() -> + Handler( + erlang:element(3, State), + {user, Msg@1}, + Connection@1 + ) + end + ), + _pipe@14 = gleam@result:map( + _pipe@11, + fun(Cont) -> case Cont of + {continue, User_state@1, Selector@3} -> + Selector@4 = begin + _pipe@12 = Selector@3, + _pipe@13 = map_user_selector(_pipe@12), + gleam@option:map( + _pipe@13, + fun(With_user) -> + gleam_erlang_ffi:merge_selector( + message_selector(), + With_user + ) + end + ) + end, + Next@2 = gleam@otp@actor:continue( + {websocket_state, + erlang:element(2, State), + User_state@1, + erlang:element(4, State)} + ), + case Selector@4 of + {some, Selector@5} -> + gleam@otp@actor:with_selector( + Next@2, + Selector@5 + ); + + _ -> + Next@2 + end; + + normal_stop -> + _ = glisten@transport:send( + erlang:element(3, Connection@1), + erlang:element(2, Connection@1), + gramps@websocket:encode_close_frame( + {normal, <<>>}, + none + ) + ), + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@3) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@3) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@3) + ) + end + ), + On_close(erlang:element(3, State)), + gleam@otp@actor:stop(); + + {abnormal_stop, Reason@1} -> + _ = glisten@transport:send( + erlang:element(3, Connection@1), + erlang:element(2, Connection@1), + gramps@websocket:encode_close_frame( + {custom_close_reason, + 4000, + gleam_stdlib:identity(Reason@1)}, + none + ) + ), + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@4) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@4) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@4) + ) + end + ), + On_close(erlang:element(3, State)), + gleam@otp@actor:stop_abnormal(Reason@1) + end end + ), + _pipe@15 = gleam@result:map_error( + _pipe@14, + fun(_) -> + logging:log( + error, + <<"Caught error in websocket handler"/utf8>> + ) + end + ), + gleam@result:lazy_unwrap( + _pipe@15, + fun() -> + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@5) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@5) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@5) + ) + end + ), + On_close(erlang:element(3, State)), + gleam@otp@actor:stop_abnormal( + <<"Crash in user websocket handler"/utf8>> + ) + end + ); + + {valid, socket_closed_message} -> + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@6) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@6) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@6) + ) + end + ), + On_close(erlang:element(3, State)), + gleam@otp@actor:stop(); + + invalid -> + logging:log( + error, + <<"Received a malformed WebSocket frame"/utf8>> + ), + _ = gleam@option:map( + erlang:element(4, State), + fun(Contexts@7) -> + gramps@websocket@compression:close( + erlang:element(3, Contexts@7) + ), + gramps@websocket@compression:close( + erlang:element(2, Contexts@7) + ) + end + ), + On_close(erlang:element(3, State)), + gleam@otp@actor:stop_abnormal( + <<"WebSocket received a malformed message"/utf8>> + ) + end + end + ), + _pipe@17 = gleam@otp@actor:start(_pipe@16), + gleam@result:map( + _pipe@17, + fun(Subj) -> + Websocket_pid@1 = case gleam@erlang@process:subject_owner( + erlang:element(3, Subj) + ) of + {ok, Websocket_pid} -> Websocket_pid; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"mist/internal/websocket"/utf8>>, + function => <<"initialize_connection"/utf8>>, + line => 286, + value => _assert_fail, + start => 9039, + 'end' => 9102, + pattern_start => 9050, + pattern_end => 9067}) + end, + {started, Websocket_pid@1, Websocket_pid@1} + end + ). diff --git a/examples/http_interactions/build/packages/mist/src/mist_ffi.erl b/examples/http_interactions/build/packages/mist/src/mist_ffi.erl new file mode 100644 index 0000000..5bc4a9f --- /dev/null +++ b/examples/http_interactions/build/packages/mist/src/mist_ffi.erl @@ -0,0 +1,112 @@ +-module(mist_ffi). + +-export([binary_match/2, decode_packet/3, decode_atom/1,file_open/1, string_to_int/2, hpack_decode/2, + hpack_encode/2, hpack_new_max_table_size/2, ets_lookup_element/3, get_path_and_query/1, + file_close/1, now/0]). + +now() -> + Timestamp = os:system_time(microsecond), + {Date, Time} = calendar:system_time_to_universal_time(Timestamp, microsecond), + Weekday = calendar:day_of_the_week(Date), + {Weekday, Date, Time}. + +decode_packet(Type, Packet, Opts) -> + case erlang:decode_packet(Type, Packet, Opts) of + {ok, http_eoh, Rest} -> + {ok, {end_of_headers, Rest}}; + {ok, {http_request, <<"PRI">>, '*', {2, 0}}, Rest} -> + {ok, {http2_upgrade, Rest}}; + {ok, Binary, Rest} -> + {ok, {binary_data, Binary, Rest}}; + {more, undefined} -> + {ok, {more_data, none}}; + {more, Length} -> + {ok, {more_data, {some, Length}}}; + {error, Reason} -> + {error, Reason} + end. + +binary_match(Source, Pattern) -> + case binary:match(Source, Pattern) of + {Before, After} -> + {ok, {Before, After}}; + nomatch -> + {error, nil} + end. + +string_to_int(String, Base) -> + try + {ok, erlang:list_to_integer(String, Base)} + catch + error:badarg -> + {error, nil} + end. + +file_open(Path) -> + case file:open(Path, [raw, binary]) of + {ok, Fd} -> + {ok, Fd}; + {error, enoent} -> + {error, no_entry}; + {error, eacces} -> + {error, no_access}; + {error, eisdir} -> + {error, is_dir}; + _ -> + {error, unknown_file_error} + end. + +file_close(File) -> + case file:close(File) of + ok -> + {ok, nil}; + {error, enoent} -> + {error, no_entry}; + {error, eacces} -> + {error, no_access}; + {error, eisdir} -> + {error, is_dir}; + _ -> + {error, unknown_file_error} + end. + +hpack_decode(Context, Bin) -> + case hpack:decode(Bin, Context) of + {ok, {Headers, NewContext}} -> + {ok, {Headers, NewContext}}; + {error, compression_error} -> + {error, {hpack_error, compression}}; + {error, {compression_error, {bad_header_packet, Binary}}} -> + {error, {hpack_error, {bad_header_packet, Binary}}} + end. + +hpack_encode(Context, Headers) -> + hpack:encode(Headers, Context). + +hpack_new_max_table_size(Context, Size) -> + hpack:new_max_table_size(Size, Context). + +ets_lookup_element(Table, Key, Position) -> + try + {ok, ets:lookup_element(Table, Key, Position)} + catch + error:badarg -> + {error, nil} + end. + +get_path_and_query(String) -> + case uri_string:parse(String) of + {error, Value, Term} -> + {error, {Value, Term}}; + UriMap -> + Query = + case maps:find(query, UriMap) of + error -> + {error, nil}; + Ok -> Ok + end, + {ok, {maps:get(path, UriMap), Query}} + end. + +decode_atom(Value) when is_atom(Value) -> {ok, Value}; +decode_atom(_Value) -> {error, nil}. diff --git a/examples/http_interactions/build/packages/multipart_form/LICENSE b/examples/http_interactions/build/packages/multipart_form/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/multipart_form/README.md b/examples/http_interactions/build/packages/multipart_form/README.md new file mode 100644 index 0000000..c8fc564 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/README.md @@ -0,0 +1,42 @@ +# multipart_form + +[![Package Version](https://img.shields.io/hexpm/v/multipart_form)](https://hex.pm/packages/multipart_form) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/multipart_form/) + +```sh +gleam add multipart_form@1 +``` + +```gleam +import gleam/http +import gleam/http/request +import multipart_form +import multipart_form/field + +pub fn main() { + let test_image = <<>> // Image BitArray ommited for reading convinience + let form = [ + #("description", field.String("A random image found on the web")), + #("file", field.File("image.jpg", "image/jpeg", test_image)), + ] + + let req = + request.new() + |> request.set_scheme(http.Https) + |> request.set_method(http.Post) + |> request.set_path("/index.html") + |> request.set_host("example.com") + |> request.set_header("host", "example.com") + |> multipart_form.to_request(form) + + // Do stuff with your request here +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam test # Run the tests +``` diff --git a/examples/http_interactions/build/packages/multipart_form/gleam.toml b/examples/http_interactions/build/packages/multipart_form/gleam.toml new file mode 100644 index 0000000..b28952d --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/gleam.toml @@ -0,0 +1,12 @@ +name = "multipart_form" +version = "1.1.0" +description = "Small library to generate multipart/form-data requests and parse them" +licences = ["Apache-2.0"] +repository = { type = "github", user = "siesta-cat", repo = "gleam_multipart_form" } + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +gleam_http = ">= 3.7.2 and < 5.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_File.hrl b/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_File.hrl new file mode 100644 index 0000000..6ec14a2 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_File.hrl @@ -0,0 +1,5 @@ +-record(file, { + name :: binary(), + content_type :: binary(), + content :: bitstring() +}). diff --git a/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_StringWithType.hrl b/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_StringWithType.hrl new file mode 100644 index 0000000..3dae483 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/include/multipart_form@field_StringWithType.hrl @@ -0,0 +1 @@ +-record(string_with_type, {content :: binary(), content_type :: binary()}). diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form.app.src b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.app.src new file mode 100644 index 0000000..fa7c974 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.app.src @@ -0,0 +1,11 @@ +{application, multipart_form, [ + {vsn, "1.1.0"}, + {applications, [gleam_http, + gleam_stdlib]}, + {description, "Small library to generate multipart/form-data requests and parse them"}, + {modules, [multipart_form, + multipart_form@@main, + multipart_form@field, + multipart_form@form]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form.erl b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.erl new file mode 100644 index 0000000..f3a822b --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.erl @@ -0,0 +1,47 @@ +-module(multipart_form). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([to_request/2, from_request/1]). + +-file("src/multipart_form.gleam", 6). +-spec to_request( + gleam@http@request:request(any()), + list({binary(), multipart_form@field:form_body()}) +) -> gleam@http@request:request(bitstring()). +to_request(Req, Form) -> + Boundary = <<"gleam_multipart_form"/utf8>>, + _pipe = Req, + _pipe@1 = gleam@http@request:set_header( + _pipe, + <<"Content-Type"/utf8>>, + <<"multipart/form-data; boundary="/utf8, Boundary/binary>> + ), + gleam@http@request:set_body( + _pipe@1, + multipart_form@form:to_bit_array(Form, Boundary) + ). + +-file("src/multipart_form.gleam", 21). +-spec from_request(gleam@http@request:request(bitstring())) -> {ok, + list({binary(), multipart_form@field:form_body()})} | + {error, binary()}. +from_request(Req) -> + gleam@result:'try'( + begin + _pipe = gleam@http@request:get_header(Req, <<"content-type"/utf8>>), + gleam@result:replace_error( + _pipe, + <<"Request does not hace content-type"/utf8>> + ) + end, + fun(Content_type) -> case Content_type of + <<"multipart/form-data; boundary="/utf8, Boundary/binary>> -> + multipart_form@form:from_bit_array( + erlang:element(4, Req), + Boundary + ); + + _ -> + {error, <<"Request is not a multipart form"/utf8>>} + end end + ). diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form.gleam b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.gleam new file mode 100644 index 0000000..9d60fb6 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form.gleam @@ -0,0 +1,34 @@ +import gleam/http/request +import gleam/result +import multipart_form/form + +// Generate a request with a multipart/form-data body from a Form +pub fn to_request( + req req: request.Request(n), + form form: form.Form, +) -> request.Request(BitArray) { + let boundary = "gleam_multipart_form" + + req + |> request.set_header( + "Content-Type", + "multipart/form-data; boundary=" <> boundary, + ) + |> request.set_body(form.to_bit_array(form, boundary)) +} + +// Parse a Form from a request conaining a multipart/form-data body +pub fn from_request( + req req: request.Request(BitArray), +) -> Result(form.Form, String) { + use content_type <- result.try( + request.get_header(req, "content-type") + |> result.replace_error("Request does not hace content-type"), + ) + + case content_type { + "multipart/form-data; boundary=" <> boundary -> + form.from_bit_array(req.body, boundary) + _ -> Error("Request is not a multipart form") + } +} diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form/field.gleam b/examples/http_interactions/build/packages/multipart_form/src/multipart_form/field.gleam new file mode 100644 index 0000000..a3e8239 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form/field.gleam @@ -0,0 +1,42 @@ +import gleam/bit_array + +pub type FormBody { + String(String) + StringWithType(content: String, content_type: String) + File(name: String, content_type: String, content: BitArray) +} + +pub fn to_bit_array( + field_name field: String, + field_body element: FormBody, + boundary boundary: String, +) -> BitArray { + let body = case element { + File(filename, content_type, content) -> << + "Content-Disposition: form-data; name=\"":utf8, + field:utf8, + "\"; filename=\"":utf8, + filename:utf8, + "\"\r\nContent-Type: ":utf8, + content_type:utf8, + "\r\n\r\n":utf8, + content:bits, + >> + String(content) -> << + "Content-Disposition: form-data; name=\"":utf8, + field:utf8, + "\"\r\n\r\n":utf8, + content:utf8, + >> + StringWithType(content, type_) -> << + "Content-Disposition: form-data; name=\"":utf8, + field:utf8, + "\"\r\nContent-Type: ":utf8, + type_:utf8, + "\r\n\r\n":utf8, + content:utf8, + >> + } + + bit_array.concat([<<"--":utf8, boundary:utf8, "\r\n":utf8>>, body, <<"\r\n">>]) +} diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form/form.gleam b/examples/http_interactions/build/packages/multipart_form/src/multipart_form/form.gleam new file mode 100644 index 0000000..9aedded --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form/form.gleam @@ -0,0 +1,104 @@ +import gleam/bit_array +import gleam/http +import gleam/list +import gleam/option +import gleam/result +import multipart_form/field + +// A Form is defined by a list of #(field_name, field_body) +pub type Form = + List(#(String, field.FormBody)) + +// Converts the given form fields into a bitarray multipart body using the given boundary +pub fn to_bit_array(form fields: Form, boundary boundary: String) -> BitArray { + list.map(fields, fn(field) { + let #(name, body) = field + field.to_bit_array(name, body, boundary) + }) + |> list.append([<<"--", boundary:utf8, "--">>]) + |> bit_array.concat +} + +// Parse a Form from a multipart/form-data body with the given boundary +pub fn from_bit_array( + body form: BitArray, + boundary boundary: String, +) -> Result(Form, String) { + parse_field(form, boundary, []) |> result.map(list.reverse) +} + +fn parse_field( + body: BitArray, + boundary: String, + fields: List(#(String, field.FormBody)), +) -> Result(List(#(String, field.FormBody)), String) { + use headers <- result.try( + http.parse_multipart_headers(body, boundary) + |> result.replace_error("Failed to parse Field headers"), + ) + use #(headers, rest) <- result.try(case headers { + http.MoreRequiredForHeaders(_) -> Error("Must provide a full body") + http.MultipartHeaders(headers, rest) -> Ok(#(headers, rest)) + }) + + use header <- result.try( + list.key_find(headers, "content-disposition") + |> result.replace_error( + "Invalid form field, does not contain Content-Disposition", + ), + ) + use header <- result.try( + http.parse_content_disposition(header) + |> result.replace_error( + "Invalid form field, contains an invalid Content-Disposition header", + ), + ) + use field <- result.try( + list.key_find(header.parameters, "name") + |> result.replace_error("Invalid form field, does not contain field name"), + ) + + let filename = + option.from_result(list.key_find(header.parameters, "filename")) + + use body <- result.try( + http.parse_multipart_body(rest, boundary) + |> result.replace_error("Failed to parse field body"), + ) + use #(body, done, rest) <- result.try(case body { + http.MoreRequiredForBody(_, _) -> Error("Must provide a full body") + http.MultipartBody(body, done, rest) -> Ok(#(body, done, rest)) + }) + + use field <- result.try(case filename { + option.None -> { + use content <- result.map( + bit_array.to_string(body) + |> result.replace_error("Invalid utf-8 string in string field"), + ) + case list.key_find(headers, "content-type") { + Ok(content_type) -> #( + field, + field.StringWithType(content:, content_type:), + ) + Error(_) -> #(field, field.String(content)) + } + } + option.Some(filename) -> { + use header <- result.map( + list.key_find(headers, "content-type") + |> result.replace_error( + "Invalid form file field, does not contain Content-Type header", + ), + ) + #(field, field.File(filename, header, body)) + } + }) + + let fields = [field, ..fields] + + case done { + False -> parse_field(rest, boundary, fields) + True -> Ok(fields) + } +} diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form@field.erl b/examples/http_interactions/build/packages/multipart_form/src/multipart_form@field.erl new file mode 100644 index 0000000..ac77803 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form@field.erl @@ -0,0 +1,41 @@ +-module(multipart_form@field). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([to_bit_array/3]). +-export_type([form_body/0]). + +-type form_body() :: {string, binary()} | + {string_with_type, binary(), binary()} | + {file, binary(), binary(), bitstring()}. + +-file("src/multipart_form/field.gleam", 9). +-spec to_bit_array(binary(), form_body(), binary()) -> bitstring(). +to_bit_array(Field, Element, Boundary) -> + Body = case Element of + {file, Filename, Content_type, Content} -> + <<"Content-Disposition: form-data; name=\""/utf8, + Field/binary, + "\"; filename=\""/utf8, + Filename/binary, + "\"\r\nContent-Type: "/utf8, + Content_type/binary, + "\r\n\r\n"/utf8, + Content/bitstring>>; + + {string, Content@1} -> + <<"Content-Disposition: form-data; name=\""/utf8, + Field/binary, + "\"\r\n\r\n"/utf8, + Content@1/binary>>; + + {string_with_type, Content@2, Type_} -> + <<"Content-Disposition: form-data; name=\""/utf8, + Field/binary, + "\"\r\nContent-Type: "/utf8, + Type_/binary, + "\r\n\r\n"/utf8, + Content@2/binary>> + end, + gleam_stdlib:bit_array_concat( + [<<"--"/utf8, Boundary/binary, "\r\n"/utf8>>, Body, <<"\r\n"/utf8>>] + ). diff --git a/examples/http_interactions/build/packages/multipart_form/src/multipart_form@form.erl b/examples/http_interactions/build/packages/multipart_form/src/multipart_form@form.erl new file mode 100644 index 0000000..075d115 --- /dev/null +++ b/examples/http_interactions/build/packages/multipart_form/src/multipart_form@form.erl @@ -0,0 +1,213 @@ +-module(multipart_form@form). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([to_bit_array/2, from_bit_array/2]). + +-file("src/multipart_form/form.gleam", 13). +-spec to_bit_array(list({binary(), multipart_form@field:form_body()}), binary()) -> bitstring(). +to_bit_array(Fields, Boundary) -> + _pipe = gleam@list:map( + Fields, + fun(Field) -> + {Name, Body} = Field, + multipart_form@field:to_bit_array(Name, Body, Boundary) + end + ), + _pipe@1 = lists:append(_pipe, [<<"--"/utf8, Boundary/binary, "--"/utf8>>]), + gleam_stdlib:bit_array_concat(_pipe@1). + +-file("src/multipart_form/form.gleam", 30). +-spec parse_field( + bitstring(), + binary(), + list({binary(), multipart_form@field:form_body()}) +) -> {ok, list({binary(), multipart_form@field:form_body()})} | + {error, binary()}. +parse_field(Body, Boundary, Fields) -> + gleam@result:'try'( + begin + _pipe = gleam@http:parse_multipart_headers(Body, Boundary), + gleam@result:replace_error( + _pipe, + <<"Failed to parse Field headers"/utf8>> + ) + end, + fun(Headers) -> gleam@result:'try'(case Headers of + {more_required_for_headers, _} -> + {error, <<"Must provide a full body"/utf8>>}; + + {multipart_headers, Headers@1, Rest} -> + {ok, {Headers@1, Rest}} + end, fun(_use0) -> + {Headers@2, Rest@1} = _use0, + gleam@result:'try'( + begin + _pipe@1 = gleam@list:key_find( + Headers@2, + <<"content-disposition"/utf8>> + ), + gleam@result:replace_error( + _pipe@1, + <<"Invalid form field, does not contain Content-Disposition"/utf8>> + ) + end, + fun(Header) -> + gleam@result:'try'( + begin + _pipe@2 = gleam@http:parse_content_disposition( + Header + ), + gleam@result:replace_error( + _pipe@2, + <<"Invalid form field, contains an invalid Content-Disposition header"/utf8>> + ) + end, + fun(Header@1) -> + gleam@result:'try'( + begin + _pipe@3 = gleam@list:key_find( + erlang:element(3, Header@1), + <<"name"/utf8>> + ), + gleam@result:replace_error( + _pipe@3, + <<"Invalid form field, does not contain field name"/utf8>> + ) + end, + fun(Field) -> + Filename = gleam@option:from_result( + gleam@list:key_find( + erlang:element(3, Header@1), + <<"filename"/utf8>> + ) + ), + gleam@result:'try'( + begin + _pipe@4 = gleam@http:parse_multipart_body( + Rest@1, + Boundary + ), + gleam@result:replace_error( + _pipe@4, + <<"Failed to parse field body"/utf8>> + ) + end, + fun(Body@1) -> + gleam@result:'try'( + case Body@1 of + {more_required_for_body, + _, + _} -> + {error, + <<"Must provide a full body"/utf8>>}; + + {multipart_body, + Body@2, + Done, + Rest@2} -> + {ok, + {Body@2, + Done, + Rest@2}} + end, + fun(_use0@1) -> + {Body@3, + Done@1, + Rest@3} = _use0@1, + gleam@result:'try'( + case Filename of + none -> + gleam@result:map( + begin + _pipe@5 = gleam@bit_array:to_string( + Body@3 + ), + gleam@result:replace_error( + _pipe@5, + <<"Invalid utf-8 string in string field"/utf8>> + ) + end, + fun( + Content + ) -> + case gleam@list:key_find( + Headers@2, + <<"content-type"/utf8>> + ) of + {ok, + Content_type} -> + {Field, + {string_with_type, + Content, + Content_type}}; + + {error, + _} -> + {Field, + {string, + Content}} + end + end + ); + + {some, + Filename@1} -> + gleam@result:map( + begin + _pipe@6 = gleam@list:key_find( + Headers@2, + <<"content-type"/utf8>> + ), + gleam@result:replace_error( + _pipe@6, + <<"Invalid form file field, does not contain Content-Type header"/utf8>> + ) + end, + fun( + Header@2 + ) -> + {Field, + {file, + Filename@1, + Header@2, + Body@3}} + end + ) + end, + fun(Field@1) -> + Fields@1 = [Field@1 | + Fields], + case Done@1 of + false -> + parse_field( + Rest@3, + Boundary, + Fields@1 + ); + + true -> + {ok, + Fields@1} + end + end + ) + end + ) + end + ) + end + ) + end + ) + end + ) + end) end + ). + +-file("src/multipart_form/form.gleam", 23). +-spec from_bit_array(bitstring(), binary()) -> {ok, + list({binary(), multipart_form@field:form_body()})} | + {error, binary()}. +from_bit_array(Form, Boundary) -> + _pipe = parse_field(Form, Boundary, []), + gleam@result:map(_pipe, fun lists:reverse/1). diff --git a/examples/http_interactions/build/packages/operating_system/LICENSE b/examples/http_interactions/build/packages/operating_system/LICENSE new file mode 100644 index 0000000..f5371d8 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Filip Hoffmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/http_interactions/build/packages/operating_system/README.md b/examples/http_interactions/build/packages/operating_system/README.md new file mode 100644 index 0000000..5f3e94f --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/README.md @@ -0,0 +1,32 @@ +# operating_system + +[![Package Version](https://img.shields.io/hexpm/v/operating_system)](https://hex.pm/packages/operating_system) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/operating_system/) + +Note: In a while, this package might look abandoned. I can assure you - if Gleam v2 did not drop - it works. + +```sh +gleam add operating_system@1 +``` + +```gleam +import operating_system + +pub type OS { + Windows + Linux + MacOs +} + +pub fn main() -> Nil { + let os_name = operating_system.name() + let os = case os_name { + "windows_nt" -> Windows + "linux" -> Linux + "darwin" -> MacOs + _ -> Linux + } +} +``` + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/operating_system/gleam.toml b/examples/http_interactions/build/packages/operating_system/gleam.toml new file mode 100644 index 0000000..e36dde6 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/gleam.toml @@ -0,0 +1,5 @@ +name = "operating_system" +version = "1.0.1" +description = "Gleam operating system name library" +licenses = ["MIT"] +repository = { type = "github", user = "folospior", repo = "operating_system"} diff --git a/examples/http_interactions/build/packages/operating_system/src/operating_system.app.src b/examples/http_interactions/build/packages/operating_system/src/operating_system.app.src new file mode 100644 index 0000000..8ba81e6 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/src/operating_system.app.src @@ -0,0 +1,9 @@ +{application, operating_system, [ + {vsn, "1.0.1"}, + {applications, []}, + {description, "Gleam operating system name library"}, + {modules, [operating_system, + operating_system@@main, + operating_system_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/operating_system/src/operating_system.erl b/examples/http_interactions/build/packages/operating_system/src/operating_system.erl new file mode 100644 index 0000000..df531b8 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/src/operating_system.erl @@ -0,0 +1,23 @@ +-module(operating_system). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). +-define(FILEPATH, "src/operating_system.gleam"). +-export([name/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC( + " This package exposes a function - `name`, which returns the name of the\n" + " current operating system, as reported by `uname`.\n" +). + +-file("src/operating_system.gleam", 7). +?DOC(" Returns the operating system name, as returned by `uname -s`, in lowercase.\n"). +-spec name() -> binary(). +name() -> + operating_system_ffi:name(). diff --git a/examples/http_interactions/build/packages/operating_system/src/operating_system.gleam b/examples/http_interactions/build/packages/operating_system/src/operating_system.gleam new file mode 100644 index 0000000..90c3109 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/src/operating_system.gleam @@ -0,0 +1,7 @@ +//// This package exposes a function - `name`, which returns the name of the +//// current operating system, as reported by `uname`. + +/// Returns the operating system name, as returned by `uname -s`, in lowercase. +@external(erlang, "operating_system_ffi", "name") +@external(javascript, "./operating_system_ffi.mjs", "name") +pub fn name() -> String diff --git a/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.erl b/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.erl new file mode 100644 index 0000000..d922b63 --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.erl @@ -0,0 +1,9 @@ +-module(operating_system_ffi). +-export([name/0]). + +-spec name() -> bitstring(). +name() -> + case element(2, os:type()) of + nt -> <<"windows_nt">>; + Name -> atom_to_binary(Name) + end. diff --git a/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.mjs b/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.mjs new file mode 100644 index 0000000..6df65db --- /dev/null +++ b/examples/http_interactions/build/packages/operating_system/src/operating_system_ffi.mjs @@ -0,0 +1,5 @@ +import os from 'node:os'; + +export function name() { + return os.type().toLowerCase(); +} diff --git a/examples/http_interactions/build/packages/packages.toml b/examples/http_interactions/build/packages/packages.toml new file mode 100644 index 0000000..1047704 --- /dev/null +++ b/examples/http_interactions/build/packages/packages.toml @@ -0,0 +1,32 @@ +[packages] +gleam_http = "4.3.0" +gleam_httpc = "5.0.0" +platform = "1.0.0" +status_code = "1.1.0" +mist = "6.0.3" +gleam_time = "1.8.0" +splitter = "1.2.0" +simplifile = "2.4.0" +bigi = "4.1.1" +stratus = "3.0.0" +filepath = "1.1.2" +gleam_erlang = "1.3.0" +exception = "2.1.0" +gleam_stdlib = "1.0.0" +grom = "5.1.5" +multipart_form = "1.1.0" +logging = "1.5.0" +envoy = "1.2.0" +gleeunit = "1.10.0" +glisten = "9.0.1" +gleam_otp = "1.2.0" +operating_system = "1.0.1" +wisp = "2.2.2" +gleam_json = "3.1.0" +directories = "1.2.0" +houdini = "1.2.1" +gleam_crypto = "1.6.0" +gramps = "6.0.1" +hpack_erl = "0.3.0" +kryptos = "1.4.0" +marceau = "1.3.0" diff --git a/examples/http_interactions/build/packages/platform/LICENSE b/examples/http_interactions/build/packages/platform/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/platform/README.md b/examples/http_interactions/build/packages/platform/README.md new file mode 100644 index 0000000..e1081f1 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/README.md @@ -0,0 +1,21 @@ +# platform +Zero dependency platform detection library for gleam + +[![Package Version](https://img.shields.io/hexpm/v/platform)](https://hex.pm/packages/platform) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/platform/) + +```sh +gleam add platform@1 +``` +```gleam +import gleam/io +import platform + +pub fn main() { + io.debug(platform.runtime()) // Erlang, Node, Deno, Bun, Browser + io.debug(platform.arch()) // X86, X64, Arm, Arm64 + io.debug(platform.os()) // Win32, Linux, Darwin, FreeBsd, OpenBsd +} +``` + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/platform/gleam.toml b/examples/http_interactions/build/packages/platform/gleam.toml new file mode 100644 index 0000000..9c88811 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/gleam.toml @@ -0,0 +1,16 @@ +name = "platform" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +description = "Zero dependency platform detection library for gleam" +licences = ["Apache-2.0"] +repository = { type = "github", user = "ditherwither", repo = "platform" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/platform/src/platform.app.src b/examples/http_interactions/build/packages/platform/src/platform.app.src new file mode 100644 index 0000000..5e6d88c --- /dev/null +++ b/examples/http_interactions/build/packages/platform/src/platform.app.src @@ -0,0 +1,7 @@ +{application, platform, [ + {vsn, "1.0.0"}, + {applications, [gleeunit]}, + {description, "Zero dependency platform detection library for gleam"}, + {modules, [platform]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/platform/src/platform.erl b/examples/http_interactions/build/packages/platform/src/platform.erl new file mode 100644 index 0000000..af2492a --- /dev/null +++ b/examples/http_interactions/build/packages/platform/src/platform.erl @@ -0,0 +1,143 @@ +-module(platform). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([runtime/0, os/0, arch/0]). +-export_type([runtime/0, os/0, arch/0]). + +-type runtime() :: erlang | + node | + bun | + deno | + browser | + {other_runtime, binary()}. + +-type os() :: aix | + darwin | + free_bsd | + linux | + open_bsd | + sun_os | + win32 | + {other_os, binary()}. + +-type arch() :: arm | + arm64 | + x86 | + x64 | + loong64 | + mips | + mips_little_endian | + ppc | + ppc64 | + risc_v64 | + s390 | + s390x | + {other_arch, binary()}. + +-spec runtime() -> runtime(). +runtime() -> + case platform_ffi:runtime() of + <<"erlang"/utf8>> -> + erlang; + + <<"node"/utf8>> -> + node; + + <<"bun"/utf8>> -> + bun; + + <<"deno"/utf8>> -> + deno; + + <<"browser"/utf8>> -> + browser; + + Runtime -> + {other_runtime, Runtime} + end. + +-spec os() -> os(). +os() -> + case platform_ffi:os() of + <<"aix"/utf8>> -> + aix; + + <<"darwin"/utf8>> -> + darwin; + + <<"freebsd"/utf8>> -> + free_bsd; + + <<"linux"/utf8>> -> + linux; + + <<"openbsd"/utf8>> -> + open_bsd; + + <<"sunos"/utf8>> -> + sun_os; + + <<"win32"/utf8>> -> + win32; + + <<"win"/utf8, _/binary>> -> + win32; + + Os -> + {other_os, Os} + end. + +-spec arch() -> arch(). +arch() -> + case platform_ffi:arch() of + <<"arm"/utf8>> -> + arm; + + <<"arm64"/utf8>> -> + arm64; + + <<"aarch64"/utf8>> -> + arm64; + + <<"x86"/utf8>> -> + x86; + + <<"ia32"/utf8>> -> + x86; + + <<"x64"/utf8>> -> + x64; + + <<"x86_64"/utf8>> -> + x64; + + <<"amd64"/utf8>> -> + x64; + + <<"loong64"/utf8>> -> + loong64; + + <<"mips"/utf8>> -> + mips; + + <<"mipsel"/utf8>> -> + mips_little_endian; + + <<"ppc"/utf8>> -> + ppc; + + <<"ppc64"/utf8>> -> + ppc64; + + <<"riscv64"/utf8>> -> + risc_v64; + + <<"s390"/utf8>> -> + s390; + + <<"s390x"/utf8>> -> + s390x; + + Arch -> + {other_arch, Arch} + end. diff --git a/examples/http_interactions/build/packages/platform/src/platform.gleam b/examples/http_interactions/build/packages/platform/src/platform.gleam new file mode 100644 index 0000000..2d10e59 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/src/platform.gleam @@ -0,0 +1,106 @@ +pub type Runtime { + Erlang + Node + Bun + Deno + Browser + OtherRuntime(String) +} + +pub type Os { + Aix + Darwin + FreeBsd + Linux + OpenBsd + SunOs + Win32 + OtherOs(String) +} + +pub type Arch { + Arm + Arm64 + X86 + X64 + Loong64 + Mips + MipsLittleEndian + PPC + PPC64 + RiscV64 + S390 + S390X + OtherArch(String) +} + +@external(erlang, "platform_ffi", "runtime") +@external(javascript, "./platform_ffi.mjs", "runtime") +fn runtime_() -> String + +@external(erlang, "platform_ffi", "os") +@external(javascript, "./platform_ffi.mjs", "os") +fn os_() -> String + +@external(erlang, "platform_ffi", "arch") +@external(javascript, "./platform_ffi.mjs", "arch") +fn arch_() -> String + +/// Returns the runtime this application is running on +/// +/// On the erlang target, it'll always return `Erlang` +/// +/// On the js target, it'll try to detect the js runtime +pub fn runtime() -> Runtime { + case runtime_() { + "erlang" -> Erlang + "node" -> Node + "bun" -> Bun + "deno" -> Deno + "browser" -> Browser + runtime -> OtherRuntime(runtime) + } +} + +/// Returns the host operating system this appication is running on +/// +/// In web browsers, this will always return OtherOs("unknown") +pub fn os() -> Os { + case os_() { + "aix" -> Aix + "darwin" -> Darwin + "freebsd" -> FreeBsd + "linux" -> Linux + "openbsd" -> OpenBsd + "sunos" -> SunOs + "win32" | "win" <> _ -> Win32 + os -> OtherOs(os) + } +} + +/// Returns the CPU architecture of the host system +/// +/// In web browsers, this will always return OtherArch("unknown") +/// +/// On erlang for windows, it'll always return either X86 or X64, even under a beam vm compiled for arm. +/// This is because there is no simple way to get the cpu archictecture on windows, and it currently uses +/// the bitness of the cpu to guess it instead. +/// +/// As of 22nd August 2024, there are no prebuilt binaries for windows for arm, so this shouldn't matter +pub fn arch() -> Arch { + case arch_() { + "arm" -> Arm + "arm64" | "aarch64" -> Arm64 + "x86" | "ia32" -> X86 + "x64" | "x86_64" | "amd64" -> X64 + "loong64" -> Loong64 + "mips" -> Mips + "mipsel" -> MipsLittleEndian + "ppc" -> PPC + "ppc64" -> PPC64 + "riscv64" -> RiscV64 + "s390" -> S390 + "s390x" -> S390X + arch -> OtherArch(arch) + } +} diff --git a/examples/http_interactions/build/packages/platform/src/platform_ffi.erl b/examples/http_interactions/build/packages/platform/src/platform_ffi.erl new file mode 100644 index 0000000..118f588 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/src/platform_ffi.erl @@ -0,0 +1,28 @@ +-module(platform_ffi). +-export([runtime/0, os/0, arch/0]). + +runtime() -> unicode:characters_to_binary("erlang"). + +os() -> + Platform = + case os:type() of + {win32, nt} -> + "win32"; + {unix, Os} -> + erlang:atom_to_list(Os); + {_, _} -> + "unknown" + end, + list_to_binary(Platform). + +arch() -> + case erlang:system_info(os_type) of + {unix, _} -> + [Arch, _] = string:split(erlang:system_info(system_architecture), "-"), + unicode:characters_to_binary(Arch); + {win32, _} -> + case erlang:system_info(wordsize) of + 4 -> <<"ia32">>; + 8 -> <<"x64">> + end + end. diff --git a/examples/http_interactions/build/packages/platform/src/platform_ffi.mjs b/examples/http_interactions/build/packages/platform/src/platform_ffi.mjs new file mode 100644 index 0000000..8c26c35 --- /dev/null +++ b/examples/http_interactions/build/packages/platform/src/platform_ffi.mjs @@ -0,0 +1,52 @@ +"use strict"; + +export function runtime() { + if (globalThis.Deno) { + return "deno"; + } + if (globalThis.Bun) { + return "bun"; + } + if (globalThis.process?.release?.name === "node") { + return "node"; + } + + if (window !== "undefined" && typeof window.document !== "undefined") { + return "browser"; + } + + return "unknown"; +} + +export function os() { + const currentRuntime = runtime(); + + if (currentRuntime === "node" || currentRuntime === "bun") { + return process.platform; + } + if (currentRuntime === "browser") { + // TODO get browser's host os using user agent data + return "unknown"; + } + if (currentRuntime === "deno") { + return Deno.build.os; + } + return "unknown"; +} + +export function arch() { + const currentRuntime = runtime(); + + if (currentRuntime === "node" || currentRuntime === "bun") { + return process.arch; + } + if (currentRuntime === "deno") { + return Deno.build.arch; + } + if (currentRuntime === "browser") { + // TODO get browser's architecture using user agent data + return "unknown"; + } + + return "unknown"; +} diff --git a/examples/http_interactions/build/packages/simplifile/README.md b/examples/http_interactions/build/packages/simplifile/README.md new file mode 100644 index 0000000..c035c26 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/README.md @@ -0,0 +1,56 @@ +# simplifile + +[![Package Version](https://img.shields.io/hexpm/v/simplifile)](https://hex.pm/packages/simplifile) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/simplifile/) + +Simplifile provides synchronous operations for working with files and directories that work for all +non-browser targets (Erlang, Node, Deno, and Bun). + +## When should I use `simplifile`? + +Simplifile makes a simple tradeoff: it confines itself to a subset of purely synchronous file/directory utilities +so that it can have one API for all targets. Erlang and JavaScript have fundamentally different concurrency +models, so bindings to async code will simply be different for different targets. + +You *should* use simplifile if +1. The same code running on JS and Erlang targets is important +2. Sync file operations will not be a major performance bottleneck (dev tooling, configuration, scripting, etc.) +3. You just wanna do some basic file operations and get on with life + +If you think you need a different solutions, these projects may be helpful: +[File streams](https://github.com/richard-viney/file_streams) + +## Upgrading to 2.0 + +Please consult the changelog, but basically: +The deprecated `is_file`, `is_directory` functions have been removed. +The `verify_is_file`, `verify_is_directory`, and `verify_is_symlink` have had their verify_ prefixes removed (now `is_file`, `is_directory`, `is_symlink` respectively). +The `Unknown` variant of `FileError` now has an inner `String`. + +## Example +```gleam +let filepath = "./test/hello.txt" +let assert Ok(_) = "Hello, World" |> write(to: filepath) +let assert Ok(_) = "Goodbye, Mars" |> append(to: filepath) +let assert Ok("Hello, WorldGoodbye, Mars") = read(from: filepath) +let assert Ok(_) = delete(filepath) +let assert Error(_) = read(from: filepath) +``` + +## Installation + +If available on Hex this package can be added to your Gleam project: + +```sh +gleam add simplifile +``` + +and its documentation can be found at . + +## Contributing + +Contributions are welcome! The best way to start for medium to large features is by opening an issue with +the feature suggestion, otherwise feel free to just make a PR! +Code contributions should include tests. Please make sure tests pass on all supported targets +(you can run the test.sh file in the top level of the project to run tests for each target). +Please also include an entry in the Unreleased section of the changelog. diff --git a/examples/http_interactions/build/packages/simplifile/gleam.toml b/examples/http_interactions/build/packages/simplifile/gleam.toml new file mode 100644 index 0000000..92e39e5 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/gleam.toml @@ -0,0 +1,18 @@ +name = "simplifile" +version = "2.4.0" +description = "Basic file operations that work on all targets" + +licences = ["Apache-2.0"] +repository = { type = "github", user = "bcpeinhardt", repo = "simplifile" } +gleam = ">= 1.9.0" +# links = [{ title = "Website", href = "https://gleam.run" }] + +[javascript.deno] +allow_all = true + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" +filepath = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/simplifile/include/simplifile_FileInfo.hrl b/examples/http_interactions/build/packages/simplifile/include/simplifile_FileInfo.hrl new file mode 100644 index 0000000..ca795bd --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/include/simplifile_FileInfo.hrl @@ -0,0 +1,12 @@ +-record(file_info, { + size :: integer(), + mode :: integer(), + nlinks :: integer(), + inode :: integer(), + user_id :: integer(), + group_id :: integer(), + dev :: integer(), + atime_seconds :: integer(), + mtime_seconds :: integer(), + ctime_seconds :: integer() +}). diff --git a/examples/http_interactions/build/packages/simplifile/include/simplifile_FilePermissions.hrl b/examples/http_interactions/build/packages/simplifile/include/simplifile_FilePermissions.hrl new file mode 100644 index 0000000..8933d88 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/include/simplifile_FilePermissions.hrl @@ -0,0 +1,5 @@ +-record(file_permissions, { + user :: gleam@set:set(simplifile:permission()), + group :: gleam@set:set(simplifile:permission()), + other :: gleam@set:set(simplifile:permission()) +}). diff --git a/examples/http_interactions/build/packages/simplifile/include/simplifile_Unknown.hrl b/examples/http_interactions/build/packages/simplifile/include/simplifile_Unknown.hrl new file mode 100644 index 0000000..890c7f6 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/include/simplifile_Unknown.hrl @@ -0,0 +1 @@ +-record(unknown, {inner :: binary()}). diff --git a/examples/http_interactions/build/packages/simplifile/src/simplifile.app.src b/examples/http_interactions/build/packages/simplifile/src/simplifile.app.src new file mode 100644 index 0000000..a02e375 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/src/simplifile.app.src @@ -0,0 +1,10 @@ +{application, simplifile, [ + {vsn, "2.4.0"}, + {applications, [filepath, + gleam_stdlib]}, + {description, "Basic file operations that work on all targets"}, + {modules, [simplifile, + simplifile@@main, + simplifile_erl]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/simplifile/src/simplifile.erl b/examples/http_interactions/build/packages/simplifile/src/simplifile.erl new file mode 100644 index 0000000..9e0596a --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/src/simplifile.erl @@ -0,0 +1,903 @@ +-module(simplifile). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/simplifile.gleam"). +-export([describe_error/1, file_info_permissions_octal/1, file_info_type/1, file_info/1, link_info/1, delete/1, delete_file/1, delete_all/1, read_bits/1, read/1, write_bits/2, write/2, append_bits/2, append/2, is_directory/1, create_directory/1, create_symlink/2, create_link/2, read_directory/1, is_file/1, is_symlink/1, create_file/1, create_directory_all/1, copy_file/2, rename_file/2, rename/2, copy_directory/2, copy/2, rename_directory/2, clear_directory/1, get_files/1, file_permissions_to_octal/1, file_info_permissions/1, set_permissions_octal/2, set_permissions/2, current_directory/0]). +-export_type([file_error/0, file_info/0, file_type/0, permission/0, file_permissions/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type file_error() :: eacces | + eagain | + ebadf | + ebadmsg | + ebusy | + edeadlk | + edeadlock | + edquot | + eexist | + efault | + efbig | + eftype | + eintr | + einval | + eio | + eisdir | + eloop | + emfile | + emlink | + emultihop | + enametoolong | + enfile | + enobufs | + enodev | + enolck | + enolink | + enoent | + enomem | + enospc | + enosr | + enostr | + enosys | + enotblk | + enotdir | + enotsup | + enxio | + eopnotsupp | + eoverflow | + eperm | + epipe | + erange | + erofs | + espipe | + esrch | + estale | + etxtbsy | + exdev | + not_utf8 | + {unknown, binary()}. + +-type file_info() :: {file_info, + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer(), + integer()}. + +-type file_type() :: file | directory | symlink | other. + +-type permission() :: read | write | execute. + +-type file_permissions() :: {file_permissions, + gleam@set:set(permission()), + gleam@set:set(permission()), + gleam@set:set(permission())}. + +-file("src/simplifile.gleam", 124). +?DOC( + " Convert an error into a human-readable description\n" + " ## Example\n" + " ```gleam\n" + " let assert \"Input/output error\" = describe_error(Eio)\n" + " ```\n" +). +-spec describe_error(file_error()) -> binary(). +describe_error(Error) -> + case Error of + eperm -> + <<"Operation not permitted"/utf8>>; + + enoent -> + <<"No such file or directory"/utf8>>; + + esrch -> + <<"No such process"/utf8>>; + + eintr -> + <<"Interrupted system call"/utf8>>; + + eio -> + <<"Input/output error"/utf8>>; + + enxio -> + <<"Device not configured"/utf8>>; + + ebadf -> + <<"Bad file descriptor"/utf8>>; + + edeadlk -> + <<"Resource deadlock avoided"/utf8>>; + + edeadlock -> + <<"Resource deadlock avoided"/utf8>>; + + enomem -> + <<"Cannot allocate memory"/utf8>>; + + eacces -> + <<"Permission denied"/utf8>>; + + efault -> + <<"Bad address"/utf8>>; + + enotblk -> + <<"Block device required"/utf8>>; + + ebusy -> + <<"Resource busy"/utf8>>; + + eexist -> + <<"File exists"/utf8>>; + + exdev -> + <<"Cross-device link"/utf8>>; + + enodev -> + <<"Operation not supported by device"/utf8>>; + + enotdir -> + <<"Not a directory"/utf8>>; + + eisdir -> + <<"Is a directory"/utf8>>; + + einval -> + <<"Invalid argument"/utf8>>; + + enfile -> + <<"Too many open files in system"/utf8>>; + + emfile -> + <<"Too many open files"/utf8>>; + + etxtbsy -> + <<"Text file busy"/utf8>>; + + efbig -> + <<"File too large"/utf8>>; + + enospc -> + <<"No space left on device"/utf8>>; + + espipe -> + <<"Illegal seek"/utf8>>; + + erofs -> + <<"Read-only file system"/utf8>>; + + emlink -> + <<"Too many links"/utf8>>; + + epipe -> + <<"Broken pipe"/utf8>>; + + erange -> + <<"Result too large"/utf8>>; + + eagain -> + <<"Resource temporarily unavailable"/utf8>>; + + enotsup -> + <<"Operation not supported"/utf8>>; + + enobufs -> + <<"No buffer space available"/utf8>>; + + eloop -> + <<"Too many levels of symbolic links"/utf8>>; + + enametoolong -> + <<"File name too long"/utf8>>; + + edquot -> + <<"Disc quota exceeded"/utf8>>; + + estale -> + <<"Stale NFS file handle"/utf8>>; + + enolck -> + <<"No locks available"/utf8>>; + + enosys -> + <<"Function not implemented"/utf8>>; + + eftype -> + <<"Inappropriate file type or format"/utf8>>; + + eoverflow -> + <<"Value too large to be stored in data type"/utf8>>; + + ebadmsg -> + <<"Bad message"/utf8>>; + + emultihop -> + <<"Multihop attempted"/utf8>>; + + enolink -> + <<"Link has been severed"/utf8>>; + + enosr -> + <<"No STREAM resources"/utf8>>; + + enostr -> + <<"Not a STREAM"/utf8>>; + + eopnotsupp -> + <<"Operation not supported on socket"/utf8>>; + + not_utf8 -> + <<"File not UTF-8 encoded"/utf8>>; + + {unknown, Inner} -> + <<"Unknown error: "/utf8, Inner/binary>> + end. + +-file("src/simplifile.gleam", 232). +?DOC( + " Extract the file permissions from a given FileInfo value in their octal representation.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " use info <- result.try(simplifile.file_info(\"src/app.gleam\"))\n" + " simplifile.file_info_permissions_octal(info)\n" + " // --> 0o644\n" + " ```\n" +). +-spec file_info_permissions_octal(file_info()) -> integer(). +file_info_permissions_octal(File_info) -> + erlang:'band'(erlang:element(3, File_info), 8#777). + +-file("src/simplifile.gleam", 261). +?DOC( + " Extract the file type from a given FileInfo value.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " use info <- result.try(simplifile.file_info(\"src/app.gleam\"))\n" + " simplifile.file_info_type(info)\n" + " // --> simplifile.File\n" + " ```\n" +). +-spec file_info_type(file_info()) -> file_type(). +file_info_type(File_info) -> + case erlang:'band'(erlang:element(3, File_info), 8#170000) of + 8#100000 -> + file; + + 8#40000 -> + directory; + + 8#120000 -> + symlink; + + _ -> + other + end. + +-file("src/simplifile.gleam", 281). +?DOC( + " Get information about a file at a given path\n" + "\n" + " When the given `filepath` points to a symlink, this function will follow\n" + " the symlink and return information about the target file.\n" + "\n" + " See `link_info` if you want to get information about a symlink instead.\n" +). +-spec file_info(binary()) -> {ok, file_info()} | {error, file_error()}. +file_info(Filepath) -> + simplifile_erl:file_info(Filepath). + +-file("src/simplifile.gleam", 291). +?DOC( + " Get information about a file at a given path\n" + "\n" + " When the given `filepath` is a symlink, this function will return\n" + " infromation about the symlink itself.\n" + "\n" + " See `file_info` if you want to follow symlinks instead.\n" +). +-spec link_info(binary()) -> {ok, file_info()} | {error, file_error()}. +link_info(Filepath) -> + simplifile_erl:link_info(Filepath). + +-file("src/simplifile.gleam", 335). +?DOC( + " Delete a file or directory at a given path. Performs a recursive\n" + " delete on a directory.\n" + " Throws an error if the path does not exist.\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(Nil) = delete(file_at: \"./delete_me.txt\")\n" + " ```\n" +). +-spec delete(binary()) -> {ok, nil} | {error, file_error()}. +delete(Path) -> + simplifile_erl:delete(Path). + +-file("src/simplifile.gleam", 342). +?DOC( + " Delete a file. \n" + " On Erlang, if you're specifically deleting a single file, this function \n" + " should be more efficient than `delete` because it can pass the raw option.\n" +). +-spec delete_file(binary()) -> {ok, nil} | {error, file_error()}. +delete_file(Path) -> + simplifile_erl:delete_file(Path). + +-file("src/simplifile.gleam", 349). +?DOC( + " Delete all files/directories specified in a list of paths.\n" + " Recursively deletes provided directories.\n" + " Does not return an error if one or more of the provided paths\n" + " do not exist.\n" +). +-spec delete_all(list(binary())) -> {ok, nil} | {error, file_error()}. +delete_all(Paths) -> + case Paths of + [] -> + {ok, nil}; + + [Path | Rest] -> + case simplifile_erl:delete(Path) of + {ok, nil} -> + delete_all(Rest); + + {error, enoent} -> + delete_all(Rest); + + E -> + E + end + end. + +-file("src/simplifile.gleam", 383). +?DOC( + " Read a files contents as a bitstring\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(records) = read_bits(from: \"./users.csv\")\n" + " ```\n" +). +-spec read_bits(binary()) -> {ok, bitstring()} | {error, file_error()}. +read_bits(Filepath) -> + simplifile_erl:read_bits(Filepath). + +-file("src/simplifile.gleam", 299). +?DOC( + " Read a files contents as a string\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(records) = read(from: \"./users.csv\")\n" + " ```\n" +). +-spec read(binary()) -> {ok, binary()} | {error, file_error()}. +read(Filepath) -> + case simplifile_erl:read_bits(Filepath) of + {ok, Bits} -> + case gleam@bit_array:to_string(Bits) of + {ok, Str} -> + {ok, Str}; + + _ -> + {error, not_utf8} + end; + + {error, E} -> + {error, E} + end. + +-file("src/simplifile.gleam", 393). +?DOC( + " Write a bitstring to a file at the given path\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(Nil) = write_bits(to: \"./hello_world.txt\", bits: <<\"Hello, World!\":utf8>>)\n" + " ```\n" +). +-spec write_bits(binary(), bitstring()) -> {ok, nil} | {error, file_error()}. +write_bits(Filepath, Bits) -> + simplifile_erl:write_bits(Filepath, Bits). + +-file("src/simplifile.gleam", 317). +?DOC( + " Write a string to a file at the given path\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(Nil) = write(to: \"./hello_world.txt\", contents: \"Hello, World!\")\n" + " ```\n" +). +-spec write(binary(), binary()) -> {ok, nil} | {error, file_error()}. +write(Filepath, Contents) -> + _pipe = Contents, + _pipe@1 = gleam_stdlib:identity(_pipe), + simplifile_erl:write_bits(Filepath, _pipe@1). + +-file("src/simplifile.gleam", 406). +?DOC( + " Append a bitstring to the contents of a file at the given path\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(Nil) = append_bits(to: \"./needs_more_text.txt\", bits: <<\"more text\":utf8>>)\n" + " ```\n" +). +-spec append_bits(binary(), bitstring()) -> {ok, nil} | {error, file_error()}. +append_bits(Filepath, Bits) -> + simplifile_erl:append_bits(Filepath, Bits). + +-file("src/simplifile.gleam", 367). +?DOC( + " Append a string to the contents of a file at the given path\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(Nil) = append(to: \"./needs_more_text.txt\", contents: \"more text\")\n" + " ```\n" +). +-spec append(binary(), binary()) -> {ok, nil} | {error, file_error()}. +append(Filepath, Contents) -> + _pipe = Contents, + _pipe@1 = gleam_stdlib:identity(_pipe), + simplifile_erl:append_bits(Filepath, _pipe@1). + +-file("src/simplifile.gleam", 420). +?DOC( + " Checks if the provided filepath exists and is a directory.\n" + " Returns an error if it lacks permissions to read the directory.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(True) = is_directory(\"./test\")\n" + " ```\n" +). +-spec is_directory(binary()) -> {ok, boolean()} | {error, file_error()}. +is_directory(Filepath) -> + simplifile_erl:is_directory(Filepath). + +-file("src/simplifile.gleam", 431). +?DOC( + " Create a directory at the provided filepath. Returns an error if\n" + " the directory already exists.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " create_directory(\"./test\")\n" + " ```\n" +). +-spec create_directory(binary()) -> {ok, nil} | {error, file_error()}. +create_directory(Filepath) -> + simplifile_erl:create_directory(Filepath). + +-file("src/simplifile.gleam", 446). +?DOC( + " Create a symbolic link called symlink pointing to target.\n" + " \n" + " ### Footgun Alert \n" + " the target path is relative to *the symlink*,\n" + " not the current working directory. I will likely be updating \n" + " the label on the next major version to reflect that.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " create_symlink(\"../target\", \"./symlink\")\n" + " ```\n" +). +-spec create_symlink(binary(), binary()) -> {ok, nil} | {error, file_error()}. +create_symlink(Target, Symlink) -> + simplifile_erl:create_symlink(Target, Symlink). + +-file("src/simplifile.gleam", 461). +?DOC( + " Create a \"hard link\" called symlink pointing to target.\n" + " This does not have the same relative pathing footgun as \n" + " `create_symlink`.\n" + " \n" + " ## Example\n" + " ```gleam\n" + " create_link(\"../target\", \"./link\")\n" + " ```\n" +). +-spec create_link(binary(), binary()) -> {ok, nil} | {error, file_error()}. +create_link(Target, Link) -> + simplifile_erl:create_link(Target, Link). + +-file("src/simplifile.gleam", 476). +?DOC( + " Lists the contents of a directory.\n" + " The list contains directory and file names, and is not recursive.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(files_and_folders) = read_directory(at: \"./Folder1\")\n" + " ```\n" +). +-spec read_directory(binary()) -> {ok, list(binary())} | {error, file_error()}. +read_directory(Path) -> + simplifile_erl:read_directory(Path). + +-file("src/simplifile.gleam", 488). +?DOC( + " Checks if the file at the provided filepath exists and is a file.\n" + " Returns an Error if it lacks permissions to read the file.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(True) = is_file(\"./test.txt\")\n" + " ```\n" +). +-spec is_file(binary()) -> {ok, boolean()} | {error, file_error()}. +is_file(Filepath) -> + simplifile_erl:is_file(Filepath). + +-file("src/simplifile.gleam", 500). +?DOC( + " Checks if the file at the provided filepath exists and is a symbolic link.\n" + " Returns an Error if it lacks permissions to read the file.\n" + "\n" + " ## Example\n" + " ```gleam\n" + " let assert Ok(True) = is_symlink(\"./symlink\")\n" + " ```\n" +). +-spec is_symlink(binary()) -> {ok, boolean()} | {error, file_error()}. +is_symlink(Filepath) -> + simplifile_erl:is_symlink(Filepath). + +-file("src/simplifile.gleam", 505). +?DOC( + " Creates an empty file at the given filepath. Returns an `Error(Eexist)`\n" + " if the file already exists.\n" +). +-spec create_file(binary()) -> {ok, nil} | {error, file_error()}. +create_file(Filepath) -> + case {begin + _pipe = Filepath, + simplifile_erl:is_file(_pipe) + end, + begin + _pipe@1 = Filepath, + simplifile_erl:is_directory(_pipe@1) + end} of + {{ok, true}, _} -> + {error, eexist}; + + {_, {ok, true}} -> + {error, eexist}; + + {_, _} -> + simplifile_erl:write_bits(Filepath, <<>>) + end. + +-file("src/simplifile.gleam", 516). +?DOC( + " Recursively creates necessary directories for a given directory\n" + " path. Note that if you pass a path that \"looks like\" a file, i.e.\n" + " `./a/b.txt`, a folder named `b.txt` will be created, so be sure\n" + " to pass only the path to the required directory.\n" +). +-spec create_directory_all(binary()) -> {ok, nil} | {error, file_error()}. +create_directory_all(Dirpath) -> + simplifile_erl:create_dir_all(<>). + +-file("src/simplifile.gleam", 546). +?DOC( + " Copy a file at a given path to another path.\n" + " Note: destination should include the filename, not just the directory\n" +). +-spec copy_file(binary(), binary()) -> {ok, nil} | {error, file_error()}. +copy_file(Src, Dest) -> + _pipe = file:copy(Src, Dest), + gleam@result:replace(_pipe, nil). + +-file("src/simplifile.gleam", 560). +?DOC( + " Rename a file at a given path to another path.\n" + " Note: destination should include the filename, not just the directory\n" +). +-spec rename_file(binary(), binary()) -> {ok, nil} | {error, file_error()}. +rename_file(Src, Dest) -> + simplifile_erl:rename_file(Src, Dest). + +-file("src/simplifile.gleam", 565). +?DOC(" Rename a file or directory.\n"). +-spec rename(binary(), binary()) -> {ok, nil} | {error, file_error()}. +rename(Src, Dest) -> + simplifile_erl:rename_file(Src, Dest). + +-file("src/simplifile.gleam", 576). +-spec do_copy_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}. +do_copy_directory(Src, Dest) -> + gleam@result:'try'( + simplifile_erl:read_directory(Src), + fun(Segments) -> + _pipe = Segments, + gleam@list:each( + _pipe, + fun(Segment) -> + Src_path = filepath:join(Src, Segment), + Dest_path = filepath:join(Dest, Segment), + gleam@result:'try'( + simplifile_erl:file_info(Src_path), + fun(Src_info) -> case file_info_type(Src_info) of + file -> + gleam@result:'try'( + simplifile_erl:read_bits(Src_path), + fun(Content) -> _pipe@1 = Content, + simplifile_erl:write_bits( + Dest_path, + _pipe@1 + ) end + ); + + directory -> + gleam@result:'try'( + simplifile_erl:create_directory( + Dest_path + ), + fun(_) -> + do_copy_directory( + Src_path, + Dest_path + ) + end + ); + + symlink -> + {error, + {unknown, + <<"This is an internal bug where the `file_info` is somehow returning info about a simlink. Please file an issue on the simplifile repo."/utf8>>}}; + + other -> + {error, + {unknown, + <<"Unknown file type (not file, directory, or simlink)"/utf8>>}} + end end + ) + end + ), + {ok, nil} + end + ). + +-file("src/simplifile.gleam", 568). +?DOC(" Copy a directory recursively\n"). +-spec copy_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}. +copy_directory(Src, Dest) -> + gleam@result:'try'( + create_directory_all(Dest), + fun(_) -> do_copy_directory(Src, Dest) end + ). + +-file("src/simplifile.gleam", 530). +?DOC( + " Copy a file or a directory to a new path. Copies directories recursively.\n" + " \n" + " ### Performance Note \n" + " This function does work to determine if the src path\n" + " points to a file or a directory. Consider using one of the the dedicated \n" + " functions `copy_file` or `copy_directory` if you already know which one you need.\n" +). +-spec copy(binary(), binary()) -> {ok, nil} | {error, file_error()}. +copy(Src, Dest) -> + gleam@result:'try'( + simplifile_erl:file_info(Src), + fun(Src_info) -> case file_info_type(Src_info) of + file -> + copy_file(Src, Dest); + + directory -> + copy_directory(Src, Dest); + + symlink -> + {error, + {unknown, + <<"This is an internal bug where the `file_info` is somehow returning info about a simlink. Please file an issue on the simplifile repo."/utf8>>}}; + + other -> + {error, + {unknown, + <<"Unknown file type (not file, directory, or simlink)"/utf8>>}} + end end + ). + +-file("src/simplifile.gleam", 612). +?DOC(" Copy a directory recursively and then delete the old one.\n"). +-spec rename_directory(binary(), binary()) -> {ok, nil} | {error, file_error()}. +rename_directory(Src, Dest) -> + gleam@result:'try'( + copy_directory(Src, Dest), + fun(_) -> simplifile_erl:delete(Src) end + ). + +-file("src/simplifile.gleam", 622). +?DOC( + " Clear the contents of a directory, deleting all files and directories within\n" + " but leaving the top level directory in place.\n" +). +-spec clear_directory(binary()) -> {ok, nil} | {error, file_error()}. +clear_directory(Path) -> + gleam@result:'try'( + simplifile_erl:read_directory(Path), + fun(Paths) -> _pipe = Paths, + _pipe@1 = gleam@list:map( + _pipe, + fun(_capture) -> filepath:join(Path, _capture) end + ), + delete_all(_pipe@1) end + ). + +-file("src/simplifile.gleam", 632). +?DOC( + " Returns a list of filepaths for every file in the directory, including nested\n" + " files.\n" +). +-spec get_files(binary()) -> {ok, list(binary())} | {error, file_error()}. +get_files(Directory) -> + gleam@result:'try'( + simplifile_erl:read_directory(Directory), + fun(Contents) -> + gleam@list:try_fold( + Contents, + [], + fun(Acc, Content) -> + Path = filepath:join(Directory, Content), + gleam@result:'try'( + simplifile_erl:file_info(Path), + fun(Info) -> case file_info_type(Info) of + file -> + {ok, [Path | Acc]}; + + directory -> + gleam@result:'try'( + get_files(Path), + fun(Nested_files) -> + {ok, + lists:append(Acc, Nested_files)} + end + ); + + _ -> + {ok, Acc} + end end + ) + end + ) + end + ). + +-file("src/simplifile.gleam", 655). +-spec permission_to_integer(permission()) -> integer(). +permission_to_integer(Permission) -> + case Permission of + read -> + 8#4; + + write -> + 8#2; + + execute -> + 8#1 + end. + +-file("src/simplifile.gleam", 663). +-spec integer_to_permissions(integer()) -> gleam@set:set(permission()). +integer_to_permissions(Integer) -> + case erlang:'band'(Integer, 7) of + 7 -> + gleam@set:from_list([read, write, execute]); + + 6 -> + gleam@set:from_list([read, write]); + + 5 -> + gleam@set:from_list([read, execute]); + + 3 -> + gleam@set:from_list([write, execute]); + + 4 -> + gleam@set:from_list([read]); + + 2 -> + gleam@set:from_list([write]); + + 1 -> + gleam@set:from_list([execute]); + + 0 -> + gleam@set:new(); + + _ -> + erlang:error(#{gleam_error => panic, + message => <<"`panic` expression evaluated."/utf8>>, + file => <>, + module => <<"simplifile"/utf8>>, + function => <<"integer_to_permissions"/utf8>>, + line => 674}) + end. + +-file("src/simplifile.gleam", 687). +-spec file_permissions_to_octal(file_permissions()) -> integer(). +file_permissions_to_octal(Permissions) -> + Make_permission_digit = fun(Permissions@1) -> _pipe = Permissions@1, + _pipe@1 = gleam@set:to_list(_pipe), + _pipe@2 = gleam@list:map(_pipe@1, fun permission_to_integer/1), + gleam@int:sum(_pipe@2) end, + ((Make_permission_digit(erlang:element(2, Permissions)) * 64) + (Make_permission_digit( + erlang:element(3, Permissions) + ) + * 8)) + + Make_permission_digit(erlang:element(4, Permissions)). + +-file("src/simplifile.gleam", 702). +-spec octal_to_file_permissions(integer()) -> file_permissions(). +octal_to_file_permissions(Octal) -> + {file_permissions, + begin + _pipe = Octal, + _pipe@1 = erlang:'bsr'(_pipe, 6), + integer_to_permissions(_pipe@1) + end, + begin + _pipe@2 = Octal, + _pipe@3 = erlang:'bsr'(_pipe@2, 3), + integer_to_permissions(_pipe@3) + end, + begin + _pipe@4 = Octal, + integer_to_permissions(_pipe@4) + end}. + +-file("src/simplifile.gleam", 237). +?DOC(" Extract the `FilePermissions` from a given FileInfo value.\n"). +-spec file_info_permissions(file_info()) -> file_permissions(). +file_info_permissions(File_info) -> + octal_to_file_permissions(file_info_permissions_octal(File_info)). + +-file("src/simplifile.gleam", 738). +?DOC( + " Sets the permissions for a given file using an octal representation\n" + "\n" + " # Example\n" + " ```gleam\n" + " set_permissions_octal(\"./script.sh\", 0o777)\n" + " ```\n" +). +-spec set_permissions_octal(binary(), integer()) -> {ok, nil} | + {error, file_error()}. +set_permissions_octal(Filepath, Permissions) -> + simplifile_erl:set_permissions_octal(Filepath, Permissions). + +-file("src/simplifile.gleam", 723). +?DOC( + " Sets the permissions for a given file\n" + "\n" + " # Example\n" + " ```gleam\n" + " let all = set.from_list([Read, Write, Execute])\n" + " let all = FilePermissions(user: all, group: all, other: all)\n" + " let assert Ok(Nil) = set_permissions(\"./script.sh\", all)\n" + " ```\n" +). +-spec set_permissions(binary(), file_permissions()) -> {ok, nil} | + {error, file_error()}. +set_permissions(Filepath, Permissions) -> + simplifile_erl:set_permissions_octal( + Filepath, + file_permissions_to_octal(Permissions) + ). + +-file("src/simplifile.gleam", 746). +?DOC(" Returns the current working directory\n"). +-spec current_directory() -> {ok, binary()} | {error, file_error()}. +current_directory() -> + _pipe = file:get_cwd(), + gleam@result:map(_pipe, fun gleam_stdlib:utf_codepoint_list_to_string/1). diff --git a/examples/http_interactions/build/packages/simplifile/src/simplifile.gleam b/examples/http_interactions/build/packages/simplifile/src/simplifile.gleam new file mode 100644 index 0000000..5ab3810 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/src/simplifile.gleam @@ -0,0 +1,752 @@ +import filepath +import gleam/bit_array +import gleam/int +import gleam/list +import gleam/result +import gleam/set.{type Set} +import gleam/string + +/// This type represents all of the reasons for why a file system operation could fail. +/// +/// Most of these reasons are POSIX errors, which come from the operating system +/// and start with E. Others have been added to represent other issues that may +/// arise specific to this library. +/// +pub type FileError { + /// Permission denied. + Eacces + /// Resource temporarily unavailable. + Eagain + /// Bad file number + Ebadf + /// Bad message. + Ebadmsg + /// File busy. + Ebusy + /// Resource deadlock avoided. + Edeadlk + /// On most architectures, same as `Edeadlk`. On some architectures, it + /// means "File locking deadlock error." + Edeadlock + /// Disk quota exceeded. + Edquot + /// File already exists. + Eexist + /// Bad address in system call argument. + Efault + /// File too large. + Efbig + /// Inappropriate file type or format. Usually caused by trying to set the + /// "sticky bit" on a regular file (not a directory). + Eftype + /// Interrupted system call. + Eintr + /// Invalid argument. + Einval + /// I/O error. + Eio + /// Illegal operation on a directory. + Eisdir + /// Too many levels of symbolic links. + Eloop + /// Too many open files. + Emfile + /// Too many links. + Emlink + /// Multihop attempted. + Emultihop + /// Filename too long + Enametoolong + /// File table overflow + Enfile + /// No buffer space available. + Enobufs + /// No such device. + Enodev + /// No locks available. + Enolck + /// Link has been severed. + Enolink + /// No such file or directory. + Enoent + /// Not enough memory. + Enomem + /// No space left on device. + Enospc + /// No STREAM resources. + Enosr + /// Not a STREAM. + Enostr + /// Function not implemented. + Enosys + /// Block device required. + Enotblk + /// Not a directory. + Enotdir + /// Operation not supported. + Enotsup + /// No such device or address. + Enxio + /// Operation not supported on socket. + Eopnotsupp + /// Value too large to be stored in data type. + Eoverflow + /// Not owner. + Eperm + /// Broken pipe. + Epipe + /// Result too large. + Erange + /// Read-only file system. + Erofs + /// Invalid seek. + Espipe + /// No such process. + Esrch + /// Stale remote file handle. + Estale + /// Text file busy. + Etxtbsy + /// Cross-domain link. + Exdev + /// File was requested to be read as UTF-8, but is not UTF-8 encoded. + NotUtf8 + /// Any error not accounted for by this type + Unknown(inner: String) +} + +/// Convert an error into a human-readable description +/// ## Example +/// ```gleam +/// let assert "Input/output error" = describe_error(Eio) +/// ``` +/// +pub fn describe_error(error: FileError) -> String { + case error { + Eperm -> "Operation not permitted" + Enoent -> "No such file or directory" + Esrch -> "No such process" + Eintr -> "Interrupted system call" + Eio -> "Input/output error" + Enxio -> "Device not configured" + Ebadf -> "Bad file descriptor" + Edeadlk -> "Resource deadlock avoided" + Edeadlock -> "Resource deadlock avoided" + Enomem -> "Cannot allocate memory" + Eacces -> "Permission denied" + Efault -> "Bad address" + Enotblk -> "Block device required" + Ebusy -> "Resource busy" + Eexist -> "File exists" + Exdev -> "Cross-device link" + Enodev -> "Operation not supported by device" + Enotdir -> "Not a directory" + Eisdir -> "Is a directory" + Einval -> "Invalid argument" + Enfile -> "Too many open files in system" + Emfile -> "Too many open files" + Etxtbsy -> "Text file busy" + Efbig -> "File too large" + Enospc -> "No space left on device" + Espipe -> "Illegal seek" + Erofs -> "Read-only file system" + Emlink -> "Too many links" + Epipe -> "Broken pipe" + Erange -> "Result too large" + Eagain -> "Resource temporarily unavailable" + Enotsup -> "Operation not supported" + Enobufs -> "No buffer space available" + Eloop -> "Too many levels of symbolic links" + Enametoolong -> "File name too long" + Edquot -> "Disc quota exceeded" + Estale -> "Stale NFS file handle" + Enolck -> "No locks available" + Enosys -> "Function not implemented" + Eftype -> "Inappropriate file type or format" + Eoverflow -> "Value too large to be stored in data type" + Ebadmsg -> "Bad message" + Emultihop -> "Multihop attempted" + Enolink -> "Link has been severed" + Enosr -> "No STREAM resources" + Enostr -> "Not a STREAM" + Eopnotsupp -> "Operation not supported on socket" + NotUtf8 -> "File not UTF-8 encoded" + Unknown(inner) -> "Unknown error: " <> inner + } +} + +/// Represents the intersection of information available +/// from erlang's `file:read_file_info` and node's `fs.stat`. +/// For more details on what these things mean, see: +/// [the man page](https://www.man7.org/linux/man-pages/man7/inode.7.html) +pub type FileInfo { + FileInfo( + /// File size in bytes. + size: Int, + /// File mode that indicates the file type and its permissions. + /// + /// This field enocodes file type and permissions in a 16 bit int in + /// the following order + /// - bits 15-12: File Type. e.g RegularFile, Symlink, Directory + /// - bits 11-9 : Special file permissions such as setUserId, setGroupId, and the Sticky-bit + /// - bits 8-6 : User read, write, and execute permisions. + /// - bits 5-3 : Group read, write, and execute permissions. + /// - bits 2-0 : Other read, write, and execute permissions. + /// + /// For example, in Unix and Linux, a mode value of 33188 indicates + /// a regular file and the permissions associated with it + /// (read and write for the owner, and read-only for others, in + /// this case). + mode: Int, + /// Number of hard links that exist for the file. + nlinks: Int, + /// Inode number, which is a unique identifier for the file in the filesystem. + inode: Int, + /// User ID of the file's owner. + user_id: Int, + /// Group ID of the file's group. + group_id: Int, + /// Device ID of the file's major device. + /// TODO: We can actually get a major device and minor device from both + /// node and erlang. The `fs.stat` in node returns a `dev` and `rdev`, + /// so we can use some bitwise operations to get the minor out of `rdev`. + /// Someone who's not me should totally make a PR for that. + dev: Int, + /// The last access time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970). + atime_seconds: Int, + /// The last modification time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970). + mtime_seconds: Int, + /// The last change time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970). + ctime_seconds: Int, + ) +} + +/// Extract the file permissions from a given FileInfo value in their octal representation. +/// +/// ## Example +/// ```gleam +/// use info <- result.try(simplifile.file_info("src/app.gleam")) +/// simplifile.file_info_permissions_octal(info) +/// // --> 0o644 +/// ``` +pub fn file_info_permissions_octal(from file_info: FileInfo) -> Int { + int.bitwise_and(file_info.mode, 0o777) +} + +/// Extract the `FilePermissions` from a given FileInfo value. +pub fn file_info_permissions(from file_info: FileInfo) -> FilePermissions { + octal_to_file_permissions(file_info_permissions_octal(file_info)) +} + +/// An enumeration of different types of files. +pub type FileType { + /// A regular file + File + /// A directory + Directory + /// A symbolic link + Symlink + /// Another special file type present on some systems, lika a socket or device + Other +} + +/// Extract the file type from a given FileInfo value. +/// +/// ## Example +/// ```gleam +/// use info <- result.try(simplifile.file_info("src/app.gleam")) +/// simplifile.file_info_type(info) +/// // --> simplifile.File +/// ``` +pub fn file_info_type(from file_info: FileInfo) -> FileType { + // S_IFMT and related constants; + // see https://www.man7.org/linux/man-pages/man7/inode.7.html + // see https://github.com/nodejs/node/blob/main/typings/internalBinding/constants.d.ts#L147 + case int.bitwise_and(file_info.mode, 0o170000) { + 0o100000 -> File + 0o40000 -> Directory + 0o120000 -> Symlink + _ -> Other + } +} + +/// Get information about a file at a given path +/// +/// When the given `filepath` points to a symlink, this function will follow +/// the symlink and return information about the target file. +/// +/// See `link_info` if you want to get information about a symlink instead. +@external(erlang, "simplifile_erl", "file_info") +@external(javascript, "./simplifile_js.mjs", "fileInfo") +pub fn file_info(filepath: String) -> Result(FileInfo, FileError) + +/// Get information about a file at a given path +/// +/// When the given `filepath` is a symlink, this function will return +/// infromation about the symlink itself. +/// +/// See `file_info` if you want to follow symlinks instead. +@external(erlang, "simplifile_erl", "link_info") +@external(javascript, "./simplifile_js.mjs", "linkInfo") +pub fn link_info(filepath: String) -> Result(FileInfo, FileError) + +/// Read a files contents as a string +/// ## Example +/// ```gleam +/// let assert Ok(records) = read(from: "./users.csv") +/// ``` +/// +pub fn read(from filepath: String) -> Result(String, FileError) { + case read_bits(filepath) { + Ok(bits) -> { + case bit_array.to_string(bits) { + Ok(str) -> Ok(str) + _ -> Error(NotUtf8) + } + } + Error(e) -> Error(e) + } +} + +/// Write a string to a file at the given path +/// ## Example +/// ```gleam +/// let assert Ok(Nil) = write(to: "./hello_world.txt", contents: "Hello, World!") +/// ``` +/// +pub fn write( + to filepath: String, + contents contents: String, +) -> Result(Nil, FileError) { + contents + |> bit_array.from_string + |> write_bits(to: filepath) +} + +/// Delete a file or directory at a given path. Performs a recursive +/// delete on a directory. +/// Throws an error if the path does not exist. +/// ## Example +/// ```gleam +/// let assert Ok(Nil) = delete(file_at: "./delete_me.txt") +/// ``` +@external(erlang, "simplifile_erl", "delete") +@external(javascript, "./simplifile_js.mjs", "delete_") +pub fn delete(file_or_dir_at path: String) -> Result(Nil, FileError) + +/// Delete a file. +/// On Erlang, if you're specifically deleting a single file, this function +/// should be more efficient than `delete` because it can pass the raw option. +@external(erlang, "simplifile_erl", "delete_file") +@external(javascript, "./simplifile_js.mjs", "deleteFile") +pub fn delete_file(at path: String) -> Result(Nil, FileError) + +/// Delete all files/directories specified in a list of paths. +/// Recursively deletes provided directories. +/// Does not return an error if one or more of the provided paths +/// do not exist. +/// +pub fn delete_all(paths paths: List(String)) -> Result(Nil, FileError) { + case paths { + [] -> Ok(Nil) + [path, ..rest] -> { + case delete(path) { + Ok(Nil) | Error(Enoent) -> delete_all(rest) + e -> e + } + } + } +} + +/// Append a string to the contents of a file at the given path +/// ## Example +/// ```gleam +/// let assert Ok(Nil) = append(to: "./needs_more_text.txt", contents: "more text") +/// ``` +/// +pub fn append( + to filepath: String, + contents contents: String, +) -> Result(Nil, FileError) { + contents + |> bit_array.from_string + |> append_bits(to: filepath) +} + +/// Read a files contents as a bitstring +/// ## Example +/// ```gleam +/// let assert Ok(records) = read_bits(from: "./users.csv") +/// ``` +@external(erlang, "simplifile_erl", "read_bits") +@external(javascript, "./simplifile_js.mjs", "readBits") +pub fn read_bits(from filepath: String) -> Result(BitArray, FileError) + +/// Write a bitstring to a file at the given path +/// ## Example +/// ```gleam +/// let assert Ok(Nil) = write_bits(to: "./hello_world.txt", bits: <<"Hello, World!":utf8>>) +/// ``` +/// +@external(erlang, "simplifile_erl", "write_bits") +@external(javascript, "./simplifile_js.mjs", "writeBits") +pub fn write_bits( + to filepath: String, + bits bits: BitArray, +) -> Result(Nil, FileError) + +/// Append a bitstring to the contents of a file at the given path +/// ## Example +/// ```gleam +/// let assert Ok(Nil) = append_bits(to: "./needs_more_text.txt", bits: <<"more text":utf8>>) +/// ``` +/// +@external(erlang, "simplifile_erl", "append_bits") +@external(javascript, "./simplifile_js.mjs", "appendBits") +pub fn append_bits( + to filepath: String, + bits bits: BitArray, +) -> Result(Nil, FileError) + +/// Checks if the provided filepath exists and is a directory. +/// Returns an error if it lacks permissions to read the directory. +/// +/// ## Example +/// ```gleam +/// let assert Ok(True) = is_directory("./test") +/// ``` +@external(erlang, "simplifile_erl", "is_directory") +@external(javascript, "./simplifile_js.mjs", "isDirectory") +pub fn is_directory(filepath: String) -> Result(Bool, FileError) + +/// Create a directory at the provided filepath. Returns an error if +/// the directory already exists. +/// +/// ## Example +/// ```gleam +/// create_directory("./test") +/// ``` +@external(erlang, "simplifile_erl", "create_directory") +@external(javascript, "./simplifile_js.mjs", "createDirectory") +pub fn create_directory(filepath: String) -> Result(Nil, FileError) + +/// Create a symbolic link called symlink pointing to target. +/// +/// ### Footgun Alert +/// the target path is relative to *the symlink*, +/// not the current working directory. I will likely be updating +/// the label on the next major version to reflect that. +/// +/// ## Example +/// ```gleam +/// create_symlink("../target", "./symlink") +/// ``` +@external(erlang, "simplifile_erl", "create_symlink") +@external(javascript, "./simplifile_js.mjs", "createSymlink") +pub fn create_symlink( + to target: String, + from symlink: String, +) -> Result(Nil, FileError) + +/// Create a "hard link" called symlink pointing to target. +/// This does not have the same relative pathing footgun as +/// `create_symlink`. +/// +/// ## Example +/// ```gleam +/// create_link("../target", "./link") +/// ``` +@external(erlang, "simplifile_erl", "create_link") +@external(javascript, "./simplifile_js.mjs", "createLink") +pub fn create_link( + to target: String, + from link: String, +) -> Result(Nil, FileError) + +/// Lists the contents of a directory. +/// The list contains directory and file names, and is not recursive. +/// +/// ## Example +/// ```gleam +/// let assert Ok(files_and_folders) = read_directory(at: "./Folder1") +/// ``` +/// +@external(erlang, "simplifile_erl", "read_directory") +@external(javascript, "./simplifile_js.mjs", "readDirectory") +pub fn read_directory(at path: String) -> Result(List(String), FileError) + +/// Checks if the file at the provided filepath exists and is a file. +/// Returns an Error if it lacks permissions to read the file. +/// +/// ## Example +/// ```gleam +/// let assert Ok(True) = is_file("./test.txt") +/// ``` +/// +@external(erlang, "simplifile_erl", "is_file") +@external(javascript, "./simplifile_js.mjs", "isFile") +pub fn is_file(filepath: String) -> Result(Bool, FileError) + +/// Checks if the file at the provided filepath exists and is a symbolic link. +/// Returns an Error if it lacks permissions to read the file. +/// +/// ## Example +/// ```gleam +/// let assert Ok(True) = is_symlink("./symlink") +/// ``` +/// +@external(erlang, "simplifile_erl", "is_symlink") +@external(javascript, "./simplifile_js.mjs", "isSymlink") +pub fn is_symlink(filepath: String) -> Result(Bool, FileError) + +/// Creates an empty file at the given filepath. Returns an `Error(Eexist)` +/// if the file already exists. +/// +pub fn create_file(at filepath: String) -> Result(Nil, FileError) { + case filepath |> is_file, filepath |> is_directory { + Ok(True), _ | _, Ok(True) -> Error(Eexist) + _, _ -> write_bits(<<>>, to: filepath) + } +} + +/// Recursively creates necessary directories for a given directory +/// path. Note that if you pass a path that "looks like" a file, i.e. +/// `./a/b.txt`, a folder named `b.txt` will be created, so be sure +/// to pass only the path to the required directory. +pub fn create_directory_all(dirpath: String) -> Result(Nil, FileError) { + do_create_dir_all(dirpath <> "/") +} + +@external(erlang, "simplifile_erl", "create_dir_all") +@external(javascript, "./simplifile_js.mjs", "createDirAll") +fn do_create_dir_all(dirpath: String) -> Result(Nil, FileError) + +/// Copy a file or a directory to a new path. Copies directories recursively. +/// +/// ### Performance Note +/// This function does work to determine if the src path +/// points to a file or a directory. Consider using one of the the dedicated +/// functions `copy_file` or `copy_directory` if you already know which one you need. +pub fn copy(src src: String, dest dest: String) -> Result(Nil, FileError) { + use src_info <- result.try(file_info(src)) + case file_info_type(src_info) { + File -> copy_file(src, dest) + Directory -> copy_directory(src, dest) + Symlink -> + Error(Unknown( + "This is an internal bug where the `file_info` is somehow returning info about a simlink. Please file an issue on the simplifile repo.", + )) + Other -> + Error(Unknown("Unknown file type (not file, directory, or simlink)")) + } +} + +/// Copy a file at a given path to another path. +/// Note: destination should include the filename, not just the directory +pub fn copy_file(at src: String, to dest: String) -> Result(Nil, FileError) { + do_copy_file(src, dest) + |> result.replace(Nil) +} + +@external(erlang, "file", "copy") +@external(javascript, "./simplifile_js.mjs", "copyFile") +fn do_copy_file(src: String, dest: String) -> Result(Int, FileError) + +/// Rename a file at a given path to another path. +/// Note: destination should include the filename, not just the directory +@deprecated("This function can move a file or a directory, so it's being renamed `rename`.") +@external(erlang, "simplifile_erl", "rename_file") +@external(javascript, "./simplifile_js.mjs", "renameFile") +pub fn rename_file(at src: String, to dest: String) -> Result(Nil, FileError) + +/// Rename a file or directory. +@external(erlang, "simplifile_erl", "rename_file") +@external(javascript, "./simplifile_js.mjs", "renameFile") +pub fn rename(at src: String, to dest: String) -> Result(Nil, FileError) + +/// Copy a directory recursively +pub fn copy_directory(at src: String, to dest: String) -> Result(Nil, FileError) { + // Erlang does not provide a built in `copy_dir` function, + // and Deno doesn't support Node's `fs.cpSync`, so we'll just roll + // our own for now. + use _ <- result.try(create_directory_all(dest)) + do_copy_directory(src, dest) +} + +fn do_copy_directory(src: String, dest: String) -> Result(Nil, FileError) { + // Iterate over the segments of the file + use segments <- result.try(read_directory(src)) + segments + |> list.each(fn(segment) { + let src_path = filepath.join(src, segment) + let dest_path = filepath.join(dest, segment) + + use src_info <- result.try(file_info(src_path)) + case file_info_type(src_info) { + File -> { + // For a file, create the file in the new directory + use content <- result.try(read_bits(src_path)) + content + |> write_bits(to: dest_path) + } + Directory -> { + // Create the target directory and recurse + use _ <- result.try(create_directory(dest_path)) + do_copy_directory(src_path, dest_path) + } + // Theoretically this shouldn't happen, as the file info function + // will follow a simlink. + Symlink -> + Error(Unknown( + "This is an internal bug where the `file_info` is somehow returning info about a simlink. Please file an issue on the simplifile repo.", + )) + Other -> + Error(Unknown("Unknown file type (not file, directory, or simlink)")) + } + }) + Ok(Nil) +} + +/// Copy a directory recursively and then delete the old one. +@deprecated("Use the `rename` function, which can rename a file or a directory.") +pub fn rename_directory( + at src: String, + to dest: String, +) -> Result(Nil, FileError) { + use _ <- result.try(copy_directory(src, dest)) + delete(src) +} + +/// Clear the contents of a directory, deleting all files and directories within +/// but leaving the top level directory in place. +pub fn clear_directory(at path: String) -> Result(Nil, FileError) { + use paths <- result.try(read_directory(path)) + paths + |> list.map(filepath.join(path, _)) + |> delete_all +} + +/// Returns a list of filepaths for every file in the directory, including nested +/// files. +/// +pub fn get_files(in directory: String) -> Result(List(String), FileError) { + use contents <- result.try(read_directory(directory)) + use acc, content <- list.try_fold(over: contents, from: []) + let path = filepath.join(directory, content) + use info <- result.try(file_info(path)) + + case file_info_type(info) { + File -> Ok([path, ..acc]) + Directory -> { + use nested_files <- result.try(get_files(path)) + Ok(list.append(acc, nested_files)) + } + _ -> Ok(acc) + } +} + +/// Represents a file permission +pub type Permission { + Read + Write + Execute +} + +fn permission_to_integer(permission: Permission) -> Int { + case permission { + Read -> 0o4 + Write -> 0o2 + Execute -> 0o1 + } +} + +fn integer_to_permissions(integer: Int) -> Set(Permission) { + case int.bitwise_and(integer, 7) { + 7 -> set.from_list([Read, Write, Execute]) + 6 -> set.from_list([Read, Write]) + 5 -> set.from_list([Read, Execute]) + 3 -> set.from_list([Write, Execute]) + 4 -> set.from_list([Read]) + 2 -> set.from_list([Write]) + 1 -> set.from_list([Execute]) + 0 -> set.new() + // since we bitwise_and, these are all possible values. + _ -> panic + } +} + +/// Represents a set of file permissions for a given file +pub type FilePermissions { + FilePermissions( + user: Set(Permission), + group: Set(Permission), + other: Set(Permission), + ) +} + +pub fn file_permissions_to_octal(permissions: FilePermissions) -> Int { + let make_permission_digit = fn(permissions: Set(Permission)) { + permissions + |> set.to_list + |> list.map(permission_to_integer) + |> int.sum + } + + make_permission_digit(permissions.user) + * 64 + + make_permission_digit(permissions.group) + * 8 + + make_permission_digit(permissions.other) +} + +fn octal_to_file_permissions(octal: Int) -> FilePermissions { + FilePermissions( + user: octal + |> int.bitwise_shift_right(6) + |> integer_to_permissions, + group: octal + |> int.bitwise_shift_right(3) + |> integer_to_permissions, + other: octal + |> integer_to_permissions, + ) +} + +/// Sets the permissions for a given file +/// +/// # Example +/// ```gleam +/// let all = set.from_list([Read, Write, Execute]) +/// let all = FilePermissions(user: all, group: all, other: all) +/// let assert Ok(Nil) = set_permissions("./script.sh", all) +/// ``` +pub fn set_permissions( + for_file_at filepath: String, + to permissions: FilePermissions, +) -> Result(Nil, FileError) { + set_permissions_octal(filepath, file_permissions_to_octal(permissions)) +} + +/// Sets the permissions for a given file using an octal representation +/// +/// # Example +/// ```gleam +/// set_permissions_octal("./script.sh", 0o777) +/// ``` +@external(erlang, "simplifile_erl", "set_permissions_octal") +@external(javascript, "./simplifile_js.mjs", "setPermissionsOctal") +pub fn set_permissions_octal( + for_file_at filepath: String, + to permissions: Int, +) -> Result(Nil, FileError) + +/// Returns the current working directory +/// +@external(javascript, "./simplifile_js.mjs", "currentDirectory") +pub fn current_directory() -> Result(String, FileError) { + erl_do_current_directory() + |> result.map(string.from_utf_codepoints) +} + +@external(erlang, "file", "get_cwd") +fn erl_do_current_directory() -> Result(List(UtfCodepoint), FileError) diff --git a/examples/http_interactions/build/packages/simplifile/src/simplifile_erl.erl b/examples/http_interactions/build/packages/simplifile/src/simplifile_erl.erl new file mode 100644 index 0000000..f997758 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/src/simplifile_erl.erl @@ -0,0 +1,244 @@ +%%% -------------------------------------------------- +%%% @author Benjamin Peinhardt +%%% @doc Erlang ffi glue code for the simplifile Gleam package. +%%% @end +%%% -------------------------------------------------- + +-module(simplifile_erl). + +-compile({no_auto_import,[link/2]}). + +%% API +-export([ + append_bits/2, + create_directory/1, + create_dir_all/1, + delete_file/1, + create_link/2, + create_symlink/2, + delete/1, + delete_directory/1, + file_info/1, + link_info/1, + is_directory/1, + is_file/1, + is_symlink/1, + read_bits/1, + read_directory/1, + rename_file/2, + set_permissions_octal/2, + write_bits/2 +]). + +-include_lib("kernel/include/file.hrl"). + +%% A macro for checking whether the error returned is one of the atoms for a posixe error. +-define(is_posix_error(Error), + Error =:= eacces + orelse Error =:= eagain + orelse Error =:= ebadf + orelse Error =:= ebadmsg + orelse Error =:= ebusy + orelse Error =:= edeadlk + orelse Error =:= edeadlock + orelse Error =:= edquot + orelse Error =:= eexist + orelse Error =:= efault + orelse Error =:= efbig + orelse Error =:= eftype + orelse Error =:= eintr + orelse Error =:= einval + orelse Error =:= eio + orelse Error =:= eisdir + orelse Error =:= eloop + orelse Error =:= emfile + orelse Error =:= emlink + orelse Error =:= emultihop + orelse Error =:= enametoolong + orelse Error =:= enfile + orelse Error =:= enobufs + orelse Error =:= enodev + orelse Error =:= enolck + orelse Error =:= enolink + orelse Error =:= enoent + orelse Error =:= enomem + orelse Error =:= enospc + orelse Error =:= enosr + orelse Error =:= enostr + orelse Error =:= enosys + orelse Error =:= enotblk + orelse Error =:= enotdir + orelse Error =:= enotsup + orelse Error =:= enxio + orelse Error =:= eopnotsupp + orelse Error =:= eoverflow + orelse Error =:= eperm + orelse Error =:= epipe + orelse Error =:= erange + orelse Error =:= erofs + orelse Error =:= espipe + orelse Error =:= esrch + orelse Error =:= estale + orelse Error =:= etxtbsy + orelse Error =:= exdev). + +%% Only return the error if it's a posix error. Also converts ok to {ok, nil}, +%% as gleam doesn't have atoms. +posix_result(Result) -> + case Result of + ok -> + {ok, nil}; + {ok, Value} -> + {ok, Value}; + {error, Reason} when ?is_posix_error(Reason) -> + {error, Reason}; + {error, Reason} -> + {error, {unknown, string:uppercase(atom_to_binary(Reason))}} + end. + +%% Read the binary contents of a file +read_bits(Filename) -> + posix_result(file:read_file(Filename)). + +%% Write bytes to a file +write_bits(Filename, Contents) -> + case bit_size(Contents) rem 8 of + 0 -> posix_result(file:write_file(Filename, Contents, [raw])); + _ -> {error, einval} + end. + +%% Append bytes to a file +append_bits(Filename, Contents) -> + case bit_size(Contents) rem 8 of + 0 -> posix_result(file:write_file(Filename, Contents, [append, raw])); + _ -> {error, einval} + end. + +%% Delete the file at the given path +delete_file(Filename) -> + posix_result(file:delete(Filename, [raw])). + +%% Create a directory at the given path. Missing parent directories are not created. +create_directory(Dir) -> + posix_result(file:make_dir(Dir)). + +%% Create a symbolic link New to the file or directory Existing (does not need to exist). +create_symlink(Existing, New) -> + posix_result(file:make_symlink(Existing, New)). + +%% Create a "hard link" New to the file or directory Existing. +create_link(Existing, New) -> + posix_result(file:make_link(Existing, New)). + +%% List the contents of a directory +read_directory(Dir) -> + case file:list_dir(Dir) of + {ok, Filenames} -> + {ok, [unicode:characters_to_binary(Filename) || Filename <- Filenames]}; + {error, Reason} when ?is_posix_error(Reason) -> + {error, Reason} + end. + +%% Delete a directory at the given path. Directory must be empty. +delete_directory(Dir) -> + posix_result(file:del_dir(Dir)). + +%% Deletes a file/directory and everything in it. +delete(Dir) -> + posix_result(file:del_dir_r(Dir)). + +%% Creates the entire path for a given directory to exist +create_dir_all(Filename) -> + posix_result(filelib:ensure_path(Filename)). + +%% Move file from one path to another +rename_file(Source, Destination) -> + posix_result(file:rename(Source, Destination)). + +%% Set the permissions for the given file. +set_permissions_octal(Filename, Permissions) -> + posix_result(file:change_mode(Filename, Permissions)). + +is_directory(Path) -> + case file:read_file_info(Path) of + {ok, FileInfo} -> + case FileInfo#file_info.type of + directory -> + {ok, true}; + _ -> + {ok, false} + end; + {error, enoent} -> + {ok, false}; + {error, Reason} -> + posix_result({error, Reason}) + end. + +is_file(Path) -> + case file:read_file_info(Path) of + {ok, FileInfo} -> + case FileInfo#file_info.type of + regular -> + {ok, true}; + _ -> + {ok, false} + end; + {error, enoent} -> + {ok, false}; + {error, Reason} -> + posix_result({error, Reason}) + end. + +is_symlink(Path) -> + case file:read_link_info(Path) of + {ok, FileInfo} -> + case FileInfo#file_info.type of + symlink -> + {ok, true}; + _ -> + {ok, false} + end; + {error, enoent} -> + {ok, false}; + {error, Reason} -> + posix_result({error, Reason}) + end. + +file_info_result(Result) -> + case Result of + {ok, + {file_info, + Size, + _Type, + _Access, + Atime, + Mtime, + Ctime, + Mode, + Links, + MajorDevice, + _MinorDevice, + Inode, + Uid, + Gid}} -> + {ok, + {file_info, + Size, + Mode, + Links, + Inode, + Uid, + Gid, + MajorDevice, + Atime, + Mtime, + Ctime}}; + {error, Reason} when ?is_posix_error(Reason) -> + Result + end. + +file_info(Filename) -> + file_info_result(file:read_file_info(Filename, [{time, posix}])). + +link_info(Filename) -> + file_info_result(file:read_link_info(Filename, [{time, posix}])). diff --git a/examples/http_interactions/build/packages/simplifile/src/simplifile_js.mjs b/examples/http_interactions/build/packages/simplifile/src/simplifile_js.mjs new file mode 100644 index 0000000..6b82923 --- /dev/null +++ b/examples/http_interactions/build/packages/simplifile/src/simplifile_js.mjs @@ -0,0 +1,412 @@ +// @ts-check + +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { BitArray, Ok, Error as GError, toList } from "./gleam.mjs"; +import * as $simplifile from "./simplifile.mjs"; + +/** + * Read the contents of a file as a BitArray + * + * @param {string} filepath + * @returns {Ok | GError} A Result with the BitArray of the file contents + */ +export function readBits(filepath) { + return gleamResult(() => { + const contents = fs.readFileSync(path.normalize(filepath)); + return new BitArray(new Uint8Array(contents)); + }); +} + +/** + * Write the given BitArray to a file + * + * @param {string} filepath + * @param {BitArray} contents + * @returns {Ok | GError} + */ +export function writeBits(filepath, contents) { + return gleamResult(() => + fs.writeFileSync(path.normalize(filepath), toUint8Array(contents)) + ); +} + +/** + * Append the given BitArray to a file + * + * @param {string} filepath + * @param {BitArray} contents + * @returns {Ok | GError} + */ +export function appendBits(filepath, contents) { + return gleamResult(() => + fs.appendFileSync(path.normalize(filepath), toUint8Array(contents)) + ); +} + +/** + * Returns a BitArray as an aligned Uint8Array. Avoids a copy when possible. + * + * @param {BitArray} contents + * @returns Uint8Array + */ +function toUint8Array(contents) { + if (contents.bitSize % 8 !== 0) { + throw new GError(new $simplifile.Einval()); + } + + let buffer = contents.rawBuffer; + if (contents.bitOffset !== 0) { + buffer = new Uint8Array(contents.byteSize); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = contents.byteAt(i); + } + } + + return buffer; +} + +/** + * Check whether a file exists at the given path + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function isFile(filepath) { + try { + return new Ok(fs.statSync(path.normalize(filepath)).isFile()); + } catch (e) { + if (e.code === "ENOENT") { + return new Ok(false); + } else { + return new GError(cast_error(e.code)); + } + } +} + +/** + * Check whether a symbolic link exists at the given path + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function isSymlink(filepath) { + try { + return new Ok(fs.lstatSync(path.normalize(filepath)).isSymbolicLink()); + } catch (e) { + if (e.code === "ENOENT") { + return new Ok(false); + } else { + return new GError(cast_error(e.code)); + } + } +} + +/** + * Check whether a directory exists at the given path + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function isDirectory(filepath) { + try { + return new Ok(fs.statSync(path.normalize(filepath)).isDirectory()); + } catch (e) { + if (e.code === "ENOENT") { + return new Ok(false); + } else { + return new GError(cast_error(e.code)); + } + } +} + +/** + * Create the symbolic link called path pointing to target + * + * @param {string} target + * @param {string} path + * @returns {Ok | GError} + */ +export function createSymlink(target, path) { + return gleamResult(() => fs.symlinkSync(target, path)); +} + +/** + * Create the "hard link" called path pointing to the target + * + * @param {string} target + * @param {string} path + * returns {ok | GError} + */ +export function createLink(target, path) { + return gleamResult(() => fs.linkSync(target, path)); +} + +/** + * Create a directory at the given filepath + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function createDirectory(filepath) { + return gleamResult(() => fs.mkdirSync(path.normalize(filepath))); +} + +/** + * Recursively create a directory structure from the given path. + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function createDirAll(filepath) { + return gleamResult(() => { + fs.mkdirSync(path.normalize(filepath), { recursive: true }); + }); +} + +/** + * Recursively delete a directory or delete a file at the given path. + * + * @param {string} fileOrDirPath + * @returns {Ok | GError} + */ +export function delete_(fileOrDirPath) { + return gleamResult(() => { + const isDir = isDirectory(fileOrDirPath); + if (isDir instanceof Ok && isDir[0] === true) { + fs.rmSync(path.normalize(fileOrDirPath), { recursive: true }); + } else { + fs.unlinkSync(path.normalize(fileOrDirPath)); + } + }); +} + +/** + * Specifically delete a single file. + * + * @param {string} filePath + * @returns {Ok | GError} + */ +export function deleteFile(filePath) { + return gleamResult(() => { + fs.unlinkSync(path.normalize(filePath)); + }) +} + +/** + * List the contents of a directory. + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function readDirectory(filepath) { + return gleamResult(() => toList(fs.readdirSync(path.normalize(filepath)))); +} + +/** + * Copy a file to a new path. + * + * @param {string} srcpath + * @param {string} destpath + * @returns {Ok | GError} + */ +export function copyFile(srcpath, destpath) { + return gleamResult(() => + fs.copyFileSync(path.normalize(srcpath), path.normalize(destpath)), + ); +} + +/** + * Move a file to the new path. + * + * @param {string} srcpath + * @param {string} destpath + * @returns {Ok | GError} + */ +export function renameFile(srcpath, destpath) { + return gleamResult(() => + fs.renameSync(path.normalize(srcpath), path.normalize(destpath)), + ); +} + +/** + * Set the file permissions. Octal number should be in base 8. + * + * @param {string} filepath + * @param {number} octalNumber + * @returns {Ok | GError} + */ +export function setPermissionsOctal(filepath, octalNumber) { + return gleamResult(() => fs.chmodSync(path.normalize(filepath), octalNumber)); +} + +/** + * Return the current directory. + * + * @returns {Ok | GError} The current directory + */ +export function currentDirectory() { + return gleamResult(() => process.cwd()); +} + +/** + * + * @param {string} filepath + * @returns {Ok | GError} + */ +export function fileInfo(filepath) { + return gleamResult(() => { + const stat = fs.statSync(path.normalize(filepath)); + return new FileInfo(stat); + }); +} + +/** + * @param {string} filepath + * @returns {Ok | GError} + */ +export function linkInfo(filepath) { + return gleamResult(() => { + const stat = fs.lstatSync(path.normalize(filepath)); + return new FileInfo(stat); + }); +} + +class FileInfo { + /** + * @param {fs.Stats} stat + */ + constructor(stat) { + this.size = stat.size; + this.mode = stat.mode; + this.nlinks = stat.nlink; + this.inode = stat.ino; + this.user_id = stat.uid; + this.group_id = stat.gid; + this.dev = stat.dev; + this.atime_seconds = Math.floor(stat.atimeMs / 1000); + this.mtime_seconds = Math.floor(stat.mtimeMs / 1000); + this.ctime_seconds = Math.floor(stat.ctimeMs / 1000); + } +} + +/** + * Perform some operation and return a Gleam `Result(a, String)` + * where `a` is the type returned by the operation and the `String` + * is the error code. + * + * @param {function():any} op + * @returns {Ok | GError} + */ +function gleamResult(op) { + try { + const val = op(); + return new Ok(val); + } catch (e) { + return new GError(cast_error(e.code)); + } +} + +function cast_error(error_code) { + switch (error_code) { + case "EACCES": + return new $simplifile.Eacces(); + case "EAGAIN": + return new $simplifile.Eagain(); + case "EBADF": + return new $simplifile.Ebadf(); + case "EBADMSG": + return new $simplifile.Ebadmsg(); + case "EBUSY": + return new $simplifile.Ebusy(); + case "EDEADLK": + return new $simplifile.Edeadlk(); + case "EDEADLOCK": + return new $simplifile.Edeadlock(); + case "EDQUOT": + return new $simplifile.Edquot(); + case "EEXIST": + return new $simplifile.Eexist(); + case "EFAULT": + return new $simplifile.Efault(); + case "EFBIG": + return new $simplifile.Efbig(); + case "EFTYPE": + return new $simplifile.Eftype(); + case "EINTR": + return new $simplifile.Eintr(); + case "EINVAL": + return new $simplifile.Einval(); + case "EIO": + return new $simplifile.Eio(); + case "EISDIR": + return new $simplifile.Eisdir(); + case "ELOOP": + return new $simplifile.Eloop(); + case "EMFILE": + return new $simplifile.Emfile(); + case "EMLINK": + return new $simplifile.Emlink(); + case "EMULTIHOP": + return new $simplifile.Emultihop(); + case "ENAMETOOLONG": + return new $simplifile.Enametoolong(); + case "ENFILE": + return new $simplifile.Enfile(); + case "ENOBUFS": + return new $simplifile.Enobufs(); + case "ENODEV": + return new $simplifile.Enodev(); + case "ENOLCK": + return new $simplifile.Enolck(); + case "ENOLINK": + return new $simplifile.Enolink(); + case "ENOENT": + return new $simplifile.Enoent(); + case "ENOMEM": + return new $simplifile.Enomem(); + case "ENOSPC": + return new $simplifile.Enospc(); + case "ENOSR": + return new $simplifile.Enosr(); + case "ENOSTR": + return new $simplifile.Enostr(); + case "ENOSYS": + return new $simplifile.Enosys(); + case "ENOBLK": + return new $simplifile.Enotblk(); + case "ENOTDIR": + return new $simplifile.Enotdir(); + case "ENOTSUP": + return new $simplifile.Enotsup(); + case "ENXIO": + return new $simplifile.Enxio(); + case "EOPNOTSUPP": + return new $simplifile.Eopnotsupp(); + case "EOVERFLOW": + return new $simplifile.Eoverflow(); + case "EPERM": + return new $simplifile.Eperm(); + case "EPIPE": + return new $simplifile.Epipe(); + case "ERANGE": + return new $simplifile.Erange(); + case "EROFS": + return new $simplifile.Erofs(); + case "ESPIPE": + return new $simplifile.Espipe(); + case "ESRCH": + return new $simplifile.Esrch(); + case "ESTALE": + return new $simplifile.Estale(); + case "ETXTBSY": + return new $simplifile.Etxtbsy(); + case "EXDEV": + return new $simplifile.Exdev(); + case "NOTUTF8": + return new $simplifile.NotUtf8(); + default: + return new $simplifile.Unknown(error_code); + } +} diff --git a/examples/http_interactions/build/packages/splitter/README.md b/examples/http_interactions/build/packages/splitter/README.md new file mode 100644 index 0000000..7ddfa2e --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/README.md @@ -0,0 +1,25 @@ +# splitter + +Efficiently slice prefixes from strings. Good for parsers! + +[![Package Version](https://img.shields.io/hexpm/v/splitter)](https://hex.pm/packages/splitter) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/splitter/) + +```sh +gleam add splitter@1 +``` +```gleam +import splitter + +pub fn main() -> Nil { + let line_ends = splitter.new(["\n", "\r\n"]) + + splitter.split(line_ends, "1. Bread\n2. Milk\n3. Eggs\n") + // -> #("1. Bread", "\n", "2. Milk\n3. Eggs\n") + + splitter.split(line_ends, "No end of line here!") + // -> #("No end of line here!", "", "") +} +``` + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/splitter/gleam.toml b/examples/http_interactions/build/packages/splitter/gleam.toml new file mode 100644 index 0000000..58bd071 --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/gleam.toml @@ -0,0 +1,14 @@ +name = "splitter" +version = "1.2.0" +description = "Efficiently slice prefixes from strings. Good for parsers!" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "splitter" } +links = [ + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/splitter/src/splitter.app.src b/examples/http_interactions/build/packages/splitter/src/splitter.app.src new file mode 100644 index 0000000..57cc290 --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/src/splitter.app.src @@ -0,0 +1,9 @@ +{application, splitter, [ + {vsn, "1.2.0"}, + {applications, [gleam_stdlib]}, + {description, "Efficiently slice prefixes from strings. Good for parsers!"}, + {modules, [splitter, + splitter@@main, + splitter_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/splitter/src/splitter.erl b/examples/http_interactions/build/packages/splitter/src/splitter.erl new file mode 100644 index 0000000..e2983f1 --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/src/splitter.erl @@ -0,0 +1,145 @@ +-module(splitter). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/splitter.gleam"). +-export([split/2, split_before/2, split_after/2, would_split/2, new/1]). +-export_type([splitter/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type splitter() :: any(). + +-file("src/splitter.gleam", 49). +?DOC( + " Use the splitter to find the first substring in the input string, splitting\n" + " the input string at that point.\n" + "\n" + " A tuple of three strings is returned:\n" + " 1. The string prefix before the split.\n" + " 2. The substring the string was split with.\n" + " 3. The string suffix after the split.\n" + "\n" + " If no substring was found then strings 2 and 3 will be empty, and string 1\n" + " will be the whole input string.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let line_ends = splitter.new([\"\\n\", \"\\r\\n\"])\n" + "\n" + " splitter.split(line_ends, \"1. Bread\\n2. Milk\\n\")\n" + " // -> #(\"1. Bread\", \"\\n\", \"2. Milk\\n\")\n" + "\n" + " splitter.split(line_ends, \"No end of line here!\")\n" + " // -> #(\"No end of line here!\", \"\", \"\")\n" + " ```\n" +). +-spec split(splitter(), binary()) -> {binary(), binary(), binary()}. +split(Splitter, String) -> + splitter_ffi:split(Splitter, String). + +-file("src/splitter.gleam", 75). +?DOC( + " Use the splitter to find the first substring in the input string, splitting\n" + " the input string before that point.\n" + "\n" + " A tuple of two strings is returned:\n" + " 1. The string prefix before the matched substring.\n" + " 2. The matched substring and the suffix after.\n" + "\n" + " If no substring was found then the suffix will be empty, and the prefix\n" + " will be the whole input string.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let line_ends = splitter.new([\"\\n\", \"\\r\\n\"])\n" + "\n" + " splitter.split_before(line_ends, \"1. Bread\\n2. Milk\\n\")\n" + " // -> #(\"1. Bread\", \"\\n2. Milk\\n\")\n" + "\n" + " splitter.split_before(line_ends, \"No end of line here!\")\n" + " // -> #(\"No end of line here!\", \"\")\n" + " ```\n" +). +-spec split_before(splitter(), binary()) -> {binary(), binary()}. +split_before(Splitter, String) -> + splitter_ffi:split_before(Splitter, String). + +-file("src/splitter.gleam", 101). +?DOC( + " Use the splitter to find the first substring in the input string, splitting\n" + " the input string after that point.\n" + "\n" + " A tuple of two strings is returned:\n" + " 1. The string prefix before and including the matched substring.\n" + " 2. The suffix after the matched substring.\n" + "\n" + " If no substring was found then the suffix will be empty, and the prefix\n" + " will be the whole input string.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let line_ends = splitter.new([\"\\n\", \"\\r\\n\"])\n" + "\n" + " splitter.split_after(line_ends, \"1. Bread\\n2. Milk\\n\")\n" + " // -> #(\"1. Bread\\n\", \"2. Milk\\n\")\n" + "\n" + " splitter.split_after(line_ends, \"No end of line here!\")\n" + " // -> #(\"No end of line here!\", \"\")\n" + " ```\n" +). +-spec split_after(splitter(), binary()) -> {binary(), binary()}. +split_after(Splitter, String) -> + splitter_ffi:split_after(Splitter, String). + +-file("src/splitter.gleam", 123). +?DOC( + " Use the splitter to check whether any of the substrings are contained\n" + " in the input string, without splitting the input string.\n" + "\n" + " Returns a bool value indicating whether a match was found in the input\n" + " string.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let line_ends = splitter.new([\"\\n\", \"\\r\\n\"])\n" + "\n" + " splitter.would_split(line_ends, \"1. Bread\\n2. Milk\\n\")\n" + " // -> True\n" + "\n" + " splitter.would_split(line_ends, \"No end of line here!\")\n" + " // -> False\n" + " ```\n" +). +-spec would_split(splitter(), binary()) -> boolean(). +would_split(Splitter, String) -> + splitter_ffi:would_split(Splitter, String). + +-file("src/splitter.gleam", 18). +?DOC( + " Create a new splitter for a given list of substrings.\n" + "\n" + " Substrings are matched for in the order the appear in the list, if one\n" + " substring is the substring of another place it later in the list than the\n" + " superstring.\n" + "\n" + " Empty strings are discarded, and an empty list will not split off any\n" + " prefix.\n" + "\n" + " There is a small cost to creating a splitter, so if you are going to split\n" + " a string multiple times, and you want as much performance as possible, then\n" + " it is better to reuse the same splitter than to create a new one each time.\n" +). +-spec new(list(binary())) -> splitter(). +new(Substrings) -> + _pipe = Substrings, + _pipe@1 = gleam@list:filter(_pipe, fun(X) -> X /= <<""/utf8>> end), + splitter_ffi:new(_pipe@1). diff --git a/examples/http_interactions/build/packages/splitter/src/splitter.gleam b/examples/http_interactions/build/packages/splitter/src/splitter.gleam new file mode 100644 index 0000000..bb877c9 --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/src/splitter.gleam @@ -0,0 +1,127 @@ +import gleam/list + +pub type Splitter + +/// Create a new splitter for a given list of substrings. +/// +/// Substrings are matched for in the order the appear in the list, if one +/// substring is the substring of another place it later in the list than the +/// superstring. +/// +/// Empty strings are discarded, and an empty list will not split off any +/// prefix. +/// +/// There is a small cost to creating a splitter, so if you are going to split +/// a string multiple times, and you want as much performance as possible, then +/// it is better to reuse the same splitter than to create a new one each time. +/// +pub fn new(substrings: List(String)) -> Splitter { + substrings + |> list.filter(fn(x) { x != "" }) + |> make +} + +/// Use the splitter to find the first substring in the input string, splitting +/// the input string at that point. +/// +/// A tuple of three strings is returned: +/// 1. The string prefix before the split. +/// 2. The substring the string was split with. +/// 3. The string suffix after the split. +/// +/// If no substring was found then strings 2 and 3 will be empty, and string 1 +/// will be the whole input string. +/// +/// # Examples +/// +/// ```gleam +/// let line_ends = splitter.new(["\n", "\r\n"]) +/// +/// splitter.split(line_ends, "1. Bread\n2. Milk\n") +/// // -> #("1. Bread", "\n", "2. Milk\n") +/// +/// splitter.split(line_ends, "No end of line here!") +/// // -> #("No end of line here!", "", "") +/// ``` +/// +@external(erlang, "splitter_ffi", "split") +@external(javascript, "./splitter_ffi.mjs", "split") +pub fn split(splitter: Splitter, string: String) -> #(String, String, String) + +/// Use the splitter to find the first substring in the input string, splitting +/// the input string before that point. +/// +/// A tuple of two strings is returned: +/// 1. The string prefix before the matched substring. +/// 2. The matched substring and the suffix after. +/// +/// If no substring was found then the suffix will be empty, and the prefix +/// will be the whole input string. +/// +/// # Examples +/// +/// ```gleam +/// let line_ends = splitter.new(["\n", "\r\n"]) +/// +/// splitter.split_before(line_ends, "1. Bread\n2. Milk\n") +/// // -> #("1. Bread", "\n2. Milk\n") +/// +/// splitter.split_before(line_ends, "No end of line here!") +/// // -> #("No end of line here!", "") +/// ``` +/// +@external(erlang, "splitter_ffi", "split_before") +@external(javascript, "./splitter_ffi.mjs", "split_before") +pub fn split_before(splitter: Splitter, string: String) -> #(String, String) + +/// Use the splitter to find the first substring in the input string, splitting +/// the input string after that point. +/// +/// A tuple of two strings is returned: +/// 1. The string prefix before and including the matched substring. +/// 2. The suffix after the matched substring. +/// +/// If no substring was found then the suffix will be empty, and the prefix +/// will be the whole input string. +/// +/// # Examples +/// +/// ```gleam +/// let line_ends = splitter.new(["\n", "\r\n"]) +/// +/// splitter.split_after(line_ends, "1. Bread\n2. Milk\n") +/// // -> #("1. Bread\n", "2. Milk\n") +/// +/// splitter.split_after(line_ends, "No end of line here!") +/// // -> #("No end of line here!", "") +/// ``` +/// +@external(erlang, "splitter_ffi", "split_after") +@external(javascript, "./splitter_ffi.mjs", "split_after") +pub fn split_after(splitter: Splitter, string: String) -> #(String, String) + +/// Use the splitter to check whether any of the substrings are contained +/// in the input string, without splitting the input string. +/// +/// Returns a bool value indicating whether a match was found in the input +/// string. +/// +/// # Examples +/// +/// ```gleam +/// let line_ends = splitter.new(["\n", "\r\n"]) +/// +/// splitter.would_split(line_ends, "1. Bread\n2. Milk\n") +/// // -> True +/// +/// splitter.would_split(line_ends, "No end of line here!") +/// // -> False +/// ``` +/// +@external(erlang, "splitter_ffi", "would_split") +@external(javascript, "./splitter_ffi.mjs", "would_split") +pub fn would_split(splitter: Splitter, string: String) -> Bool + +@external(erlang, "splitter_ffi", "new") +@external(javascript, "./splitter_ffi.mjs", "make") +fn make(patterns: List(String)) -> Splitter diff --git a/examples/http_interactions/build/packages/splitter/src/splitter_ffi.erl b/examples/http_interactions/build/packages/splitter/src/splitter_ffi.erl new file mode 100644 index 0000000..f8c1b10 --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/src/splitter_ffi.erl @@ -0,0 +1,44 @@ +-module(splitter_ffi). +-export([new/1, split/2, split_before/2, split_after/2, would_split/2]). + +new([]) -> + empty_splitter; +new(Patterns) -> + binary:compile_pattern(Patterns). + +split(empty_splitter, String) -> + {<<>>, <<>>, String}; +split(Splitter, String) -> + case binary:match(String, Splitter) of + nomatch -> {String, <<"">>, <<"">>}; % No delimiter found + {Index, Length} -> + {binary:part(String, 0, Index), + binary:part(String, Index, Length), + binary:part(String, Index + Length, byte_size(String) - Index - Length)} + end. + +split_before(empty_splitter, String) -> + {<<>>, String}; +split_before(Splitter, String) -> + case binary:match(String, Splitter) of + nomatch -> {String, <<"">>}; % No delimiter found + {Index, _Length} -> + {binary:part(String, 0, Index), + binary:part(String, Index, byte_size(String) - Index)} + end. + +split_after(empty_splitter, String) -> + {<<>>, String}; +split_after(Splitter, String) -> + case binary:match(String, Splitter) of + nomatch -> {String, <<"">>}; % No delimiter found + {Index, Length} -> + SplitPoint = Index + Length, + {binary:part(String, 0, SplitPoint), + binary:part(String, SplitPoint, byte_size(String) - SplitPoint)} + end. + +would_split(empty_splitter, _String) -> + false; +would_split(Splitter, String) -> + binary:match(String, Splitter) =/= nomatch. diff --git a/examples/http_interactions/build/packages/splitter/src/splitter_ffi.mjs b/examples/http_interactions/build/packages/splitter/src/splitter_ffi.mjs new file mode 100644 index 0000000..c21884e --- /dev/null +++ b/examples/http_interactions/build/packages/splitter/src/splitter_ffi.mjs @@ -0,0 +1,55 @@ +export function make(patterns) { + let pattern = ""; + let cursor = patterns; + while (cursor.tail) { + if (pattern !== "") pattern += "|"; + pattern += escapeRegExp(cursor.head); + cursor = cursor.tail; + } + return new RegExp(pattern); +} + +export function split(splitter, string) { + const match = string.match(splitter); + + if (!match) return [string, "", ""]; // No delimiter found + + const index = match.index; + const delimiter = match[0]; + + return [ + string.slice(0, index), + delimiter, + string.slice(index + delimiter.length), + ]; +} + +export function split_before(splitter, string) { + const match = string.match(splitter); + + if (!match) return [string, ""]; // No delimiter found + + const split_point = match.index; + return [string.slice(0, split_point), string.slice(split_point)]; +} + +export function split_after(splitter, string) { + const match = string.match(splitter); + + if (!match) return [string, ""]; // No delimiter found + + const split_point = match.index + match[0].length; + return [string.slice(0, split_point), string.slice(split_point)]; +} + +export function would_split(splitter, string) { + // According to https://tc39.es/ecma262/#sec-escaperegexppattern + // RegExp.source must escape "" as "(?:)" + // i.e. this checks for the empty splitter + if (splitter.source === "(?:)") return false + return splitter.test(string) +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} diff --git a/examples/http_interactions/build/packages/status_code/LICENSE b/examples/http_interactions/build/packages/status_code/LICENSE new file mode 100644 index 0000000..ee2796d --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Filip Hoffmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/http_interactions/build/packages/status_code/README.md b/examples/http_interactions/build/packages/status_code/README.md new file mode 100644 index 0000000..182ab22 --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/README.md @@ -0,0 +1,38 @@ +# status_code + +[![Package Version](https://img.shields.io/hexpm/v/status_code)](https://hex.pm/packages/status_code) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/status_code/) + +```sh +gleam add status_code gleam_httpc gleam_http +``` +```gleam +import gleam/http +import gleam/http/request +import gleam/http/response +import gleam/httpc +import gleam/io +import gleam/result +import status_code + +pub fn main() -> Nil { + let response = + request.new() + |> request.set_scheme(http.Https) + |> request.set_host("example.org") + |> request.set_method(http.Get) + |> httpc.send + |> result.unwrap(response.new(status_code.not_found)) + + case response.status { + status if status == status_code.ok -> io.println("Ok!") + status if status == status_code.forbidden -> + io.println("We've been forbidden from entering this realm") + status if status == status_code.im_a_teapot -> + io.println("Never ask a teapot for coffee") + _ -> io.println("A strange thing has happened...") + } +} +``` + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/status_code/gleam.toml b/examples/http_interactions/build/packages/status_code/gleam.toml new file mode 100644 index 0000000..60c6733 --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/gleam.toml @@ -0,0 +1,5 @@ +name = "status_code" +version = "1.1.0" +description = "Provides HTTP status codes in a human-readable way" +licences = ["MIT"] +repository = { type = "github", user = "folospior", repo = "status_code" } diff --git a/examples/http_interactions/build/packages/status_code/src/status_code.app.src b/examples/http_interactions/build/packages/status_code/src/status_code.app.src new file mode 100644 index 0000000..2daecd6 --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/src/status_code.app.src @@ -0,0 +1,8 @@ +{application, status_code, [ + {vsn, "1.1.0"}, + {applications, []}, + {description, "Provides HTTP status codes in a human-readable way"}, + {modules, [status_code, + status_code@@main]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/status_code/src/status_code.erl b/examples/http_interactions/build/packages/status_code/src/status_code.erl new file mode 100644 index 0000000..4b92570 --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/src/status_code.erl @@ -0,0 +1,29 @@ +-module(status_code). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src\\status_code.gleam"). +-export([is_informational/1, is_successful/1, is_redirection/1, is_client_error/1, is_server_error/1]). + +-file("src\\status_code.gleam", 129). +-spec is_informational(integer()) -> boolean(). +is_informational(Status) -> + (Status >= 100) andalso (Status =< 199). + +-file("src\\status_code.gleam", 133). +-spec is_successful(integer()) -> boolean(). +is_successful(Status) -> + (Status >= 200) andalso (Status =< 299). + +-file("src\\status_code.gleam", 137). +-spec is_redirection(integer()) -> boolean(). +is_redirection(Status) -> + (Status >= 300) andalso (Status =< 399). + +-file("src\\status_code.gleam", 141). +-spec is_client_error(integer()) -> boolean(). +is_client_error(Status) -> + (Status >= 400) andalso (Status =< 499). + +-file("src\\status_code.gleam", 145). +-spec is_server_error(integer()) -> boolean(). +is_server_error(Status) -> + (Status >= 500) andalso (Status =< 599). diff --git a/examples/http_interactions/build/packages/status_code/src/status_code.gleam b/examples/http_interactions/build/packages/status_code/src/status_code.gleam new file mode 100644 index 0000000..d4b83e3 --- /dev/null +++ b/examples/http_interactions/build/packages/status_code/src/status_code.gleam @@ -0,0 +1,147 @@ +// PUBLIC CONSTANTS ------------------------------------------------------------ + +pub const continue = 100 + +pub const switching_protocols = 101 + +pub const processing = 102 + +pub const early_hints = 103 + +pub const ok = 200 + +pub const created = 201 + +pub const accepted = 202 + +pub const non_authoritative_information = 203 + +pub const no_content = 204 + +pub const reset_content = 205 + +pub const partial_content = 206 + +pub const multi_status = 207 + +pub const already_reported = 208 + +pub const im_used = 226 + +pub const multiple_choices = 300 + +pub const moved_permanently = 301 + +pub const found = 302 + +pub const see_other = 303 + +pub const not_modified = 304 + +pub const use_proxy = 305 + +pub const temporary_redirect = 307 + +pub const permanent_redirect = 308 + +pub const bad_request = 400 + +pub const unauthorized = 401 + +pub const payment_required = 402 + +pub const forbidden = 403 + +pub const not_found = 404 + +pub const method_not_allowed = 405 + +pub const not_acceptable = 406 + +pub const proxy_authentication_required = 407 + +pub const request_timeout = 408 + +pub const conflict = 409 + +pub const gone = 410 + +pub const length_required = 411 + +pub const precondition_failed = 412 + +pub const content_too_large = 413 + +pub const uri_too_long = 414 + +pub const unsupported_media_type = 415 + +pub const range_not_satisfiable = 416 + +pub const expectation_failed = 417 + +pub const im_a_teapot = 418 + +pub const misdirected_request = 421 + +pub const unprocessable_content = 422 + +pub const locked = 423 + +pub const failed_dependency = 424 + +pub const too_early = 425 + +pub const upgrade_required = 426 + +pub const precondition_required = 428 + +pub const too_many_requests = 429 + +pub const request_header_fields_too_large = 431 + +pub const unavailable_for_legal_reasons = 451 + +pub const internal_server_error = 500 + +pub const not_implemented = 501 + +pub const bad_gateway = 502 + +pub const service_unavailable = 503 + +pub const gateway_timeout = 504 + +pub const http_version_not_supported = 505 + +pub const variant_also_negotiates = 506 + +pub const insufficient_storage = 507 + +pub const loop_detected = 508 + +pub const not_extended = 510 + +pub const network_authentication_required = 511 + +// PUBLIC API FUNCTIONS -------------------------------------------------------- + +pub fn is_informational(status: Int) -> Bool { + status >= 100 && status <= 199 +} + +pub fn is_successful(status: Int) -> Bool { + status >= 200 && status <= 299 +} + +pub fn is_redirection(status: Int) -> Bool { + status >= 300 && status <= 399 +} + +pub fn is_client_error(status: Int) -> Bool { + status >= 400 && status <= 499 +} + +pub fn is_server_error(status: Int) -> Bool { + status >= 500 && status <= 599 +} diff --git a/examples/http_interactions/build/packages/stratus/LICENSE b/examples/http_interactions/build/packages/stratus/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/http_interactions/build/packages/stratus/README.md b/examples/http_interactions/build/packages/stratus/README.md new file mode 100644 index 0000000..4f99111 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/README.md @@ -0,0 +1,126 @@ +# stratus + +A Gleam WebSocket client (and possibly more). + +[![Package Version](https://img.shields.io/hexpm/v/stratus)](https://hex.pm/packages/stratus) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/stratus/) + +```sh +gleam add stratus +``` + +## Features +- [x] Sending and receiving Text and Binary frames +- [x] Custom user messages +- [ ] Sending and receiving continuation frames +- [x] Per-message deflate +- [ ] Graceful shutdown + +## Example + +```gleam +import gleam/erlang/process.{type Subject} +import gleam/function +import gleam/http/request +import gleam/int +import gleam/io +import gleam/string +import gleam/time/calendar +import gleam/time/timestamp +import logging +import repeatedly +import stratus + +pub type Msg { + Close + TimeUpdated(String) + DoTheThing(Subject(Int)) +} + +pub fn main() { + logging.configure() + logging.set_level(logging.Debug) + let assert Ok(req) = + request.to("http://localhost:3000/ws?hello=world&value=123") + let assert Ok(subj) = + // NOTE: If you need to do something in your startup, you can use this + // + // stratus.initialised(some_state) + // |> stratus.selecting(my_selector) + // |> Ok + stratus.new(req, Nil) + |> stratus.on_message(fn(state, msg, conn) { + case msg { + stratus.Text(msg) -> { + logging.log(logging.Info, "Got a message: " <> msg) + stratus.continue(state) + } + stratus.User(TimeUpdated(msg)) -> { + let assert Ok(_resp) = stratus.send_text_message(conn, msg) + stratus.continue(state) + } + stratus.User(DoTheThing(resp)) -> { + process.send(resp, 1234) + stratus.continue(state) + } + stratus.Binary(_msg) -> stratus.continue(state) + stratus.User(Close) -> { + let assert Ok(_) = + stratus.close(conn, stratus.GoingAway(<<"goodbye!">>)) + stratus.stop() + } + } + }) + |> stratus.on_close(fn(_state, _reason) { io.println("oh noooo") }) + |> stratus.start + + let timer = + repeatedly.call(1000, Nil, fn(_state, _count_) { + timestamp.system_time() + |> timestamp.to_rfc3339(calendar.local_offset()) + |> TimeUpdated + |> stratus.to_user_message + |> process.send(subj.data, _) + }) + + process.spawn(fn() { + process.sleep(6000) + + stratus.to_user_message(Close) + |> process.send(subj.data, _) + }) + + process.spawn(fn() { + process.sleep(500) + let resp = + process.call(subj.data, 100, fn(subj) { + stratus.to_user_message(DoTheThing(subj)) + }) + logging.log(logging.Info, "Got the thing: " <> int.to_string(resp)) + process.sleep(1000) + let resp = + process.call_forever(subj.data, fn(subj) { + stratus.to_user_message(DoTheThing(subj)) + }) + logging.log(logging.Info, "Got the thing pt 2: " <> int.to_string(resp)) + }) + + let assert Ok(owner) = process.subject_owner(subj.data) + let done = + process.new_selector() + |> process.select_specific_monitor( + process.monitor(owner), + function.identity, + ) + |> process.selector_receive_forever + + logging.log( + logging.Info, + "WebSocket process exited: " <> string.inspect(done), + ) + + repeatedly.stop(timer) +} +``` + +Further documentation can be found at . diff --git a/examples/http_interactions/build/packages/stratus/gleam.toml b/examples/http_interactions/build/packages/stratus/gleam.toml new file mode 100644 index 0000000..103adff --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/gleam.toml @@ -0,0 +1,19 @@ +name = "stratus" +version = "3.0.0" + +description = "A Gleam WebSocket client" +licences = ["Apache-2.0"] +repository = { type = "github", user = "rawhat", repo = "stratus" } + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +gleam_otp = ">= 1.0.0 and < 2.0.0" +gleam_http = ">= 4.0.0 and < 5.0.0" +gleam_crypto = ">= 1.5.0 and < 2.0.0" +logging = ">= 1.3.0 and < 2.0.0" +exception = ">= 2.1.0 and < 3.0.0" +gramps = ">= 6.0.0 and < 7.0.0" + +[dev-dependencies] +gleeunit = ">= 1.5.1 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_AbnormalStop.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_AbnormalStop.hrl new file mode 100644 index 0000000..d2ca52b --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_AbnormalStop.hrl @@ -0,0 +1 @@ +-record(abnormal_stop, {reason :: binary()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_Builder.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_Builder.hrl new file mode 100644 index 0000000..95c8a5f --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_Builder.hrl @@ -0,0 +1,8 @@ +-record(builder, { + request :: gleam@http@request:request(binary()), + connect_timeout :: integer(), + init :: fun(() -> {ok, stratus:initialised(any(), any())} | + {error, binary()}), + loop :: fun((any(), stratus:message(any()), stratus:connection()) -> stratus:next(any(), any())), + on_close :: fun((any(), stratus:close_reason()) -> nil) +}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_Connection.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_Connection.hrl new file mode 100644 index 0000000..e8f54a2 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_Connection.hrl @@ -0,0 +1,5 @@ +-record(connection, { + socket :: stratus@internal@socket:socket(), + transport :: stratus@internal@transport:transport(), + context :: gleam@option:option(gramps@websocket@compression:context()) +}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_Continue.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_Continue.hrl new file mode 100644 index 0000000..85677d1 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_Continue.hrl @@ -0,0 +1,4 @@ +-record(continue, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_CustomCloseReason.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_CustomCloseReason.hrl new file mode 100644 index 0000000..92e3e4f --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_CustomCloseReason.hrl @@ -0,0 +1 @@ +-record(custom_close_reason, {code :: integer(), body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_GoingAway.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_GoingAway.hrl new file mode 100644 index 0000000..4992317 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_GoingAway.hrl @@ -0,0 +1 @@ +-record(going_away, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_InconsistentDataType.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_InconsistentDataType.hrl new file mode 100644 index 0000000..aedbbdb --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_InconsistentDataType.hrl @@ -0,0 +1 @@ +-record(inconsistent_data_type, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_Initialised.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_Initialised.hrl new file mode 100644 index 0000000..3c567a5 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_Initialised.hrl @@ -0,0 +1,4 @@ +-record(initialised, { + state :: any(), + selector :: gleam@option:option(gleam@erlang@process:selector(any())) +}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_MessageTooBig.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_MessageTooBig.hrl new file mode 100644 index 0000000..ec6d67f --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_MessageTooBig.hrl @@ -0,0 +1 @@ +-record(message_too_big, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_MissingExtensions.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_MissingExtensions.hrl new file mode 100644 index 0000000..2c6ab73 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_MissingExtensions.hrl @@ -0,0 +1 @@ +-record(missing_extensions, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_Normal.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_Normal.hrl new file mode 100644 index 0000000..ab2c396 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_Normal.hrl @@ -0,0 +1 @@ +-record(normal, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_PolicyViolation.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_PolicyViolation.hrl new file mode 100644 index 0000000..7fa4ca6 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_PolicyViolation.hrl @@ -0,0 +1 @@ +-record(policy_violation, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_ProtocolError.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_ProtocolError.hrl new file mode 100644 index 0000000..32ed846 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_ProtocolError.hrl @@ -0,0 +1 @@ +-record(protocol_error, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedCondition.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedCondition.hrl new file mode 100644 index 0000000..ffe5cec --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedCondition.hrl @@ -0,0 +1 @@ +-record(unexpected_condition, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedDataType.hrl b/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedDataType.hrl new file mode 100644 index 0000000..bf800f9 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/include/stratus_UnexpectedDataType.hrl @@ -0,0 +1 @@ +-record(unexpected_data_type, {body :: bitstring()}). diff --git a/examples/http_interactions/build/packages/stratus/src/stratus.app.src b/examples/http_interactions/build/packages/stratus/src/stratus.app.src new file mode 100644 index 0000000..0483b01 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus.app.src @@ -0,0 +1,20 @@ +{application, stratus, [ + {vsn, "3.0.0"}, + {applications, [exception, + gleam_crypto, + gleam_erlang, + gleam_http, + gleam_otp, + gleam_stdlib, + gramps, + logging]}, + {description, "A Gleam WebSocket client"}, + {modules, [stratus, + stratus@@main, + stratus@internal@socket, + stratus@internal@ssl, + stratus@internal@tcp, + stratus@internal@transport, + stratus_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/stratus/src/stratus.erl b/examples/http_interactions/build/packages/stratus/src/stratus.erl new file mode 100644 index 0000000..7851026 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus.erl @@ -0,0 +1,1502 @@ +-module(stratus). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/stratus.gleam"). +-export([continue/1, with_selector/2, stop/0, stop_abnormal/1, initialised/1, new/2, selecting/2, new_with_initialiser/2, on_message/2, with_connect_timeout/2, on_close/2, to_user_message/1, send_text_message/2, send_binary_message/2, send_ping/2, get_custom_code/1, get_custom_body/1, close/2, close_custom/3, start/1, supervised/1]). +-export_type([connection/0, socket_reason/0, custom_close_error/0, next/2, internal_message/1, message/1, builder/2, initialised/2, state/2, close_reason/0, custom_close_reason/0, handshake_error/0, handshake_response/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-opaque connection() :: {connection, + stratus@internal@socket:socket(), + stratus@internal@transport:transport(), + gleam@option:option(gramps@websocket@compression:context())}. + +-type socket_reason() :: socket_closed | + timeout | + badarg | + terminated | + eaddrinuse | + eaddrnotavail | + eafnosupport | + ealready | + econnaborted | + econnrefused | + econnreset | + edestaddrreq | + ehostdown | + ehostunreach | + einprogress | + eisconn | + emsgsize | + enetdown | + enetunreach | + enopkg | + enoprotoopt | + enotconn | + enotty | + enotsock | + eproto | + eprotonosupport | + eprototype | + esocktnosupport | + etimedout | + ewouldblock | + exbadport | + exbadseq. + +-type custom_close_error() :: {socket_fail, socket_reason()} | invalid_code. + +-opaque next(HEJ, HEK) :: {continue, + HEJ, + gleam@option:option(gleam@erlang@process:selector(HEK))} | + normal_stop | + {abnormal_stop, binary()}. + +-opaque internal_message(HEL) :: {user_message, HEL} | + {err, socket_reason()} | + {data, bitstring()} | + {closed, close_reason()} | + {ready, bitstring()} | + shutdown. + +-type message(HEM) :: {text, binary()} | {binary, bitstring()} | {user, HEM}. + +-opaque builder(HEN, HEO) :: {builder, + gleam@http@request:request(binary()), + integer(), + fun(() -> {ok, initialised(HEN, HEO)} | {error, binary()}), + fun((HEN, message(HEO), connection()) -> next(HEN, HEO)), + fun((HEN, close_reason()) -> nil)}. + +-type initialised(HEP, HEQ) :: {initialised, + HEP, + gleam@option:option(gleam@erlang@process:selector(HEQ))}. + +-type state(HER, HES) :: {state, + bitstring(), + gleam@option:option(gramps@websocket:frame()), + gleam@erlang@process:subject(internal_message(HES)), + stratus@internal@socket:socket(), + HER, + gleam@option:option(gramps@websocket@compression:compression())}. + +-type close_reason() :: not_provided | + {normal, bitstring()} | + {going_away, bitstring()} | + {protocol_error, bitstring()} | + {unexpected_data_type, bitstring()} | + {inconsistent_data_type, bitstring()} | + {policy_violation, bitstring()} | + {message_too_big, bitstring()} | + {missing_extensions, bitstring()} | + {unexpected_condition, bitstring()} | + {custom, custom_close_reason()}. + +-opaque custom_close_reason() :: {custom_close_reason, integer(), bitstring()}. + +-type handshake_error() :: {sock, socket_reason()} | + {protocol, bitstring()} | + {upgrade_failed, gleam@http@response:response(bitstring())}. + +-type handshake_response() :: {handshake_response, + stratus@internal@socket:socket(), + gleam@http@response:response(bitstring()), + bitstring()}. + +-file("src/stratus.gleam", 81). +-spec convert_socket_reason(stratus@internal@socket:socket_reason()) -> socket_reason(). +convert_socket_reason(Reason) -> + case Reason of + badarg -> + badarg; + + closed -> + socket_closed; + + eaddrinuse -> + eaddrinuse; + + eaddrnotavail -> + eaddrnotavail; + + eafnosupport -> + eafnosupport; + + ealready -> + ealready; + + econnaborted -> + econnaborted; + + econnrefused -> + econnrefused; + + econnreset -> + econnreset; + + edestaddrreq -> + edestaddrreq; + + ehostdown -> + ehostdown; + + ehostunreach -> + ehostunreach; + + einprogress -> + einprogress; + + eisconn -> + eisconn; + + emsgsize -> + emsgsize; + + enetdown -> + enetdown; + + enetunreach -> + enetunreach; + + enopkg -> + enopkg; + + enoprotoopt -> + enoprotoopt; + + enotconn -> + enotconn; + + enotsock -> + enotsock; + + enotty -> + enotty; + + eproto -> + eproto; + + eprotonosupport -> + eprotonosupport; + + eprototype -> + eprototype; + + esocktnosupport -> + esocktnosupport; + + etimedout -> + etimedout; + + ewouldblock -> + ewouldblock; + + exbadport -> + exbadport; + + exbadseq -> + exbadseq; + + terminated -> + terminated; + + timeout -> + timeout + end. + +-file("src/stratus.gleam", 118). +-spec from_socket_message(stratus@internal@socket:socket_message()) -> internal_message(any()). +from_socket_message(Msg) -> + case Msg of + {data, Bits} -> + {data, Bits}; + + {err, Reason} -> + {err, convert_socket_reason(Reason)} + end. + +-file("src/stratus.gleam", 131). +-spec continue(HEV) -> next(HEV, any()). +continue(State) -> + {continue, State, none}. + +-file("src/stratus.gleam", 135). +-spec with_selector(next(HEZ, HFA), gleam@erlang@process:selector(HFA)) -> next(HEZ, HFA). +with_selector(Next, Selector) -> + case Next of + {continue, State, _} -> + {continue, State, {some, Selector}}; + + _ -> + Next + end. + +-file("src/stratus.gleam", 145). +-spec stop() -> next(any(), any()). +stop() -> + normal_stop. + +-file("src/stratus.gleam", 149). +-spec stop_abnormal(binary()) -> next(any(), any()). +stop_abnormal(Reason) -> + {abnormal_stop, Reason}. + +-file("src/stratus.gleam", 204). +-spec initialised(HFT) -> initialised(HFT, any()). +initialised(State) -> + {initialised, State, none}. + +-file("src/stratus.gleam", 187). +?DOC( + " This creates a builder to set up a WebSocket actor. This will use default\n" + " values for the connection initialization timeout, and provide an empty\n" + " function to be called when the server closes the connection. If you want to\n" + " customize either of those, see the helper functions `with_connect_timeout`\n" +). +-spec new(gleam@http@request:request(binary()), HFP) -> builder(HFP, any()). +new(Req, State) -> + {builder, + Req, + 5000, + fun() -> {ok, initialised(State)} end, + fun(State@1, _, _) -> continue(State@1) end, + fun(_, _) -> nil end}. + +-file("src/stratus.gleam", 208). +-spec selecting(initialised(HFX, any()), gleam@erlang@process:selector(HGB)) -> initialised(HFX, HGB). +selecting(Initialised, Selector) -> + {initialised, erlang:element(2, Initialised), {some, Selector}}. + +-file("src/stratus.gleam", 215). +-spec new_with_initialiser( + gleam@http@request:request(binary()), + fun(() -> {ok, initialised(HGG, HGH)} | {error, binary()}) +) -> builder(HGG, HGH). +new_with_initialiser(Req, Init) -> + {builder, + Req, + 5000, + Init, + fun(State, _, _) -> continue(State) end, + fun(_, _) -> nil end}. + +-file("src/stratus.gleam", 228). +-spec on_message( + builder(HGO, HGP), + fun((HGO, message(HGP), connection()) -> next(HGO, HGP)) +) -> builder(HGO, HGP). +on_message(Builder, On_message) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + On_message, + erlang:element(6, Builder)}. + +-file("src/stratus.gleam", 240). +?DOC( + " This sets the maximum amount of time you are willing to wait for both\n" + " connecting to the server and receiving the upgrade response. This means\n" + " that it may take up to `timeout * 2` to begin sending or receiving messages.\n" + " This value defaults to 5 seconds.\n" +). +-spec with_connect_timeout(builder(HGX, HGY), integer()) -> builder(HGX, HGY). +with_connect_timeout(Builder, Timeout) -> + {builder, + erlang:element(2, Builder), + Timeout, + erlang:element(4, Builder), + erlang:element(5, Builder), + erlang:element(6, Builder)}. + +-file("src/stratus.gleam", 253). +?DOC( + " You can provide a function to be called when the connection is closed. This\n" + " function receives the last value for the state of the WebSocket.\n" + "\n" + " NOTE: If you manually call `stratus.close`, this function will not be\n" + " called. I'm unsure right now if this is a bug or working as intended. But\n" + " you will be in the loop with the state value handy.\n" +). +-spec on_close(builder(HHD, HHE), fun((HHD, close_reason()) -> nil)) -> builder(HHD, HHE). +on_close(Builder, On_close) -> + {builder, + erlang:element(2, Builder), + erlang:element(3, Builder), + erlang:element(4, Builder), + erlang:element(5, Builder), + On_close}. + +-file("src/stratus.gleam", 617). +?DOC( + " The `Subject` returned from `start` is an opaque type. In order to\n" + " send custom messages to your process, you can do this mapping.\n" + "\n" + " For example:\n" + " ```gleam\n" + " // using `process.send`\n" + " MyMessage(some_data)\n" + " |> stratus.to_user_message\n" + " |> process.send(stratus_subject, _)\n" + " // using `process.call`\n" + " process.call(stratus_subject, fn(subj) {\n" + " stratus.to_user_message(MyMessage(some_data, subj))\n" + " })\n" + " ```\n" +). +-spec to_user_message(HII) -> internal_message(HII). +to_user_message(User_message) -> + {user_message, User_message}. + +-file("src/stratus.gleam", 625). +?DOC( + " From within the actor loop, this is how you send a WebSocket text frame.\n" + " This must be valid UTF-8, so it is a `String`.\n" +). +-spec send_text_message(connection(), binary()) -> {ok, nil} | + {error, socket_reason()}. +send_text_message(Conn, Msg) -> + Frame = gramps@websocket:encode_text_frame( + Msg, + erlang:element(4, Conn), + {some, crypto:strong_rand_bytes(4)} + ), + _pipe = stratus@internal@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Frame + ), + gleam@result:map_error(_pipe, fun convert_socket_reason/1). + +-file("src/stratus.gleam", 640). +?DOC(" From within the actor loop, this is how you send a WebSocket text frame.\n"). +-spec send_binary_message(connection(), bitstring()) -> {ok, nil} | + {error, socket_reason()}. +send_binary_message(Conn, Msg) -> + Frame = gramps@websocket:encode_binary_frame( + Msg, + erlang:element(4, Conn), + {some, crypto:strong_rand_bytes(4)} + ), + _pipe = stratus@internal@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Frame + ), + gleam@result:map_error(_pipe, fun convert_socket_reason/1). + +-file("src/stratus.gleam", 655). +?DOC(" Send a ping frame with some data.\n"). +-spec send_ping(connection(), bitstring()) -> {ok, nil} | + {error, socket_reason()}. +send_ping(Conn, Data) -> + Size = erlang:byte_size(Data), + Mask = case Size of + 0 -> + {some, <<0:4>>}; + + _ -> + {some, crypto:strong_rand_bytes(4)} + end, + Frame = gramps@websocket:encode_ping_frame(Data, Mask), + _pipe = stratus@internal@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Frame + ), + gleam@result:map_error(_pipe, fun convert_socket_reason/1). + +-file("src/stratus.gleam", 695). +-spec get_custom_code(custom_close_reason()) -> integer(). +get_custom_code(Reason) -> + erlang:element(2, Reason). + +-file("src/stratus.gleam", 699). +-spec get_custom_body(custom_close_reason()) -> bitstring(). +get_custom_body(Reason) -> + erlang:element(3, Reason). + +-file("src/stratus.gleam", 703). +-spec from_websocket_close_reason(gramps@websocket:close_reason()) -> close_reason(). +from_websocket_close_reason(Reason) -> + case Reason of + not_provided -> + not_provided; + + {going_away, Body} -> + {going_away, Body}; + + {inconsistent_data_type, Body@1} -> + {inconsistent_data_type, Body@1}; + + {message_too_big, Body@2} -> + {message_too_big, Body@2}; + + {missing_extensions, Body@3} -> + {missing_extensions, Body@3}; + + {normal, Body@4} -> + {normal, Body@4}; + + {policy_violation, Body@5} -> + {policy_violation, Body@5}; + + {protocol_error, Body@6} -> + {protocol_error, Body@6}; + + {unexpected_condition, Body@7} -> + {unexpected_condition, Body@7}; + + {unexpected_data_type, Body@8} -> + {unexpected_data_type, Body@8}; + + {custom_close_reason, Code, Body@9} -> + {custom, {custom_close_reason, Code, Body@9}} + end. + +-file("src/stratus.gleam", 499). +-spec handle_frame( + builder(HHX, HHY), + state(HHX, HHY), + connection(), + gramps@websocket:frame() +) -> next(state(HHX, HHY), internal_message(HHY)). +handle_frame(Builder, State, Conn, Frame) -> + case Frame of + {data, {text_frame, Data}} -> + Str@1 = case gleam@bit_array:to_string(Data) of + {ok, Str} -> Str; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"handle_frame"/utf8>>, + line => 507, + value => _assert_fail, + start => 15119, + 'end' => 15165, + pattern_start => 15130, + pattern_end => 15137}) + end, + Res = exception_ffi:rescue( + fun() -> + (erlang:element(5, Builder))( + erlang:element(6, State), + {text, Str@1}, + Conn + ) + end + ), + case Res of + {ok, {continue, User_state, User_selector}} -> + New_state = {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + User_state, + erlang:element(7, State)}, + case User_selector of + {some, User_selector@1} -> + Selector = begin + _pipe = User_selector@1, + _pipe@1 = gleam_erlang_ffi:map_selector( + _pipe, + fun(Field@0) -> {user_message, Field@0} end + ), + gleam_erlang_ffi:merge_selector( + _pipe@1, + gleam_erlang_ffi:map_selector( + stratus@internal@socket:selector(), + fun(Msg) -> + Msg@2 = case Msg of + {ok, Msg@1} -> Msg@1; + _assert_fail@1 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"handle_frame"/utf8>>, + line => 523, + value => _assert_fail@1, + start => 15760, + 'end' => 15784, + pattern_start => 15771, + pattern_end => 15778} + ) + end, + from_socket_message(Msg@2) + end + ) + ) + end, + {continue, New_state, {some, Selector}}; + + _ -> + continue(New_state) + end; + + {ok, normal_stop} -> + normal_stop; + + {ok, {abnormal_stop, Reason}} -> + {abnormal_stop, Reason}; + + {error, Reason@1} -> + logging:log( + error, + <<"Caught error in user handler: "/utf8, + (gleam@string:inspect(Reason@1))/binary>> + ), + continue(State) + end; + + {data, {binary_frame, Data@1}} -> + Res@1 = exception_ffi:rescue( + fun() -> + (erlang:element(5, Builder))( + erlang:element(6, State), + {binary, Data@1}, + Conn + ) + end + ), + case Res@1 of + {ok, {continue, User_state@1, User_selector@2}} -> + New_state@1 = {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + User_state@1, + erlang:element(7, State)}, + case User_selector@2 of + {some, User_selector@3} -> + Selector@1 = begin + _pipe@2 = User_selector@3, + _pipe@3 = gleam_erlang_ffi:map_selector( + _pipe@2, + fun(Field@0) -> {user_message, Field@0} end + ), + gleam_erlang_ffi:merge_selector( + _pipe@3, + gleam_erlang_ffi:map_selector( + stratus@internal@socket:selector(), + fun(Msg@3) -> + Msg@5 = case Msg@3 of + {ok, Msg@4} -> Msg@4; + _assert_fail@2 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"handle_frame"/utf8>>, + line => 559, + value => _assert_fail@2, + start => 16942, + 'end' => 16966, + pattern_start => 16953, + pattern_end => 16960} + ) + end, + from_socket_message(Msg@5) + end + ) + ) + end, + {continue, New_state@1, {some, Selector@1}}; + + _ -> + continue(New_state@1) + end; + + {ok, normal_stop} -> + normal_stop; + + {ok, {abnormal_stop, Reason@2}} -> + {abnormal_stop, Reason@2}; + + {error, Reason@3} -> + logging:log( + error, + <<"Caught error in user handler: "/utf8, + (gleam@string:inspect(Reason@3))/binary>> + ), + continue(State) + end; + + {control, {ping_frame, Payload}} -> + Mask = {some, <<0:4/unit:8>>}, + Frame@1 = gramps@websocket:encode_pong_frame(Payload, Mask), + _ = stratus@internal@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Frame@1 + ), + continue(State); + + {control, {pong_frame, _}} -> + continue(State); + + {control, {close_frame, Reason@4}} -> + logging:log( + debug, + <<"WebSocket closing: "/utf8, + (gleam@string:inspect(Reason@4))/binary>> + ), + (erlang:element(6, Builder))( + erlang:element(6, State), + from_websocket_close_reason(Reason@4) + ), + normal_stop; + + {continuation, _, _} -> + continue(State) + end. + +-file("src/stratus.gleam", 720). +-spec to_websocket_close_reason(close_reason()) -> gramps@websocket:close_reason(). +to_websocket_close_reason(Reason) -> + case Reason of + not_provided -> + not_provided; + + {going_away, Body} -> + {going_away, Body}; + + {inconsistent_data_type, Body@1} -> + {inconsistent_data_type, Body@1}; + + {message_too_big, Body@2} -> + {message_too_big, Body@2}; + + {missing_extensions, Body@3} -> + {missing_extensions, Body@3}; + + {normal, Body@4} -> + {normal, Body@4}; + + {policy_violation, Body@5} -> + {policy_violation, Body@5}; + + {protocol_error, Body@6} -> + {protocol_error, Body@6}; + + {unexpected_condition, Body@7} -> + {unexpected_condition, Body@7}; + + {unexpected_data_type, Body@8} -> + {unexpected_data_type, Body@8}; + + {custom, {custom_close_reason, Code, Body@9}} -> + {custom_close_reason, Code, Body@9} + end. + +-file("src/stratus.gleam", 752). +-spec close(connection(), close_reason()) -> {ok, nil} | + {error, socket_reason()}. +close(Conn, Reason) -> + Reason@1 = to_websocket_close_reason(Reason), + Mask = crypto:strong_rand_bytes(4), + Frame = gramps@websocket:encode_close_frame(Reason@1, {some, Mask}), + _pipe = stratus@internal@transport:send( + erlang:element(3, Conn), + erlang:element(2, Conn), + Frame + ), + gleam@result:map_error(_pipe, fun convert_socket_reason/1). + +-file("src/stratus.gleam", 738). +?DOC(" Closes the connection with a custom close code between 1000 and 4999.\n"). +-spec close_custom(connection(), integer(), bitstring()) -> {ok, nil} | + {error, custom_close_error()}. +close_custom(Conn, Code, Body) -> + gleam@bool:guard( + (Code >= 5000) orelse (Code < 1000), + {error, invalid_code}, + fun() -> + _pipe = close(Conn, {custom, {custom_close_reason, Code, Body}}), + gleam@result:map_error( + _pipe, + fun(Field@0) -> {socket_fail, Field@0} end + ) + end + ). + +-file("src/stratus.gleam", 764). +-spec make_upgrade(gleam@http@request:request(binary())) -> gleam@bytes_tree:bytes_tree(). +make_upgrade(Req) -> + User_headers = case erlang:element(3, Req) of + [] -> + <<""/utf8>>; + + _ -> + _pipe = erlang:element(3, Req), + _pipe@1 = gleam@list:filter( + _pipe, + fun(Pair) -> + {Key, _} = Pair, + ((((Key /= <<"host"/utf8>>) andalso (Key /= <<"upgrade"/utf8>>)) + andalso (Key /= <<"connection"/utf8>>)) + andalso (Key /= <<"sec-websocket-key"/utf8>>)) + andalso (Key /= <<"sec-websocket-version"/utf8>>) + end + ), + _pipe@2 = gleam@list:map( + _pipe@1, + fun(Pair@1) -> + {Key@1, Value} = Pair@1, + <<<>/binary, Value/binary>> + end + ), + _pipe@3 = gleam@string:join(_pipe@2, <<"\r\n"/utf8>>), + gleam@string:append(_pipe@3, <<"\r\n"/utf8>>) + end, + Path@1 = case erlang:element(8, Req) of + <<""/utf8>> -> + <<"/"/utf8>>; + + Path -> + Path + end, + Query = begin + _pipe@4 = Req, + _pipe@5 = gleam@http@request:get_query(_pipe@4), + _pipe@6 = gleam@result:map(_pipe@5, fun gleam@uri:query_to_string/1), + (fun(Str) -> case Str of + {ok, <<""/utf8>>} -> + <<""/utf8>>; + + {ok, Str@1} -> + <<"?"/utf8, Str@1/binary>>; + + _ -> + <<""/utf8>> + end end)(_pipe@6) + end, + Port@1 = begin + _pipe@7 = erlang:element(7, Req), + _pipe@8 = gleam@option:map( + _pipe@7, + fun(Port) -> + <<":"/utf8, (erlang:integer_to_binary(Port))/binary>> + end + ), + gleam@option:unwrap(_pipe@8, <<""/utf8>>) + end, + _pipe@9 = gleam@bytes_tree:new(), + _pipe@10 = gleam@bytes_tree:append_string( + _pipe@9, + <<<<<<"GET "/utf8, Path@1/binary>>/binary, Query/binary>>/binary, + " HTTP/1.1\r\n"/utf8>> + ), + _pipe@11 = gleam@bytes_tree:append_string( + _pipe@10, + <<<<<<"host: "/utf8, (erlang:element(6, Req))/binary>>/binary, + Port@1/binary>>/binary, + "\r\n"/utf8>> + ), + _pipe@12 = gleam@bytes_tree:append_string( + _pipe@11, + <<"upgrade: websocket\r\n"/utf8>> + ), + _pipe@13 = gleam@bytes_tree:append_string( + _pipe@12, + <<"connection: upgrade\r\n"/utf8>> + ), + _pipe@14 = gleam@bytes_tree:append_string( + _pipe@13, + <<<<"sec-websocket-key: "/utf8, + (gramps@websocket:make_client_key())/binary>>/binary, + "\r\n"/utf8>> + ), + _pipe@15 = gleam@bytes_tree:append_string( + _pipe@14, + <<"sec-websocket-version: 13\r\n"/utf8>> + ), + _pipe@16 = gleam@bytes_tree:append_string( + _pipe@15, + <<"sec-websocket-extensions: permessage-deflate\r\n"/utf8>> + ), + _pipe@17 = gleam@bytes_tree:append_string(_pipe@16, User_headers), + gleam@bytes_tree:append_string(_pipe@17, <<"\r\n"/utf8>>). + +-file("src/stratus.gleam", 927). +-spec read_body( + stratus@internal@transport:transport(), + stratus@internal@socket:socket(), + integer(), + integer(), + bitstring() +) -> {ok, {bitstring(), bitstring()}} | {error, socket_reason()}. +read_body(Transport, Socket, Timeout, Length, Body) -> + case Body of + <> -> + {ok, {Data, Rest}}; + + _ -> + case stratus@internal@transport:receive_timeout( + Transport, + Socket, + 0, + Timeout + ) of + {ok, Data@1} -> + read_body( + Transport, + Socket, + Timeout, + Length, + <> + ); + + {error, Reason} -> + {error, convert_socket_reason(Reason)} + end + end. + +-file("src/stratus.gleam", 837). +-spec perform_handshake( + gleam@http@request:request(binary()), + stratus@internal@transport:transport(), + integer() +) -> {ok, handshake_response()} | {error, handshake_error()}. +perform_handshake(Req, Transport, Timeout) -> + Certs = case erlang:element(5, Req) of + https -> + case stratus_ffi:ssl_start() of + {ok, _} -> nil; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"perform_handshake"/utf8>>, + line => 844, + value => _assert_fail, + start => 25185, + 'end' => 25217, + pattern_start => 25196, + pattern_end => 25203}) + end, + [{cacerts, public_key:cacerts_get()}, + stratus_ffi:custom_sni_matcher()]; + + http -> + [] + end, + Opts = stratus@internal@socket:convert_options( + lists:append( + [{packets_of, binary}, + {send_timeout, 30000}, + {send_timeout_close, true}, + {reuseaddr, true}, + {nodelay, true}], + [{'receive', pull} | Certs] + ) + ), + Port = gleam@option:lazy_unwrap( + erlang:element(7, Req), + fun() -> case Transport of + ssl -> + 443; + + tcp -> + 80 + end end + ), + logging:log( + debug, + <<<<<<"Making request to "/utf8, (erlang:element(6, Req))/binary>>/binary, + " at "/utf8>>/binary, + (erlang:integer_to_binary(Port))/binary>> + ), + gleam@result:'try'( + gleam@result:map_error( + stratus@internal@transport:connect( + Transport, + unicode:characters_to_list(erlang:element(6, Req)), + Port, + Opts, + Timeout + ), + fun(Err) -> {sock, convert_socket_reason(Err)} end + ), + fun(Socket) -> + Upgrade_req = make_upgrade(Req), + gleam@result:'try'( + gleam@result:map_error( + stratus@internal@transport:send( + Transport, + Socket, + Upgrade_req + ), + fun(Err@1) -> {sock, convert_socket_reason(Err@1)} end + ), + fun(_) -> + logging:log( + debug, + <<"Sent upgrade request, waiting "/utf8, + (erlang:integer_to_binary(Timeout))/binary>> + ), + gleam@result:'try'( + gleam@result:map_error( + stratus@internal@transport:receive_timeout( + Transport, + Socket, + 0, + Timeout + ), + fun(Err@2) -> + {sock, convert_socket_reason(Err@2)} + end + ), + fun(Resp) -> _pipe = Resp, + _pipe@1 = gramps@http:read_response(_pipe), + _pipe@2 = gleam@result:map_error( + _pipe@1, + fun(_) -> {protocol, Resp} end + ), + _pipe@6 = gleam@result:'try'( + _pipe@2, + fun(Pair) -> + {Resp@1, Body} = Pair, + Body_size = begin + _pipe@3 = erlang:element(3, Resp@1), + _pipe@4 = gleam@list:key_find( + _pipe@3, + <<"content-length"/utf8>> + ), + _pipe@5 = gleam@result:'try'( + _pipe@4, + fun gleam_stdlib:parse_int/1 + ), + gleam@result:unwrap(_pipe@5, 0) + end, + case read_body( + Transport, + Socket, + Timeout, + Body_size, + Body + ) of + {ok, {Body@1, Rest}} -> + {ok, + {gleam@http@response:set_body( + Resp@1, + Body@1 + ), + Rest}}; + + {error, Reason} -> + {error, {sock, Reason}} + end + end + ), + gleam@result:'try'( + _pipe@6, + fun(Pair@1) -> + {Resp@2, Buffer} = Pair@1, + case erlang:element(2, Resp@2) of + 101 -> + {ok, + {handshake_response, + Socket, + Resp@2, + Buffer}}; + + _ -> + {error, {upgrade_failed, Resp@2}} + end + end + ) end + ) + end + ) + end + ). + +-file("src/stratus.gleam", 947). +-spec close_contexts( + gleam@option:option(gramps@websocket@compression:compression()) +) -> nil. +close_contexts(Contexts) -> + case Contexts of + {some, Compression} -> + gramps@websocket@compression:close(erlang:element(3, Compression)), + gramps@websocket@compression:close(erlang:element(2, Compression)), + nil; + + _ -> + nil + end. + +-file("src/stratus.gleam", 281). +?DOC( + " This opens the WebSocket connection with the provided `Builder`. It makes\n" + " some assumptions about the request if you do not provide it. It will use\n" + " ports 80 or 443 for `ws` or `wss` respectively.\n" + "\n" + " It will open the connection and perform the WebSocket handshake. If this\n" + " fails, the actor will fail to start with the given reason as a string value.\n" + "\n" + " After that, received messages will be passed to your loop, and you can use\n" + " the helper functions to send messages to the server. The `close` method will\n" + " send a close frame and end the connection.\n" +). +-spec start(builder(any(), HHK)) -> {ok, + gleam@otp@actor:started(gleam@erlang@process:subject(internal_message(HHK)))} | + {error, gleam@otp@actor:start_error()}. +start(Builder) -> + Transport = case erlang:element(5, erlang:element(2, Builder)) of + https -> + ssl; + + _ -> + tcp + end, + Timeout = erlang:element(3, Builder) + 100, + _pipe@12 = gleam@otp@actor:new_with_initialiser( + Timeout, + fun(Subject) -> + Started_selector = gleam@erlang@process:select( + gleam_erlang_ffi:new_selector(), + Subject + ), + Handshake_result = begin + _pipe = perform_handshake( + erlang:element(2, Builder), + Transport, + erlang:element(3, Builder) + ), + gleam@result:map_error( + _pipe, + fun(Reason) -> + Msg = case Reason of + {upgrade_failed, Resp} -> + <<"WebSocket handshake failed with status "/utf8, + (erlang:integer_to_binary( + erlang:element(2, Resp) + ))/binary>>; + + Reason@1 -> + <<"WebSocket handshake failed: "/utf8, + (gleam@string:inspect(Reason@1))/binary>> + end, + logging:log(error, Msg), + Msg + end + ) + end, + gleam@result:'try'( + Handshake_result, + fun(Handshake_response) -> + logging:log(debug, <<"Handshake successful"/utf8>>), + Extensions = begin + _pipe@1 = erlang:element(3, Handshake_response), + _pipe@2 = gleam@http@response:get_header( + _pipe@1, + <<"sec-websocket-extensions"/utf8>> + ), + _pipe@3 = gleam@result:map( + _pipe@2, + fun(_capture) -> + gleam@string:split(_capture, <<"; "/utf8>>) + end + ), + gleam@result:unwrap(_pipe@3, []) + end, + Context_takeovers = gramps@websocket:get_context_takeovers( + Extensions + ), + logging:log(debug, <<"Calling user initializer"/utf8>>), + gleam@result:'try'( + (erlang:element(4, Builder))(), + fun(_use0) -> + {initialised, User_state, User_selector} = _use0, + Selector@1 = case User_selector of + {some, Selector} -> + _pipe@4 = Selector, + _pipe@5 = gleam_erlang_ffi:map_selector( + _pipe@4, + fun(Field@0) -> {user_message, Field@0} end + ), + _pipe@6 = gleam_erlang_ffi:merge_selector( + _pipe@5, + Started_selector + ), + gleam_erlang_ffi:merge_selector( + _pipe@6, + gleam_erlang_ffi:map_selector( + stratus@internal@socket:selector(), + fun(Msg@1) -> + Msg@3 = case Msg@1 of + {ok, Msg@2} -> Msg@2; + _assert_fail -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 331, + value => _assert_fail, + start => 9676, + 'end' => 9700, + pattern_start => 9687, + pattern_end => 9694} + ) + end, + from_socket_message(Msg@3) + end + ) + ); + + _ -> + _pipe@7 = Started_selector, + gleam_erlang_ffi:merge_selector( + _pipe@7, + gleam_erlang_ffi:map_selector( + stratus@internal@socket:selector(), + fun(Msg@4) -> + Msg@6 = case Msg@4 of + {ok, Msg@5} -> Msg@5; + _assert_fail@1 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 340, + value => _assert_fail@1, + start => 9913, + 'end' => 9937, + pattern_start => 9924, + pattern_end => 9931} + ) + end, + from_socket_message(Msg@6) + end + ) + ) + end, + Context = case gramps@websocket:has_deflate( + Extensions + ) of + true -> + {some, + gramps@websocket@compression:init( + Context_takeovers + )}; + + false -> + none + end, + gleam@erlang@process:send( + Subject, + {ready, erlang:element(4, Handshake_response)} + ), + _pipe@8 = {state, + <<>>, + none, + Subject, + erlang:element(2, Handshake_response), + User_state, + Context}, + _pipe@9 = gleam@otp@actor:initialised(_pipe@8), + _pipe@10 = gleam@otp@actor:selecting( + _pipe@9, + Selector@1 + ), + _pipe@11 = gleam@otp@actor:returning( + _pipe@10, + Subject + ), + {ok, _pipe@11} + end + ) + end + ) + end + ), + _pipe@18 = gleam@otp@actor:on_message( + _pipe@12, + fun(State, Message) -> case Message of + {ready, Buffer} -> + _ = case Buffer of + <<>> -> + nil; + + Data -> + gleam@erlang@process:send( + erlang:element(4, State), + {data, Data} + ) + end, + case stratus@internal@transport:set_opts( + Transport, + erlang:element(5, State), + stratus@internal@socket:convert_options( + [{'receive', once}] + ) + ) of + {ok, _} -> nil; + _assert_fail@2 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 370, + value => _assert_fail@2, + start => 10704, + 'end' => 10866, + pattern_start => 10715, + pattern_end => 10720}) + end, + gleam@otp@actor:continue(State); + + {user_message, User_message} -> + Conn = {connection, + erlang:element(5, State), + Transport, + gleam@option:map( + erlang:element(7, State), + fun(Context@1) -> erlang:element(3, Context@1) end + )}, + Res = exception_ffi:rescue( + fun() -> + (erlang:element(5, Builder))( + erlang:element(6, State), + {user, User_message}, + Conn + ) + end + ), + case Res of + {ok, {continue, User_state@1, User_selector@1}} -> + New_state = {state, + erlang:element(2, State), + erlang:element(3, State), + erlang:element(4, State), + erlang:element(5, State), + User_state@1, + erlang:element(7, State)}, + case User_selector@1 of + {some, User_selector@2} -> + Selector@2 = begin + _pipe@13 = User_selector@2, + _pipe@14 = gleam_erlang_ffi:map_selector( + _pipe@13, + fun(Field@0) -> {user_message, Field@0} end + ), + gleam_erlang_ffi:merge_selector( + _pipe@14, + gleam_erlang_ffi:map_selector( + stratus@internal@socket:selector( + + ), + fun(Msg@7) -> + Msg@9 = case Msg@7 of + {ok, Msg@8} -> Msg@8; + _assert_fail@3 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 400, + value => _assert_fail@3, + start => 11755, + 'end' => 11779, + pattern_start => 11766, + pattern_end => 11773} + ) + end, + from_socket_message(Msg@9) + end + ) + ) + end, + _pipe@15 = New_state, + _pipe@16 = gleam@otp@actor:continue( + _pipe@15 + ), + gleam@otp@actor:with_selector( + _pipe@16, + Selector@2 + ); + + _ -> + gleam@otp@actor:continue(New_state) + end; + + {ok, normal_stop} -> + gleam@otp@actor:stop(); + + {ok, {abnormal_stop, Reason@2}} -> + gleam@otp@actor:stop_abnormal(Reason@2); + + {error, Reason@3} -> + logging:log( + error, + <<"Caught error in user handler: "/utf8, + (gleam@string:inspect(Reason@3))/binary>> + ), + gleam@otp@actor:continue(State) + end; + + {err, Reason@4} -> + close_contexts(erlang:element(7, State)), + gleam@otp@actor:stop_abnormal( + gleam@string:inspect(Reason@4) + ); + + {data, Bits} -> + Conn@1 = {connection, + erlang:element(5, State), + Transport, + gleam@option:map( + erlang:element(7, State), + fun(Context@2) -> erlang:element(3, Context@2) end + )}, + {Frames, Rest} = gramps@websocket:decode_many_frames( + gleam@bit_array:append(erlang:element(2, State), Bits), + gleam@option:map( + erlang:element(7, State), + fun(Context@3) -> erlang:element(2, Context@3) end + ), + [] + ), + Frames@1 = gramps@websocket:aggregate_frames( + Frames, + erlang:element(3, State), + [] + ), + _pipe@17 = case Frames@1 of + {error, nil} -> + continue(State); + + {ok, Frames@2} -> + gleam@list:fold_until( + Frames@2, + continue(State), + fun(Acc, Frame) -> + Prev_state@1 = case Acc of + {continue, Prev_state, _} -> Prev_state; + _assert_fail@4 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 444, + value => _assert_fail@4, + start => 13208, + 'end' => 13256, + pattern_start => 13219, + pattern_end => 13250} + ) + end, + case handle_frame( + Builder, + Prev_state@1, + Conn@1, + Frame + ) of + {continue, _, _} = Next -> + {continue, Next}; + + Err -> + {stop, Err} + end + end + ) + end, + (fun(Next@1) -> case Next@1 of + {continue, State@1, Selector@3} -> + case stratus@internal@transport:set_opts( + Transport, + erlang:element(5, State@1), + stratus@internal@socket:convert_options( + [{'receive', once}] + ) + ) of + {ok, _} -> nil; + _assert_fail@5 -> + erlang:error( + #{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"stratus"/utf8>>, + function => <<"start"/utf8>>, + line => 455, + value => _assert_fail@5, + start => 13577, + 'end' => 13769, + pattern_start => 13588, + pattern_end => 13593} + ) + end, + Next@2 = gleam@otp@actor:continue( + {state, + Rest, + erlang:element(3, State@1), + erlang:element(4, State@1), + erlang:element(5, State@1), + erlang:element(6, State@1), + erlang:element(7, State@1)} + ), + case Selector@3 of + {some, Selector@4} -> + gleam@otp@actor:with_selector( + Next@2, + Selector@4 + ); + + _ -> + Next@2 + end; + + normal_stop -> + close_contexts(erlang:element(7, State)), + gleam@otp@actor:stop(); + + {abnormal_stop, Reason@5} -> + close_contexts(erlang:element(7, State)), + gleam@otp@actor:stop_abnormal(Reason@5) + end end)(_pipe@17); + + {closed, Reason@6} -> + logging:log(debug, <<"Received closed frame"/utf8>>), + (erlang:element(6, Builder))( + erlang:element(6, State), + Reason@6 + ), + close_contexts(erlang:element(7, State)), + gleam@otp@actor:stop(); + + shutdown -> + logging:log(debug, <<"Received shutdown messag"/utf8>>), + close_contexts(erlang:element(7, State)), + gleam@otp@actor:stop() + end end + ), + gleam@otp@actor:start(_pipe@18). + +-file("src/stratus.gleam", 495). +-spec supervised(builder(any(), HHT)) -> gleam@otp@supervision:child_specification(gleam@erlang@process:subject(internal_message(HHT))). +supervised(Builder) -> + gleam@otp@supervision:worker(fun() -> start(Builder) end). diff --git a/examples/http_interactions/build/packages/stratus/src/stratus.gleam b/examples/http_interactions/build/packages/stratus/src/stratus.gleam new file mode 100644 index 0000000..e92ea3e --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus.gleam @@ -0,0 +1,956 @@ +import exception +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree.{type BytesTree} +import gleam/crypto +import gleam/erlang/charlist +import gleam/erlang/process.{type Selector, type Subject} +import gleam/http.{Http, Https} +import gleam/http/request.{type Request} +import gleam/http/response.{type Response} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import gleam/otp/supervision +import gleam/result +import gleam/string +import gleam/uri +import gramps/http as gramps_http +import gramps/websocket.{ + BinaryFrame, CloseFrame, Continuation, Control, Data as DataFrame, PingFrame, + PongFrame, TextFrame, +} +import gramps/websocket/compression +import logging +import stratus/internal/socket.{ + type Socket, type SocketMessage, Cacerts, Once, Pull, Receive, +} +import stratus/internal/ssl +import stratus/internal/transport.{type Transport, Ssl, Tcp} + +/// This holds some information needed to communicate with the WebSocket. +pub opaque type Connection { + Connection( + socket: Socket, + transport: Transport, + context: Option(compression.Context), + ) +} + +pub type SocketReason { + SocketClosed + Timeout + Badarg + Terminated + Eaddrinuse + Eaddrnotavail + Eafnosupport + Ealready + Econnaborted + Econnrefused + Econnreset + Edestaddrreq + Ehostdown + Ehostunreach + Einprogress + Eisconn + Emsgsize + Enetdown + Enetunreach + Enopkg + Enoprotoopt + Enotconn + Enotty + Enotsock + Eproto + Eprotonosupport + Eprototype + Esocktnosupport + Etimedout + Ewouldblock + Exbadport + Exbadseq +} + +pub type CustomCloseError { + SocketFail(SocketReason) + InvalidCode +} + +fn convert_socket_reason(reason: socket.SocketReason) -> SocketReason { + case reason { + socket.Badarg -> Badarg + socket.Closed -> SocketClosed + socket.Eaddrinuse -> Eaddrinuse + socket.Eaddrnotavail -> Eaddrnotavail + socket.Eafnosupport -> Eafnosupport + socket.Ealready -> Ealready + socket.Econnaborted -> Econnaborted + socket.Econnrefused -> Econnrefused + socket.Econnreset -> Econnreset + socket.Edestaddrreq -> Edestaddrreq + socket.Ehostdown -> Ehostdown + socket.Ehostunreach -> Ehostunreach + socket.Einprogress -> Einprogress + socket.Eisconn -> Eisconn + socket.Emsgsize -> Emsgsize + socket.Enetdown -> Enetdown + socket.Enetunreach -> Enetunreach + socket.Enopkg -> Enopkg + socket.Enoprotoopt -> Enoprotoopt + socket.Enotconn -> Enotconn + socket.Enotsock -> Enotsock + socket.Enotty -> Enotty + socket.Eproto -> Eproto + socket.Eprotonosupport -> Eprotonosupport + socket.Eprototype -> Eprototype + socket.Esocktnosupport -> Esocktnosupport + socket.Etimedout -> Etimedout + socket.Ewouldblock -> Ewouldblock + socket.Exbadport -> Exbadport + socket.Exbadseq -> Exbadseq + socket.Terminated -> Terminated + socket.Timeout -> Timeout + } +} + +fn from_socket_message(msg: SocketMessage) -> InternalMessage(user_message) { + case msg { + socket.Data(bits) -> Data(bits) + socket.Err(reason) -> Err(convert_socket_reason(reason)) + } +} + +pub opaque type Next(state, user_message) { + Continue(state: state, selector: Option(Selector(user_message))) + NormalStop + AbnormalStop(reason: String) +} + +pub fn continue(state: state) -> Next(state, user_message) { + Continue(state, None) +} + +pub fn with_selector( + next: Next(state, user_message), + selector: Selector(user_message), +) -> Next(state, user_message) { + case next { + Continue(state, _) -> Continue(state, Some(selector)) + _ -> next + } +} + +pub fn stop() -> Next(state, user_message) { + NormalStop +} + +pub fn stop_abnormal(reason: String) -> Next(state, user_message) { + AbnormalStop(reason) +} + +/// These are the messages emitted or received by the underlying process. You +/// should only need to interact with `Message` below. +pub opaque type InternalMessage(user_message) { + UserMessage(user_message) + Err(SocketReason) + Data(BitArray) + Closed(CloseReason) + Ready(BitArray) + Shutdown +} + +/// This is the type of message your handler might receive. +pub type Message(user_message) { + Text(String) + Binary(BitArray) + User(user_message) +} + +pub opaque type Builder(state, user_message) { + Builder( + request: Request(String), + connect_timeout: Int, + init: fn() -> Result(Initialised(state, user_message), String), + loop: fn(state, Message(user_message), Connection) -> + Next(state, user_message), + on_close: fn(state, CloseReason) -> Nil, + ) +} + +// and `on_close`. +/// This creates a builder to set up a WebSocket actor. This will use default +/// values for the connection initialization timeout, and provide an empty +/// function to be called when the server closes the connection. If you want to +/// customize either of those, see the helper functions `with_connect_timeout` +pub fn new( + request req: Request(String), + state state: state, +) -> Builder(state, user_message) { + Builder( + request: req, + connect_timeout: 5000, + init: fn() { Ok(initialised(state)) }, + loop: fn(state, _msg, _conn) { continue(state) }, + on_close: fn(_state, _close_reason) { Nil }, + ) +} + +pub type Initialised(state, user_message) { + Initialised(state: state, selector: Option(Selector(user_message))) +} + +pub fn initialised(state: state) -> Initialised(state, user_message) { + Initialised(state:, selector: None) +} + +pub fn selecting( + initialised: Initialised(state, old_message), + selector: Selector(user_message), +) -> Initialised(state, user_message) { + Initialised(state: initialised.state, selector: Some(selector)) +} + +pub fn new_with_initialiser( + request req: Request(String), + init init: fn() -> Result(Initialised(state, user_message), String), +) -> Builder(state, user_message) { + Builder( + request: req, + connect_timeout: 5000, + init: init, + loop: fn(state, _msg, _conn) { continue(state) }, + on_close: fn(_state, _close_reason) { Nil }, + ) +} + +pub fn on_message( + builder: Builder(state, user_message), + on_message: fn(state, Message(user_message), Connection) -> + Next(state, user_message), +) -> Builder(state, user_message) { + Builder(..builder, loop: on_message) +} + +/// This sets the maximum amount of time you are willing to wait for both +/// connecting to the server and receiving the upgrade response. This means +/// that it may take up to `timeout * 2` to begin sending or receiving messages. +/// This value defaults to 5 seconds. +pub fn with_connect_timeout( + builder: Builder(state, user_message), + timeout: Int, +) -> Builder(state, user_message) { + Builder(..builder, connect_timeout: timeout) +} + +/// You can provide a function to be called when the connection is closed. This +/// function receives the last value for the state of the WebSocket. +/// +/// NOTE: If you manually call `stratus.close`, this function will not be +/// called. I'm unsure right now if this is a bug or working as intended. But +/// you will be in the loop with the state value handy. +pub fn on_close( + builder: Builder(state, user_message), + on_close: fn(state, CloseReason) -> Nil, +) -> Builder(state, user_message) { + Builder(..builder, on_close: on_close) +} + +type State(state, user_message) { + State( + buffer: BitArray, + incomplete: Option(websocket.Frame), + self: Subject(InternalMessage(user_message)), + socket: Socket, + user_state: state, + compression: Option(compression.Compression), + ) +} + +/// This opens the WebSocket connection with the provided `Builder`. It makes +/// some assumptions about the request if you do not provide it. It will use +/// ports 80 or 443 for `ws` or `wss` respectively. +/// +/// It will open the connection and perform the WebSocket handshake. If this +/// fails, the actor will fail to start with the given reason as a string value. +/// +/// After that, received messages will be passed to your loop, and you can use +/// the helper functions to send messages to the server. The `close` method will +/// send a close frame and end the connection. +pub fn start( + builder: Builder(state, user_message), +) -> Result( + actor.Started(Subject(InternalMessage(user_message))), + actor.StartError, +) { + let transport = case builder.request.scheme { + Https -> Ssl + _ -> Tcp + } + + // Adding a small buffer to the timeout here for the rest of the init function + let timeout = builder.connect_timeout + 100 + + actor.new_with_initialiser(timeout, fn(subject) { + let started_selector = process.select(process.new_selector(), subject) + + let handshake_result = + perform_handshake(builder.request, transport, builder.connect_timeout) + |> result.map_error(fn(reason) { + let msg = case reason { + UpgradeFailed(resp) -> + "WebSocket handshake failed with status " + <> int.to_string(resp.status) + reason -> "WebSocket handshake failed: " <> string.inspect(reason) + } + logging.log(logging.Error, msg) + msg + }) + + use handshake_response <- result.try(handshake_result) + logging.log(logging.Debug, "Handshake successful") + + let extensions = + handshake_response.response + |> response.get_header("sec-websocket-extensions") + |> result.map(string.split(_, "; ")) + |> result.unwrap([]) + + let context_takeovers = websocket.get_context_takeovers(extensions) + + logging.log(logging.Debug, "Calling user initializer") + use Initialised(user_state, user_selector) <- result.try(builder.init()) + let selector = case user_selector { + Some(selector) -> { + selector + |> process.map_selector(UserMessage) + |> process.merge_selector(started_selector) + |> process.merge_selector( + process.map_selector(socket.selector(), fn(msg) { + let assert Ok(msg) = msg + from_socket_message(msg) + }), + ) + } + _ -> + started_selector + |> process.merge_selector( + process.map_selector(socket.selector(), fn(msg) { + let assert Ok(msg) = msg + from_socket_message(msg) + }), + ) + } + let context = case websocket.has_deflate(extensions) { + True -> Some(compression.init(context_takeovers)) + False -> None + } + process.send(subject, Ready(handshake_response.buffer)) + State( + buffer: <<>>, + incomplete: None, + self: subject, + socket: handshake_response.socket, + user_state: user_state, + compression: context, + ) + |> actor.initialised + |> actor.selecting(selector) + |> actor.returning(subject) + |> Ok + }) + |> actor.on_message(fn(state, message) { + case message { + Ready(buffer) -> { + let _ = case buffer { + <<>> -> Nil + data -> process.send(state.self, Data(data)) + } + let assert Ok(_) = + transport.set_opts( + transport, + state.socket, + socket.convert_options([Receive(Once)]), + ) + actor.continue(state) + } + UserMessage(user_message) -> { + let conn = + Connection( + state.socket, + transport, + option.map(state.compression, fn(context) { context.deflate }), + ) + let res = + exception.rescue(fn() { + builder.loop(state.user_state, User(user_message), conn) + }) + case res { + // TODO: de-dupe this + Ok(Continue(user_state, user_selector)) -> { + let new_state = State(..state, user_state: user_state) + case user_selector { + Some(user_selector) -> { + let selector = + user_selector + |> process.map_selector(UserMessage) + |> process.merge_selector( + process.map_selector(socket.selector(), fn(msg) { + let assert Ok(msg) = msg + from_socket_message(msg) + }), + ) + new_state + |> actor.continue + |> actor.with_selector(selector) + } + _ -> actor.continue(new_state) + } + } + Ok(NormalStop) -> actor.stop() + Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason) + Error(reason) -> { + logging.log( + logging.Error, + "Caught error in user handler: " <> string.inspect(reason), + ) + actor.continue(state) + } + } + } + Err(reason) -> { + close_contexts(state.compression) + actor.stop_abnormal(string.inspect(reason)) + } + Data(bits) -> { + let conn = + Connection( + state.socket, + transport, + option.map(state.compression, fn(context) { context.deflate }), + ) + let #(frames, rest) = + websocket.decode_many_frames( + bit_array.append(state.buffer, bits), + option.map(state.compression, fn(context) { context.inflate }), + [], + ) + let frames = websocket.aggregate_frames(frames, state.incomplete, []) + case frames { + Error(Nil) -> continue(state) + Ok(frames) -> { + list.fold_until(frames, continue(state), fn(acc, frame) { + let assert Continue(prev_state, _selector) = acc + case handle_frame(builder, prev_state, conn, frame) { + Continue(..) as next -> list.Continue(next) + err -> list.Stop(err) + } + }) + } + } + |> fn(next) { + case next { + Continue(state, selector) -> { + let assert Ok(_) = + transport.set_opts( + transport, + state.socket, + socket.convert_options([Receive(Once)]), + ) + let next = actor.continue(State(..state, buffer: rest)) + case selector { + Some(selector) -> actor.with_selector(next, selector) + _ -> next + } + } + NormalStop -> { + close_contexts(state.compression) + actor.stop() + } + AbnormalStop(reason) -> { + close_contexts(state.compression) + actor.stop_abnormal(reason) + } + } + } + } + Closed(reason) -> { + logging.log(logging.Debug, "Received closed frame") + builder.on_close(state.user_state, reason) + close_contexts(state.compression) + actor.stop() + } + // TODO: handle shutdown better? + Shutdown -> { + logging.log(logging.Debug, "Received shutdown messag") + close_contexts(state.compression) + actor.stop() + } + } + }) + |> actor.start +} + +pub fn supervised(builder: Builder(state, user_message)) { + supervision.worker(fn() { start(builder) }) +} + +fn handle_frame( + builder: Builder(user_state, user_message), + state: State(user_state, user_message), + conn: Connection, + frame: websocket.Frame, +) -> Next(State(user_state, user_message), InternalMessage(user_message)) { + case frame { + DataFrame(TextFrame(payload: data)) -> { + let assert Ok(str) = bit_array.to_string(data) + let res = + exception.rescue(fn() { + builder.loop(state.user_state, Text(str), conn) + }) + case res { + // TODO: de-dupe this + Ok(Continue(user_state, user_selector)) -> { + let new_state = State(..state, user_state: user_state) + case user_selector { + Some(user_selector) -> { + let selector = + user_selector + |> process.map_selector(UserMessage) + |> process.merge_selector( + process.map_selector(socket.selector(), fn(msg) { + let assert Ok(msg) = msg + from_socket_message(msg) + }), + ) + Continue(new_state, Some(selector)) + } + _ -> continue(new_state) + } + } + Ok(NormalStop) -> NormalStop + Ok(AbnormalStop(reason)) -> AbnormalStop(reason) + Error(reason) -> { + logging.log( + logging.Error, + "Caught error in user handler: " <> string.inspect(reason), + ) + continue(state) + } + } + } + DataFrame(BinaryFrame(payload: data)) -> { + let res = + exception.rescue(fn() { + builder.loop(state.user_state, Binary(data), conn) + }) + case res { + // TODO: de-dupe this + Ok(Continue(user_state, user_selector)) -> { + let new_state = State(..state, user_state: user_state) + case user_selector { + Some(user_selector) -> { + let selector = + user_selector + |> process.map_selector(UserMessage) + |> process.merge_selector( + process.map_selector(socket.selector(), fn(msg) { + let assert Ok(msg) = msg + from_socket_message(msg) + }), + ) + Continue(new_state, Some(selector)) + } + _ -> continue(new_state) + } + } + Ok(NormalStop) -> NormalStop + Ok(AbnormalStop(reason)) -> AbnormalStop(reason) + Error(reason) -> { + logging.log( + logging.Error, + "Caught error in user handler: " <> string.inspect(reason), + ) + continue(state) + } + } + } + Control(PingFrame(payload)) -> { + let mask = Some(<<0:unit(8)-size(4)>>) + let frame = websocket.encode_pong_frame(payload, mask) + + let _ = transport.send(conn.transport, conn.socket, frame) + continue(state) + } + Control(PongFrame(..)) -> { + continue(state) + } + Control(CloseFrame(reason)) -> { + logging.log( + logging.Debug, + "WebSocket closing: " <> string.inspect(reason), + ) + builder.on_close(state.user_state, from_websocket_close_reason(reason)) + NormalStop + } + Continuation(..) -> { + continue(state) + } + } +} + +/// The `Subject` returned from `start` is an opaque type. In order to +/// send custom messages to your process, you can do this mapping. +/// +/// For example: +/// ```gleam +/// // using `process.send` +/// MyMessage(some_data) +/// |> stratus.to_user_message +/// |> process.send(stratus_subject, _) +/// // using `process.call` +/// process.call(stratus_subject, fn(subj) { +/// stratus.to_user_message(MyMessage(some_data, subj)) +/// }) +/// ``` +pub fn to_user_message( + user_message: user_message, +) -> InternalMessage(user_message) { + UserMessage(user_message) +} + +/// From within the actor loop, this is how you send a WebSocket text frame. +/// This must be valid UTF-8, so it is a `String`. +pub fn send_text_message( + conn: Connection, + msg: String, +) -> Result(Nil, SocketReason) { + let frame = + websocket.encode_text_frame( + msg, + conn.context, + Some(crypto.strong_random_bytes(4)), + ) + transport.send(conn.transport, conn.socket, frame) + |> result.map_error(convert_socket_reason) +} + +/// From within the actor loop, this is how you send a WebSocket text frame. +pub fn send_binary_message( + conn: Connection, + msg: BitArray, +) -> Result(Nil, SocketReason) { + let frame = + websocket.encode_binary_frame( + msg, + conn.context, + Some(crypto.strong_random_bytes(4)), + ) + transport.send(conn.transport, conn.socket, frame) + |> result.map_error(convert_socket_reason) +} + +/// Send a ping frame with some data. +pub fn send_ping(conn: Connection, data: BitArray) -> Result(Nil, SocketReason) { + let size = bit_array.byte_size(data) + let mask = case size { + 0 -> Some(<<0:size(4)>>) + _n -> Some(crypto.strong_random_bytes(4)) + } + let frame = websocket.encode_ping_frame(data, mask) + transport.send(conn.transport, conn.socket, frame) + |> result.map_error(convert_socket_reason) +} + +pub type CloseReason { + NotProvided + /// Status code: 1000 + Normal(body: BitArray) + /// Status code: 1001 + GoingAway(body: BitArray) + /// Status code: 1002 + ProtocolError(body: BitArray) + /// Status code: 1003 + UnexpectedDataType(body: BitArray) + /// Status code: 1007 + InconsistentDataType(body: BitArray) + /// Status code: 1008 + PolicyViolation(body: BitArray) + /// Status code: 1009 + MessageTooBig(body: BitArray) + /// Status code: 1010 + MissingExtensions(body: BitArray) + /// Status code: 1011 + UnexpectedCondition(body: BitArray) + /// Use [`close_custom`](#close_custom) to send a custom close code. + Custom(CustomCloseReason) +} + +/// Use [`close_custom`](#close_custom) to send a custom close code. +pub opaque type CustomCloseReason { + CustomCloseReason(code: Int, body: BitArray) +} + +pub fn get_custom_code(reason: CustomCloseReason) -> Int { + reason.code +} + +pub fn get_custom_body(reason: CustomCloseReason) -> BitArray { + reason.body +} + +fn from_websocket_close_reason(reason: websocket.CloseReason) -> CloseReason { + case reason { + websocket.NotProvided -> NotProvided + websocket.GoingAway(body:) -> GoingAway(body:) + websocket.InconsistentDataType(body:) -> InconsistentDataType(body:) + websocket.MessageTooBig(body:) -> MessageTooBig(body:) + websocket.MissingExtensions(body:) -> MissingExtensions(body:) + websocket.Normal(body:) -> Normal(body:) + websocket.PolicyViolation(body:) -> PolicyViolation(body:) + websocket.ProtocolError(body:) -> ProtocolError(body:) + websocket.UnexpectedCondition(body:) -> UnexpectedCondition(body:) + websocket.UnexpectedDataType(body:) -> UnexpectedDataType(body:) + websocket.CustomCloseReason(code:, body:) -> + Custom(CustomCloseReason(code:, body:)) + } +} + +fn to_websocket_close_reason(reason: CloseReason) -> websocket.CloseReason { + case reason { + NotProvided -> websocket.NotProvided + GoingAway(body:) -> websocket.GoingAway(body:) + InconsistentDataType(body:) -> websocket.InconsistentDataType(body:) + MessageTooBig(body:) -> websocket.MessageTooBig(body:) + MissingExtensions(body:) -> websocket.MissingExtensions(body:) + Normal(body:) -> websocket.Normal(body:) + PolicyViolation(body:) -> websocket.PolicyViolation(body:) + ProtocolError(body:) -> websocket.ProtocolError(body:) + UnexpectedCondition(body:) -> websocket.UnexpectedCondition(body:) + UnexpectedDataType(body:) -> websocket.UnexpectedDataType(body:) + Custom(CustomCloseReason(code:, body:)) -> + websocket.CustomCloseReason(code:, body:) + } +} + +/// Closes the connection with a custom close code between 1000 and 4999. +pub fn close_custom( + conn: Connection, + code code: Int, + body body: BitArray, +) -> Result(Nil, CustomCloseError) { + use <- bool.guard( + when: code >= 5000 || code < 1000, + return: Error(InvalidCode), + ) + + close(conn, Custom(CustomCloseReason(code:, body:))) + |> result.map_error(SocketFail) +} + +pub fn close( + conn: Connection, + because reason: CloseReason, +) -> Result(Nil, SocketReason) { + let reason = to_websocket_close_reason(reason) + let mask = crypto.strong_random_bytes(4) + let frame = websocket.encode_close_frame(reason, Some(mask)) + + transport.send(conn.transport, conn.socket, frame) + |> result.map_error(convert_socket_reason) +} + +fn make_upgrade(req: Request(String)) -> BytesTree { + let user_headers = case req.headers { + [] -> "" + _ -> + req.headers + |> list.filter(fn(pair) { + let #(key, _value) = pair + key != "host" + && key != "upgrade" + && key != "connection" + && key != "sec-websocket-key" + && key != "sec-websocket-version" + }) + |> list.map(fn(pair) { + let #(key, value) = pair + key <> ": " <> value + }) + |> string.join("\r\n") + |> string.append("\r\n") + } + + let path = case req.path { + "" -> "/" + path -> path + } + + let query = + req + |> request.get_query + |> result.map(uri.query_to_string) + |> fn(str) { + case str { + Ok("") -> "" + Ok(str) -> "?" <> str + _ -> "" + } + } + + let port = + req.port + |> option.map(fn(port) { ":" <> int.to_string(port) }) + |> option.unwrap("") + + bytes_tree.new() + |> bytes_tree.append_string("GET " <> path <> query <> " HTTP/1.1\r\n") + |> bytes_tree.append_string("host: " <> req.host <> port <> "\r\n") + |> bytes_tree.append_string("upgrade: websocket\r\n") + |> bytes_tree.append_string("connection: upgrade\r\n") + |> bytes_tree.append_string( + "sec-websocket-key: " <> websocket.make_client_key() <> "\r\n", + ) + |> bytes_tree.append_string("sec-websocket-version: 13\r\n") + |> bytes_tree.append_string( + "sec-websocket-extensions: permessage-deflate\r\n", + ) + |> bytes_tree.append_string(user_headers) + |> bytes_tree.append_string("\r\n") +} + +pub type HandshakeError { + Sock(SocketReason) + Protocol(BitArray) + UpgradeFailed(Response(BitArray)) +} + +type HandshakeResponse { + HandshakeResponse( + socket: Socket, + response: Response(BitArray), + buffer: BitArray, + ) +} + +fn perform_handshake( + req: Request(String), + transport: Transport, + timeout: Int, +) -> Result(HandshakeResponse, HandshakeError) { + let certs = case req.scheme { + Https -> { + let assert Ok(_ok) = ssl.start() + [Cacerts(socket.get_certs()), socket.get_custom_matcher()] + } + Http -> [] + } + + let opts = + socket.convert_options( + list.append(socket.default_options, [Receive(Pull), ..certs]), + ) + + let port = + option.lazy_unwrap(req.port, fn() { + case transport { + Ssl -> 443 + Tcp -> 80 + } + }) + + logging.log( + logging.Debug, + "Making request to " <> req.host <> " at " <> int.to_string(port), + ) + + use socket <- result.try( + result.map_error( + transport.connect( + transport, + charlist.from_string(req.host), + port, + opts, + timeout, + ), + fn(err) { Sock(convert_socket_reason(err)) }, + ), + ) + + let upgrade_req = make_upgrade(req) + + use _nil <- result.try( + result.map_error(transport.send(transport, socket, upgrade_req), fn(err) { + Sock(convert_socket_reason(err)) + }), + ) + + logging.log( + logging.Debug, + "Sent upgrade request, waiting " <> int.to_string(timeout), + ) + + use resp <- result.try( + result.map_error( + transport.receive_timeout(transport, socket, 0, timeout), + fn(err) { Sock(convert_socket_reason(err)) }, + ), + ) + + resp + |> gramps_http.read_response + |> result.map_error(fn(_err) { Protocol(resp) }) + |> result.try(fn(pair) { + let #(resp, body) = pair + let body_size = + resp.headers + |> list.key_find("content-length") + |> result.try(int.parse) + |> result.unwrap(0) + case read_body(transport, socket, timeout, body_size, body) { + Ok(#(body, rest)) -> { + Ok(#(response.set_body(resp, body), rest)) + } + Error(reason) -> Error(Sock(reason)) + } + }) + |> result.try(fn(pair) { + let #(resp, buffer) = pair + case resp.status { + 101 -> Ok(HandshakeResponse(socket:, response: resp, buffer:)) + _ -> Error(UpgradeFailed(resp)) + } + }) +} + +fn read_body( + transport: Transport, + socket: Socket, + timeout: Int, + length: Int, + body: BitArray, +) -> Result(#(BitArray, BitArray), SocketReason) { + case body { + <> -> Ok(#(data, rest)) + _ -> { + case transport.receive_timeout(transport, socket, 0, timeout) { + Ok(data) -> { + read_body(transport, socket, timeout, length, <>) + } + Error(reason) -> Error(convert_socket_reason(reason)) + } + } + } +} + +fn close_contexts(contexts: Option(compression.Compression)) -> Nil { + case contexts { + Some(compression) -> { + compression.close(compression.deflate) + compression.close(compression.inflate) + Nil + } + _ -> Nil + } +} diff --git a/examples/http_interactions/build/packages/stratus/src/stratus/internal/socket.gleam b/examples/http_interactions/build/packages/stratus/src/stratus/internal/socket.gleam new file mode 100644 index 0000000..9ea7506 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus/internal/socket.gleam @@ -0,0 +1,174 @@ +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/erlang/atom.{type Atom} +import gleam/erlang/process.{type Selector} +import gleam/list + +pub type Socket + +pub type SocketReason { + Closed + Timeout + Badarg + Terminated + Eaddrinuse + Eaddrnotavail + Eafnosupport + Ealready + Econnaborted + Econnrefused + Econnreset + Edestaddrreq + Ehostdown + Ehostunreach + Einprogress + Eisconn + Emsgsize + Enetdown + Enetunreach + Enopkg + Enoprotoopt + Enotconn + Enotty + Enotsock + Eproto + Eprotonosupport + Eprototype + Esocktnosupport + Etimedout + Ewouldblock + Exbadport + Exbadseq +} + +pub type TcpOption = + #(Atom, Dynamic) + +pub type ReceiveMode { + Count(Int) + Once + Pull + All +} + +pub type PacketType { + Binary + List +} + +pub type Options { + Receive(ReceiveMode) + PacketsOf(PacketType) + SendTimeout(Int) + SendTimeoutClose(Bool) + Reuseaddr(Bool) + Nodelay(Bool) + Cacerts(Dynamic) + CustomizeHostnameCheck(Dynamic) +} + +pub const default_options = [ + PacketsOf(Binary), + SendTimeout(30_000), + SendTimeoutClose(True), + Reuseaddr(True), + Nodelay(True), +] + +@external(erlang, "gleam@function", "identity") +fn from(value: a) -> Dynamic + +pub fn convert_options(options: List(Options)) -> List(TcpOption) { + let active = atom.create("active") + list.map(options, fn(opt) { + case opt { + Receive(Count(count)) -> #(active, dynamic.int(count)) + Receive(Once) -> #(active, from(atom.create("once"))) + Receive(Pull) -> #(active, dynamic.bool(False)) + Receive(All) -> #(active, dynamic.bool(True)) + PacketsOf(Binary) -> #(atom.create("mode"), from(Binary)) + PacketsOf(List) -> #(atom.create("mode"), from(List)) + Cacerts(data) -> #(atom.create("cacerts"), data) + Nodelay(bool) -> #(atom.create("nodelay"), dynamic.bool(bool)) + Reuseaddr(bool) -> #(atom.create("reuseaddr"), dynamic.bool(bool)) + SendTimeout(int) -> #(atom.create("send_timeout"), dynamic.int(int)) + SendTimeoutClose(bool) -> #( + atom.create("send_timeout_close"), + dynamic.bool(bool), + ) + CustomizeHostnameCheck(funcs) -> #( + atom.create("customize_hostname_check"), + funcs, + ) + } + }) +} + +pub type Shutdown { + Read + Write + ReadWrite +} + +pub type SocketMessage { + Data(BitArray) + Err(SocketReason) +} + +type ErlangSocketMessage { + Ssl + SslClosed + SslError + Tcp + TcpClosed + TcpError +} + +pub fn selector() -> Selector(Result(SocketMessage, List(decode.DecodeError))) { + process.new_selector() + |> process.select_record(Tcp, 2, fn(data) { + { + use msg <- decode.field(2, decode.bit_array) + decode.success(Data(msg)) + } + |> decode.run(data, _) + }) + |> process.select_record(Ssl, 2, fn(data) { + { + use msg <- decode.field(2, decode.bit_array) + decode.success(Data(msg)) + } + |> decode.run(data, _) + }) + |> process.select_record(SslClosed, 1, fn(_socket) { Ok(Err(Closed)) }) + |> process.select_record(TcpClosed, 1, fn(_socket) { Ok(Err(Closed)) }) + |> process.select_record(TcpError, 2, fn(data) { + { + use reason <- decode.field(2, atom.decoder()) + case parse_known_socket_reason(reason) { + Ok(reason) -> decode.success(Err(reason)) + Error(_reason) -> decode.failure(Err(Badarg), "SocketReason") + } + } + |> decode.run(data, _) + }) + |> process.select_record(SslError, 2, fn(data) { + { + use reason <- decode.field(2, atom.decoder()) + case parse_known_socket_reason(reason) { + Ok(reason) -> decode.success(Err(reason)) + Error(_reason) -> decode.failure(Err(Badarg), "SocketReason") + } + } + |> decode.run(data, _) + }) +} + +@external(erlang, "public_key", "cacerts_get") +pub fn get_certs() -> Dynamic + +@external(erlang, "stratus_ffi", "custom_sni_matcher") +pub fn get_custom_matcher() -> Options + +@external(erlang, "stratus_ffi", "parse_known_socket_reason") +fn parse_known_socket_reason(reason: Atom) -> Result(SocketReason, Dynamic) diff --git a/examples/http_interactions/build/packages/stratus/src/stratus/internal/ssl.gleam b/examples/http_interactions/build/packages/stratus/src/stratus/internal/ssl.gleam new file mode 100644 index 0000000..2d897af --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus/internal/ssl.gleam @@ -0,0 +1,45 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/charlist.{type Charlist} +import gleam/erlang/process.{type Pid} +import stratus/internal/socket.{ + type Shutdown, type Socket, type SocketReason, type TcpOption, +} + +@external(erlang, "ssl", "connect") +pub fn connect( + address: Charlist, + port: Int, + options: List(TcpOption), + timeout: Int, +) -> Result(Socket, SocketReason) + +@external(erlang, "stratus_ffi", "ssl_shutdown") +pub fn shutdown(socket: Socket, how: Shutdown) -> Result(Nil, SocketReason) + +@external(erlang, "stratus_ffi", "ssl_send") +pub fn send(socket: Socket, packet: BytesTree) -> Result(Nil, SocketReason) + +@external(erlang, "ssl", "recv") +pub fn receive(socket: Socket, length: Int) -> Result(BitArray, SocketReason) + +@external(erlang, "ssl", "recv") +pub fn receive_timeout( + socket: Socket, + length: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) + +@external(erlang, "stratus_ffi", "ssl_set_opts") +pub fn set_opts( + socket: Socket, + opts: List(TcpOption), +) -> Result(Nil, SocketReason) + +@external(erlang, "stratus_ffi", "ssl_start") +pub fn start() -> Result(Nil, Nil) + +@external(erlang, "stratus_ffi", "ssl_controlling_process") +pub fn controlling_process( + socket: Socket, + new_owner: Pid, +) -> Result(Nil, SocketReason) diff --git a/examples/http_interactions/build/packages/stratus/src/stratus/internal/tcp.gleam b/examples/http_interactions/build/packages/stratus/src/stratus/internal/tcp.gleam new file mode 100644 index 0000000..8d97705 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus/internal/tcp.gleam @@ -0,0 +1,42 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/charlist.{type Charlist} +import gleam/erlang/process.{type Pid} +import stratus/internal/socket.{ + type Shutdown, type Socket, type SocketReason, type TcpOption, +} + +@external(erlang, "gen_tcp", "connect") +pub fn connect( + address: Charlist, + port: Int, + options: List(TcpOption), + timeout: Int, +) -> Result(Socket, SocketReason) + +@external(erlang, "stratus_ffi", "tcp_shutdown") +pub fn shutdown(socket: Socket, how: Shutdown) -> Result(Nil, SocketReason) + +@external(erlang, "stratus_ffi", "tcp_send") +pub fn send(socket: Socket, packet: BytesTree) -> Result(Nil, SocketReason) + +@external(erlang, "gen_tcp", "recv") +pub fn receive(socket: Socket, length: Int) -> Result(BitArray, SocketReason) + +@external(erlang, "gen_tcp", "recv") +pub fn receive_timeout( + socket: Socket, + length: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) + +@external(erlang, "stratus_ffi", "tcp_set_opts") +pub fn set_opts( + socket: Socket, + opts: List(TcpOption), +) -> Result(Nil, SocketReason) + +@external(erlang, "stratus_ffi", "tcp_controlling_process") +pub fn controlling_process( + socket: Socket, + new_owner: Pid, +) -> Result(Nil, SocketReason) diff --git a/examples/http_interactions/build/packages/stratus/src/stratus/internal/transport.gleam b/examples/http_interactions/build/packages/stratus/src/stratus/internal/transport.gleam new file mode 100644 index 0000000..a9a939b --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus/internal/transport.gleam @@ -0,0 +1,81 @@ +import gleam/bytes_tree.{type BytesTree} +import gleam/erlang/charlist.{type Charlist} +import stratus/internal/socket.{ + type Shutdown, type Socket, type SocketReason, type TcpOption, +} +import stratus/internal/ssl +import stratus/internal/tcp + +pub type Transport { + Tcp + Ssl +} + +pub fn connect( + transport: Transport, + host: Charlist, + port: Int, + options: List(TcpOption), + timeout: Int, +) -> Result(Socket, SocketReason) { + case transport { + Ssl -> ssl.connect(host, port, options, timeout) + Tcp -> tcp.connect(host, port, options, timeout) + } +} + +pub fn send( + transport: Transport, + socket: Socket, + data: BytesTree, +) -> Result(Nil, SocketReason) { + case transport { + Ssl -> ssl.send(socket, data) + Tcp -> tcp.send(socket, data) + } +} + +pub fn receive( + transport: Transport, + socket: Socket, + length: Int, +) -> Result(BitArray, SocketReason) { + case transport { + Ssl -> ssl.receive(socket, length) + Tcp -> tcp.receive(socket, length) + } +} + +pub fn receive_timeout( + transport: Transport, + socket: Socket, + length: Int, + timeout: Int, +) -> Result(BitArray, SocketReason) { + case transport { + Ssl -> ssl.receive_timeout(socket, length, timeout) + Tcp -> tcp.receive_timeout(socket, length, timeout) + } +} + +pub fn shutdown( + transport: Transport, + socket: Socket, + how: Shutdown, +) -> Result(Nil, SocketReason) { + case transport { + Ssl -> ssl.shutdown(socket, how) + Tcp -> tcp.shutdown(socket, how) + } +} + +pub fn set_opts( + transport: Transport, + socket: Socket, + opts: List(TcpOption), +) -> Result(Nil, SocketReason) { + case transport { + Tcp -> tcp.set_opts(socket, opts) + Ssl -> ssl.set_opts(socket, opts) + } +} diff --git a/examples/http_interactions/build/packages/stratus/src/stratus@internal@socket.erl b/examples/http_interactions/build/packages/stratus/src/stratus@internal@socket.erl new file mode 100644 index 0000000..59a5c4f --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus@internal@socket.erl @@ -0,0 +1,243 @@ +-module(stratus@internal@socket). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/stratus/internal/socket.gleam"). +-export([convert_options/1, get_certs/0, get_custom_matcher/0, selector/0]). +-export_type([socket/0, socket_reason/0, receive_mode/0, packet_type/0, options/0, shutdown/0, socket_message/0, erlang_socket_message/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type socket() :: any(). + +-type socket_reason() :: closed | + timeout | + badarg | + terminated | + eaddrinuse | + eaddrnotavail | + eafnosupport | + ealready | + econnaborted | + econnrefused | + econnreset | + edestaddrreq | + ehostdown | + ehostunreach | + einprogress | + eisconn | + emsgsize | + enetdown | + enetunreach | + enopkg | + enoprotoopt | + enotconn | + enotty | + enotsock | + eproto | + eprotonosupport | + eprototype | + esocktnosupport | + etimedout | + ewouldblock | + exbadport | + exbadseq. + +-type receive_mode() :: {count, integer()} | once | pull | all. + +-type packet_type() :: binary | list. + +-type options() :: {'receive', receive_mode()} | + {packets_of, packet_type()} | + {send_timeout, integer()} | + {send_timeout_close, boolean()} | + {reuseaddr, boolean()} | + {nodelay, boolean()} | + {cacerts, gleam@dynamic:dynamic_()} | + {customize_hostname_check, gleam@dynamic:dynamic_()}. + +-type shutdown() :: read | write | read_write. + +-type socket_message() :: {data, bitstring()} | {err, socket_reason()}. + +-type erlang_socket_message() :: ssl | + ssl_closed | + ssl_error | + tcp | + tcp_closed | + tcp_error. + +-file("src/stratus/internal/socket.gleam", 81). +?DOC(false). +-spec convert_options(list(options())) -> list({gleam@erlang@atom:atom_(), + gleam@dynamic:dynamic_()}). +convert_options(Options) -> + Active = erlang:binary_to_atom(<<"active"/utf8>>), + gleam@list:map(Options, fun(Opt) -> case Opt of + {'receive', {count, Count}} -> + {Active, gleam_stdlib:identity(Count)}; + + {'receive', once} -> + {Active, + gleam@function:identity( + erlang:binary_to_atom(<<"once"/utf8>>) + )}; + + {'receive', pull} -> + {Active, gleam_stdlib:identity(false)}; + + {'receive', all} -> + {Active, gleam_stdlib:identity(true)}; + + {packets_of, binary} -> + {erlang:binary_to_atom(<<"mode"/utf8>>), + gleam@function:identity(binary)}; + + {packets_of, list} -> + {erlang:binary_to_atom(<<"mode"/utf8>>), + gleam@function:identity(list)}; + + {cacerts, Data} -> + {erlang:binary_to_atom(<<"cacerts"/utf8>>), Data}; + + {nodelay, Bool} -> + {erlang:binary_to_atom(<<"nodelay"/utf8>>), + gleam_stdlib:identity(Bool)}; + + {reuseaddr, Bool@1} -> + {erlang:binary_to_atom(<<"reuseaddr"/utf8>>), + gleam_stdlib:identity(Bool@1)}; + + {send_timeout, Int} -> + {erlang:binary_to_atom(<<"send_timeout"/utf8>>), + gleam_stdlib:identity(Int)}; + + {send_timeout_close, Bool@2} -> + {erlang:binary_to_atom(<<"send_timeout_close"/utf8>>), + gleam_stdlib:identity(Bool@2)}; + + {customize_hostname_check, Funcs} -> + {erlang:binary_to_atom(<<"customize_hostname_check"/utf8>>), + Funcs} + end end). + +-file("src/stratus/internal/socket.gleam", 168). +?DOC(false). +-spec get_certs() -> gleam@dynamic:dynamic_(). +get_certs() -> + public_key:cacerts_get(). + +-file("src/stratus/internal/socket.gleam", 171). +?DOC(false). +-spec get_custom_matcher() -> options(). +get_custom_matcher() -> + stratus_ffi:custom_sni_matcher(). + +-file("src/stratus/internal/socket.gleam", 127). +?DOC(false). +-spec selector() -> gleam@erlang@process:selector({ok, socket_message()} | + {error, list(gleam@dynamic@decode:decode_error())}). +selector() -> + _pipe = gleam_erlang_ffi:new_selector(), + _pipe@2 = gleam@erlang@process:select_record( + _pipe, + tcp, + 2, + fun(Data) -> + _pipe@1 = begin + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_bit_array/1}, + fun(Msg) -> gleam@dynamic@decode:success({data, Msg}) end + ) + end, + gleam@dynamic@decode:run(Data, _pipe@1) + end + ), + _pipe@4 = gleam@erlang@process:select_record( + _pipe@2, + ssl, + 2, + fun(Data@1) -> + _pipe@3 = begin + gleam@dynamic@decode:field( + 2, + {decoder, fun gleam@dynamic@decode:decode_bit_array/1}, + fun(Msg@1) -> + gleam@dynamic@decode:success({data, Msg@1}) + end + ) + end, + gleam@dynamic@decode:run(Data@1, _pipe@3) + end + ), + _pipe@5 = gleam@erlang@process:select_record( + _pipe@4, + ssl_closed, + 1, + fun(_) -> {ok, {err, closed}} end + ), + _pipe@6 = gleam@erlang@process:select_record( + _pipe@5, + tcp_closed, + 1, + fun(_) -> {ok, {err, closed}} end + ), + _pipe@8 = gleam@erlang@process:select_record( + _pipe@6, + tcp_error, + 2, + fun(Data@2) -> + _pipe@7 = begin + gleam@dynamic@decode:field( + 2, + gleam@erlang@atom:decoder(), + fun(Reason) -> + case stratus_ffi:parse_known_socket_reason(Reason) of + {ok, Reason@1} -> + gleam@dynamic@decode:success({err, Reason@1}); + + {error, _} -> + gleam@dynamic@decode:failure( + {err, badarg}, + <<"SocketReason"/utf8>> + ) + end + end + ) + end, + gleam@dynamic@decode:run(Data@2, _pipe@7) + end + ), + gleam@erlang@process:select_record( + _pipe@8, + ssl_error, + 2, + fun(Data@3) -> + _pipe@9 = begin + gleam@dynamic@decode:field( + 2, + gleam@erlang@atom:decoder(), + fun(Reason@2) -> + case stratus_ffi:parse_known_socket_reason(Reason@2) of + {ok, Reason@3} -> + gleam@dynamic@decode:success({err, Reason@3}); + + {error, _} -> + gleam@dynamic@decode:failure( + {err, badarg}, + <<"SocketReason"/utf8>> + ) + end + end + ) + end, + gleam@dynamic@decode:run(Data@3, _pipe@9) + end + ). diff --git a/examples/http_interactions/build/packages/stratus/src/stratus@internal@ssl.erl b/examples/http_interactions/build/packages/stratus/src/stratus@internal@ssl.erl new file mode 100644 index 0000000..37ea1cd --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus@internal@ssl.erl @@ -0,0 +1,83 @@ +-module(stratus@internal@ssl). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/stratus/internal/ssl.gleam"). +-export([connect/4, shutdown/2, send/2, 'receive'/2, receive_timeout/3, set_opts/2, start/0, controlling_process/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/stratus/internal/ssl.gleam", 9). +?DOC(false). +-spec connect( + gleam@erlang@charlist:charlist(), + integer(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}), + integer() +) -> {ok, stratus@internal@socket:socket()} | + {error, stratus@internal@socket:socket_reason()}. +connect(Address, Port, Options, Timeout) -> + ssl:connect(Address, Port, Options, Timeout). + +-file("src/stratus/internal/ssl.gleam", 17). +?DOC(false). +-spec shutdown( + stratus@internal@socket:socket(), + stratus@internal@socket:shutdown() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +shutdown(Socket, How) -> + stratus_ffi:ssl_shutdown(Socket, How). + +-file("src/stratus/internal/ssl.gleam", 20). +?DOC(false). +-spec send(stratus@internal@socket:socket(), gleam@bytes_tree:bytes_tree()) -> {ok, + nil} | + {error, stratus@internal@socket:socket_reason()}. +send(Socket, Packet) -> + stratus_ffi:ssl_send(Socket, Packet). + +-file("src/stratus/internal/ssl.gleam", 23). +?DOC(false). +-spec 'receive'(stratus@internal@socket:socket(), integer()) -> {ok, + bitstring()} | + {error, stratus@internal@socket:socket_reason()}. +'receive'(Socket, Length) -> + ssl:recv(Socket, Length). + +-file("src/stratus/internal/ssl.gleam", 26). +?DOC(false). +-spec receive_timeout(stratus@internal@socket:socket(), integer(), integer()) -> {ok, + bitstring()} | + {error, stratus@internal@socket:socket_reason()}. +receive_timeout(Socket, Length, Timeout) -> + ssl:recv(Socket, Length, Timeout). + +-file("src/stratus/internal/ssl.gleam", 33). +?DOC(false). +-spec set_opts( + stratus@internal@socket:socket(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}) +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +set_opts(Socket, Opts) -> + stratus_ffi:ssl_set_opts(Socket, Opts). + +-file("src/stratus/internal/ssl.gleam", 39). +?DOC(false). +-spec start() -> {ok, nil} | {error, nil}. +start() -> + stratus_ffi:ssl_start(). + +-file("src/stratus/internal/ssl.gleam", 42). +?DOC(false). +-spec controlling_process( + stratus@internal@socket:socket(), + gleam@erlang@process:pid_() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +controlling_process(Socket, New_owner) -> + stratus_ffi:ssl_controlling_process(Socket, New_owner). diff --git a/examples/http_interactions/build/packages/stratus/src/stratus@internal@tcp.erl b/examples/http_interactions/build/packages/stratus/src/stratus@internal@tcp.erl new file mode 100644 index 0000000..7f8d9f2 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus@internal@tcp.erl @@ -0,0 +1,77 @@ +-module(stratus@internal@tcp). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/stratus/internal/tcp.gleam"). +-export([connect/4, shutdown/2, send/2, 'receive'/2, receive_timeout/3, set_opts/2, controlling_process/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-file("src/stratus/internal/tcp.gleam", 9). +?DOC(false). +-spec connect( + gleam@erlang@charlist:charlist(), + integer(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}), + integer() +) -> {ok, stratus@internal@socket:socket()} | + {error, stratus@internal@socket:socket_reason()}. +connect(Address, Port, Options, Timeout) -> + gen_tcp:connect(Address, Port, Options, Timeout). + +-file("src/stratus/internal/tcp.gleam", 17). +?DOC(false). +-spec shutdown( + stratus@internal@socket:socket(), + stratus@internal@socket:shutdown() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +shutdown(Socket, How) -> + stratus_ffi:tcp_shutdown(Socket, How). + +-file("src/stratus/internal/tcp.gleam", 20). +?DOC(false). +-spec send(stratus@internal@socket:socket(), gleam@bytes_tree:bytes_tree()) -> {ok, + nil} | + {error, stratus@internal@socket:socket_reason()}. +send(Socket, Packet) -> + stratus_ffi:tcp_send(Socket, Packet). + +-file("src/stratus/internal/tcp.gleam", 23). +?DOC(false). +-spec 'receive'(stratus@internal@socket:socket(), integer()) -> {ok, + bitstring()} | + {error, stratus@internal@socket:socket_reason()}. +'receive'(Socket, Length) -> + gen_tcp:recv(Socket, Length). + +-file("src/stratus/internal/tcp.gleam", 26). +?DOC(false). +-spec receive_timeout(stratus@internal@socket:socket(), integer(), integer()) -> {ok, + bitstring()} | + {error, stratus@internal@socket:socket_reason()}. +receive_timeout(Socket, Length, Timeout) -> + gen_tcp:recv(Socket, Length, Timeout). + +-file("src/stratus/internal/tcp.gleam", 33). +?DOC(false). +-spec set_opts( + stratus@internal@socket:socket(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}) +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +set_opts(Socket, Opts) -> + stratus_ffi:tcp_set_opts(Socket, Opts). + +-file("src/stratus/internal/tcp.gleam", 39). +?DOC(false). +-spec controlling_process( + stratus@internal@socket:socket(), + gleam@erlang@process:pid_() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +controlling_process(Socket, New_owner) -> + stratus_ffi:tcp_controlling_process(Socket, New_owner). diff --git a/examples/http_interactions/build/packages/stratus/src/stratus@internal@transport.erl b/examples/http_interactions/build/packages/stratus/src/stratus@internal@transport.erl new file mode 100644 index 0000000..7704ff8 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus@internal@transport.erl @@ -0,0 +1,115 @@ +-module(stratus@internal@transport). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/stratus/internal/transport.gleam"). +-export([connect/5, send/3, 'receive'/3, receive_timeout/4, shutdown/3, set_opts/3]). +-export_type([transport/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type transport() :: tcp | ssl. + +-file("src/stratus/internal/transport.gleam", 14). +?DOC(false). +-spec connect( + transport(), + gleam@erlang@charlist:charlist(), + integer(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}), + integer() +) -> {ok, stratus@internal@socket:socket()} | + {error, stratus@internal@socket:socket_reason()}. +connect(Transport, Host, Port, Options, Timeout) -> + case Transport of + ssl -> + ssl:connect(Host, Port, Options, Timeout); + + tcp -> + gen_tcp:connect(Host, Port, Options, Timeout) + end. + +-file("src/stratus/internal/transport.gleam", 27). +?DOC(false). +-spec send( + transport(), + stratus@internal@socket:socket(), + gleam@bytes_tree:bytes_tree() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +send(Transport, Socket, Data) -> + case Transport of + ssl -> + stratus_ffi:ssl_send(Socket, Data); + + tcp -> + stratus_ffi:tcp_send(Socket, Data) + end. + +-file("src/stratus/internal/transport.gleam", 38). +?DOC(false). +-spec 'receive'(transport(), stratus@internal@socket:socket(), integer()) -> {ok, + bitstring()} | + {error, stratus@internal@socket:socket_reason()}. +'receive'(Transport, Socket, Length) -> + case Transport of + ssl -> + ssl:recv(Socket, Length); + + tcp -> + gen_tcp:recv(Socket, Length) + end. + +-file("src/stratus/internal/transport.gleam", 49). +?DOC(false). +-spec receive_timeout( + transport(), + stratus@internal@socket:socket(), + integer(), + integer() +) -> {ok, bitstring()} | {error, stratus@internal@socket:socket_reason()}. +receive_timeout(Transport, Socket, Length, Timeout) -> + case Transport of + ssl -> + ssl:recv(Socket, Length, Timeout); + + tcp -> + gen_tcp:recv(Socket, Length, Timeout) + end. + +-file("src/stratus/internal/transport.gleam", 61). +?DOC(false). +-spec shutdown( + transport(), + stratus@internal@socket:socket(), + stratus@internal@socket:shutdown() +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +shutdown(Transport, Socket, How) -> + case Transport of + ssl -> + stratus_ffi:ssl_shutdown(Socket, How); + + tcp -> + stratus_ffi:tcp_shutdown(Socket, How) + end. + +-file("src/stratus/internal/transport.gleam", 72). +?DOC(false). +-spec set_opts( + transport(), + stratus@internal@socket:socket(), + list({gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()}) +) -> {ok, nil} | {error, stratus@internal@socket:socket_reason()}. +set_opts(Transport, Socket, Opts) -> + case Transport of + tcp -> + stratus_ffi:tcp_set_opts(Socket, Opts); + + ssl -> + stratus_ffi:ssl_set_opts(Socket, Opts) + end. diff --git a/examples/http_interactions/build/packages/stratus/src/stratus_ffi.erl b/examples/http_interactions/build/packages/stratus/src/stratus_ffi.erl new file mode 100644 index 0000000..515cac1 --- /dev/null +++ b/examples/http_interactions/build/packages/stratus/src/stratus_ffi.erl @@ -0,0 +1,116 @@ +-module(stratus_ffi). + +-export([tcp_shutdown/2, tcp_send/2, ssl_shutdown/2, ssl_send/2, tcp_set_opts/2, + ssl_set_opts/2, ssl_start/0, custom_sni_matcher/0, + ssl_controlling_process/2, tcp_controlling_process/2, + parse_known_socket_reason/1 ]). + +tcp_shutdown(Socket, How) -> + case gen_tcp:shutdown(Socket, How) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +tcp_send(Socket, Packet) -> + case gen_tcp:send(Socket, Packet) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +ssl_shutdown(Socket, How) -> + case ssl:shutdown(Socket, How) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +ssl_send(Socket, Packet) -> + case ssl:send(Socket, Packet) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +tcp_set_opts(Socket, Opts) -> + case inet:setopts(Socket, Opts) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +ssl_set_opts(Socket, Opts) -> + case ssl:setopts(Socket, Opts) of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +ssl_start() -> + case ssl:start() of + ok -> + {ok, nil}; + {error, Reason} -> + {error, Reason} + end. + +% Thank you! https://github.com/erlang/otp/issues/4321 +custom_sni_matcher() -> + {customize_hostname_check, + [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}. + +ssl_controlling_process(Socket, NewOwner) -> + case ssl:controlling_process(Socket, NewOwner) of + ok -> {ok, nil}; + Error -> Error + end. + +tcp_controlling_process(Socket, NewOwner) -> + case gen_tcp:controlling_process(Socket, NewOwner) of + ok -> {ok, nil}; + Error -> Error + end. + +parse_known_socket_reason(Reason) -> + case Reason of + closed -> {ok, closed}; + timeout -> {ok, timeout}; + badarg -> {ok, badarg}; + terminated -> {ok, terminated}; + eaddrinuse -> {ok, eaddrinuse}; + eaddrnotavail -> {ok, eaddrnotavail}; + eafnosupport -> {ok, eafnosupport}; + ealready -> {ok, ealready}; + econnaborted -> {ok, econnaborted}; + econnrefused -> {ok, econnrefused}; + econnreset -> {ok, econnreset}; + edestaddrreq -> {ok, edestaddrreq}; + ehostdown -> {ok, ehostdown}; + ehostunreach -> {ok, ehostunreach}; + einprogress -> {ok, einprogress}; + eisconn -> {ok, eisconn}; + emsgsize -> {ok, emsgsize}; + enetdown -> {ok, enetdown}; + enetunreach -> {ok, enetunreach}; + enopkg -> {ok, enopkg}; + enoprotoopt -> {ok, enoprotoopt}; + enotconn -> {ok, enotconn}; + enotty -> {ok, enotty}; + enotsock -> {ok, enotsock}; + eproto -> {ok, eproto}; + eprotonosupport -> {ok, eprotonosupport}; + eprototype -> {ok, eprototype}; + esocktnosupport -> {ok, esocktnosupport}; + etimedout -> {ok, etimedout}; + ewouldblock -> {ok, ewouldblock}; + exbadport -> {ok, exbadport}; + exbadseq -> {ok, exbadseq}; + Unknown -> {error, Unknown} + end. diff --git a/examples/http_interactions/build/packages/wisp/LICENCE b/examples/http_interactions/build/packages/wisp/LICENCE new file mode 100644 index 0000000..235a402 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/LICENCE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2023, Louis Pilfold . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/examples/http_interactions/build/packages/wisp/README.md b/examples/http_interactions/build/packages/wisp/README.md new file mode 100644 index 0000000..33e4315 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/README.md @@ -0,0 +1,66 @@ +![Wisp](https://github.com/lpil/wisp/blob/main/docs/images/cover.png?raw=true) + +[![Package Version](https://img.shields.io/hexpm/v/wisp)](https://hex.pm/packages/wisp) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/wisp/) + +Wisp is a practical Gleam web framework for rapid development and easy maintenance. +We worry about the hassle of web development, and you focus on writing your +application. + +It is based around two concepts: handlers and middleware. + +# Handlers + +A handler is a function that takes a HTTP request and returns a HTTP +response. A handler may also take other arguments, such as a "context" type +defined in your application which may hold other state such as a database +connection or user session. + +```gleam +import wisp.{type Request, type Response} + +pub type Context { + Context(secret: String) +} + +pub fn handle_request(request: Request, context: Context) -> Response { + wisp.ok() +} +``` + +# Middleware + +A middleware is a function that takes a response returning function as its +last argument, and itself returns a response. As with handlers both +middleware and the functions they take as an argument may take other +arguments. + +Middleware can be applied in a handler with Gleam's `use` syntax. Here the +`log_request` middleware is used to log a message for each HTTP request +handled, and the `serve_static` middleware is used to serve static files +such as images and CSS. + +```gleam +import wisp.{type Request, type Response} + +pub fn handle_request(request: Request) -> Response { + use <- wisp.log_request(request) + use <- wisp.serve_static(request, under: "/static", from: "/public") + wisp.ok() +} +``` + +# Learning Wisp + +The [Wisp examples](https://github.com/gleam-wisp/wisp/tree/main/examples) +are a good place to start. They cover various scenarios and include comments and +tests. + +API documentation is available on [HexDocs](https://hexdocs.pm/wisp/). + +# Wisp applications + +These open source Wisp applications may be useful examples. + +- [https://packages.gleam.run/](https://github.com/gleam-lang/packages): A HTML + serving application that uses an SQLite + LiteFS database, deployed to Fly.io. diff --git a/examples/http_interactions/build/packages/wisp/gleam.toml b/examples/http_interactions/build/packages/wisp/gleam.toml new file mode 100644 index 0000000..8af640a --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/gleam.toml @@ -0,0 +1,26 @@ +name = "wisp" +version = "2.2.2" +gleam = ">= 1.11.0" +description = "A practical web framework for Gleam" +licences = ["Apache-2.0"] + +repository = { type = "github", user = "gleam-wisp", repo = "wisp" } +links = [{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }] + +[dependencies] +exception = ">= 2.0.0 and < 3.0.0" +gleam_crypto = ">= 1.0.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +gleam_http = ">= 3.5.0 and < 5.0.0" +gleam_json = ">= 3.0.0 and < 4.0.0" +gleam_stdlib = ">= 0.70.0 and < 2.0.0" +mist = ">= 6.0.0 and < 7.0.0" +simplifile = ">= 2.0.0 and < 3.0.0" +marceau = ">= 1.1.0 and < 2.0.0" +logging = ">= 1.2.0 and < 2.0.0" +directories = ">= 1.0.0 and < 2.0.0" +houdini = ">= 1.0.0 and < 2.0.0" +filepath = ">= 1.1.2 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/examples/http_interactions/build/packages/wisp/include/wisp@internal_Connection.hrl b/examples/http_interactions/build/packages/wisp/include/wisp@internal_Connection.hrl new file mode 100644 index 0000000..0d059ae --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp@internal_Connection.hrl @@ -0,0 +1,8 @@ +-record(connection, { + reader :: fun((integer()) -> {ok, wisp@internal:read()} | {error, nil}), + max_body_size :: integer(), + max_files_size :: integer(), + read_chunk_size :: integer(), + secret_key_base :: binary(), + temporary_directory :: binary() +}). diff --git a/examples/http_interactions/build/packages/wisp/include/wisp@simulate_FileUpload.hrl b/examples/http_interactions/build/packages/wisp/include/wisp@simulate_FileUpload.hrl new file mode 100644 index 0000000..4d39a4d --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp@simulate_FileUpload.hrl @@ -0,0 +1,5 @@ +-record(file_upload, { + file_name :: binary(), + content_type :: binary(), + content :: bitstring() +}). diff --git a/examples/http_interactions/build/packages/wisp/include/wisp_File.hrl b/examples/http_interactions/build/packages/wisp/include/wisp_File.hrl new file mode 100644 index 0000000..355575f --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp_File.hrl @@ -0,0 +1,5 @@ +-record(file, { + path :: binary(), + offset :: integer(), + limit :: gleam@option:option(integer()) +}). diff --git a/examples/http_interactions/build/packages/wisp/include/wisp_FormData.hrl b/examples/http_interactions/build/packages/wisp/include/wisp_FormData.hrl new file mode 100644 index 0000000..e97439f --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp_FormData.hrl @@ -0,0 +1,4 @@ +-record(form_data, { + values :: list({binary(), binary()}), + files :: list({binary(), wisp:uploaded_file()}) +}). diff --git a/examples/http_interactions/build/packages/wisp/include/wisp_Range.hrl b/examples/http_interactions/build/packages/wisp/include/wisp_Range.hrl new file mode 100644 index 0000000..542ecd1 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp_Range.hrl @@ -0,0 +1 @@ +-record(range, {offset :: integer(), limit :: gleam@option:option(integer())}). diff --git a/examples/http_interactions/build/packages/wisp/include/wisp_UploadedFile.hrl b/examples/http_interactions/build/packages/wisp/include/wisp_UploadedFile.hrl new file mode 100644 index 0000000..21f2525 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/include/wisp_UploadedFile.hrl @@ -0,0 +1 @@ +-record(uploaded_file, {file_name :: binary(), path :: binary()}). diff --git a/examples/http_interactions/build/packages/wisp/src/wisp.app.src b/examples/http_interactions/build/packages/wisp/src/wisp.app.src new file mode 100644 index 0000000..efd00b1 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp.app.src @@ -0,0 +1,24 @@ +{application, wisp, [ + {vsn, "2.2.2"}, + {applications, [directories, + exception, + filepath, + gleam_crypto, + gleam_erlang, + gleam_http, + gleam_json, + gleam_stdlib, + houdini, + logging, + marceau, + mist, + simplifile]}, + {description, "A practical web framework for Gleam"}, + {modules, [wisp, + wisp@@main, + wisp@internal, + wisp@simulate, + wisp@wisp_mist, + wisp_ffi]}, + {registered, []} +]}. diff --git a/examples/http_interactions/build/packages/wisp/src/wisp.erl b/examples/http_interactions/build/packages/wisp/src/wisp.erl new file mode 100644 index 0000000..c84484c --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp.erl @@ -0,0 +1,2775 @@ +-module(wisp). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/wisp.gleam"). +-export([response/1, set_body/2, file_download/3, file_download_from_memory/3, html_response/2, json_response/2, html_body/2, json_body/2, string_tree_body/2, string_body/2, escape_html/1, method_not_allowed/1, no_content/0, set_max_body_size/2, get_max_body_size/1, set_secret_key_base/2, get_secret_key_base/1, set_max_files_size/2, get_max_files_size/1, set_read_chunk_size/2, get_read_chunk_size/1, require_method/3, get_query/1, method_override/1, read_body_bits/1, parse_range_header/1, handle_head/2, new_temporary_file/1, delete_temporary_files/1, configure_logger/0, set_logger_level/1, log_emergency/1, log_alert/1, log_critical/1, log_error/1, log_warning/1, log_notice/1, log_info/1, log_request/2, log_debug/1, random_string/1, sign_message/3, verify_signed_message/2, set_cookie/6, get_cookie/3, create_canned_connection/2, content_security_policy_protection/1, ok/0, created/0, accepted/0, redirect/1, permanent_redirect/1, not_found/0, bad_request/1, content_too_large/0, unsupported_media_type/1, unprocessable_content/0, internal_server_error/0, require_bit_array_body/2, require_content_type/3, rescue_crashes/1, require_string_body/2, require_json/2, csrf_known_header_protection/2, require_form/2, serve_static/4]). +-export_type([body/0, buffered_reader/0, quotas/0, form_data/0, uploaded_file/0, do_not_leak/0, error_kind/0, range/0, log_level/0, security/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type body() :: {text, binary()} | + {bytes, gleam@bytes_tree:bytes_tree()} | + {file, binary(), integer(), gleam@option:option(integer())}. + +-type buffered_reader() :: {buffered_reader, + fun((integer()) -> {ok, wisp@internal:read()} | {error, nil}), + bitstring()}. + +-type quotas() :: {quotas, integer(), integer()}. + +-type form_data() :: {form_data, + list({binary(), binary()}), + list({binary(), uploaded_file()})}. + +-type uploaded_file() :: {uploaded_file, binary(), binary()}. + +-type do_not_leak() :: any(). + +-type error_kind() :: errored | thrown | exited. + +-type range() :: {range, integer(), gleam@option:option(integer())}. + +-type log_level() :: emergency_level | + alert_level | + critical_level | + error_level | + warning_level | + notice_level | + info_level | + debug_level. + +-type security() :: plain_text | signed. + +-file("src/wisp.gleam", 84). +?DOC( + " Create a response with the given status code.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " response(200)\n" + " // -> Response(200, [], Text(\"\"))\n" + " ```\n" +). +-spec response(integer()) -> gleam@http@response:response(body()). +response(Status) -> + {response, Status, [], {text, <<""/utf8>>}}. + +-file("src/wisp.gleam", 98). +?DOC( + " Set the body of a response.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " response(200)\n" + " |> set_body(File(\"/tmp/myfile.txt\", option.None))\n" + " // -> Response(200, [], File(\"/tmp/myfile.txt\", option.None))\n" + " ```\n" +). +-spec set_body(gleam@http@response:response(body()), body()) -> gleam@http@response:response(body()). +set_body(Response, Body) -> + _pipe = Response, + gleam@http@response:set_body(_pipe, Body). + +-file("src/wisp.gleam", 128). +?DOC( + " Send a file from the disc as a file download.\n" + "\n" + " The operating system `send_file` function is used to efficiently send the\n" + " file over the network socket without reading the entire file into memory.\n" + "\n" + " The `content-disposition` header will be set to `attachment;\n" + " filename=\"name\"` to ensure the file is downloaded by the browser. This is\n" + " especially good for files that the browser would otherwise attempt to open\n" + " as this can result in cross-site scripting vulnerabilities.\n" + "\n" + " If you wish to not set the `content-disposition` header you could use the\n" + " `set_body` function with the `File` body variant.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " response(200)\n" + " |> file_download(named: \"myfile.txt\", from: \"/tmp/myfile.txt\")\n" + " // -> Response(\n" + " // 200,\n" + " // [#(\"content-disposition\", \"attachment; filename=\\\"myfile.txt\\\"\")],\n" + " // File(\"/tmp/myfile.txt\", option.None),\n" + " // )\n" + " ```\n" +). +-spec file_download(gleam@http@response:response(body()), binary(), binary()) -> gleam@http@response:response(body()). +file_download(Response, Name, Path) -> + Name@1 = gleam_stdlib:percent_encode(Name), + _pipe = Response, + _pipe@1 = gleam@http@response:set_header( + _pipe, + <<"content-disposition"/utf8>>, + <<<<"attachment; filename=\""/utf8, Name@1/binary>>/binary, "\""/utf8>> + ), + gleam@http@response:set_body(_pipe@1, {file, Path, 0, none}). + +-file("src/wisp.gleam", 165). +?DOC( + " Send a file from memory as a file download.\n" + "\n" + " If your file is already on the disc use `file_download` instead, to avoid\n" + " having to read the file into memory to send it.\n" + "\n" + " The `content-disposition` header will be set to `attachment;\n" + " filename=\"name\"` to ensure the file is downloaded by the browser. This is\n" + " especially good for files that the browser would otherwise attempt to open\n" + " as this can result in cross-site scripting vulnerabilities.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let content = bytes_tree.from_string(\"Hello, Joe!\")\n" + " response(200)\n" + " |> file_download_from_memory(named: \"myfile.txt\", containing: content)\n" + " // -> Response(\n" + " // 200,\n" + " // [#(\"content-disposition\", \"attachment; filename=\\\"myfile.txt\\\"\")],\n" + " // File(\"/tmp/myfile.txt\", option.None),\n" + " // )\n" + " ```\n" +). +-spec file_download_from_memory( + gleam@http@response:response(body()), + binary(), + gleam@bytes_tree:bytes_tree() +) -> gleam@http@response:response(body()). +file_download_from_memory(Response, Name, Data) -> + Name@1 = gleam_stdlib:percent_encode(Name), + _pipe = Response, + _pipe@1 = gleam@http@response:set_header( + _pipe, + <<"content-disposition"/utf8>>, + <<<<"attachment; filename=\""/utf8, Name@1/binary>>/binary, "\""/utf8>> + ), + gleam@http@response:set_body(_pipe@1, {bytes, Data}). + +-file("src/wisp.gleam", 191). +?DOC( + " Create a HTML response.\n" + "\n" + " The body is expected to be valid HTML, though this is not validated.\n" + " The `content-type` header will be set to `text/html; charset=utf-8`.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " html_response(\"

Hello, Joe!

\", 200)\n" + " // -> Response(200, [#(\"content-type\", \"text/html; charset=utf-8\")], Text(body))\n" + " ```\n" +). +-spec html_response(binary(), integer()) -> gleam@http@response:response(body()). +html_response(Html, Status) -> + {response, + Status, + [{<<"content-type"/utf8>>, <<"text/html; charset=utf-8"/utf8>>}], + {text, Html}}. + +-file("src/wisp.gleam", 211). +?DOC( + " Create a JSON response.\n" + "\n" + " The body is expected to be valid JSON, though this is not validated.\n" + " The `content-type` header will be set to `application/json`.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " json_response(\"{\\\"name\\\": \\\"Joe\\\"}\", 200)\n" + " // -> Response(200, [#(\"content-type\", \"application/json\")], Text(body))\n" + " ```\n" +). +-spec json_response(binary(), integer()) -> gleam@http@response:response(body()). +json_response(Json, Status) -> + {response, + Status, + [{<<"content-type"/utf8>>, <<"application/json; charset=utf-8"/utf8>>}], + {text, Json}}. + +-file("src/wisp.gleam", 232). +?DOC( + " Set the body of a response to a given HTML document, and set the\n" + " `content-type` header to `text/html`.\n" + "\n" + " The body is expected to be valid HTML, though this is not validated.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " response(201)\n" + " |> html_body(\"

Hello, Joe!

\")\n" + " // -> Response(201, [#(\"content-type\", \"text/html; charset=utf-8\")], Text(body))\n" + " ```\n" +). +-spec html_body(gleam@http@response:response(body()), binary()) -> gleam@http@response:response(body()). +html_body(Response, Html) -> + _pipe = Response, + _pipe@1 = gleam@http@response:set_body(_pipe, {text, Html}), + gleam@http@response:set_header( + _pipe@1, + <<"content-type"/utf8>>, + <<"text/html; charset=utf-8"/utf8>> + ). + +-file("src/wisp.gleam", 251). +?DOC( + " Set the body of a response to a given JSON document, and set the\n" + " `content-type` header to `application/json`.\n" + "\n" + " The body is expected to be valid JSON, though this is not validated.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " response(201)\n" + " |> json_body(\"{\\\"name\\\": \\\"Joe\\\"}\")\n" + " // -> Response(201, [#(\"content-type\", \"application/json; charset=utf-8\")], Text(body))\n" + " ```\n" +). +-spec json_body(gleam@http@response:response(body()), binary()) -> gleam@http@response:response(body()). +json_body(Response, Json) -> + _pipe = Response, + _pipe@1 = gleam@http@response:set_body(_pipe, {text, Json}), + gleam@http@response:set_header( + _pipe@1, + <<"content-type"/utf8>>, + <<"application/json; charset=utf-8"/utf8>> + ). + +-file("src/wisp.gleam", 271). +?DOC( + " Set the body of a response to a given string tree.\n" + "\n" + " You likely want to also set the request `content-type` header to an\n" + " appropriate value for the format of the content.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let body = string_tree.from_string(\"Hello, Joe!\")\n" + " response(201)\n" + " |> string_tree_body(body)\n" + " // -> Response(201, [], Text(body))\n" + " ```\n" +). +-spec string_tree_body( + gleam@http@response:response(body()), + gleam@string_tree:string_tree() +) -> gleam@http@response:response(body()). +string_tree_body(Response, Content) -> + _pipe = Response, + gleam@http@response:set_body( + _pipe, + {bytes, gleam_stdlib:wrap_list(Content)} + ). + +-file("src/wisp.gleam", 290). +?DOC( + " Set the body of a response to a given string.\n" + "\n" + " You likely want to also set the request `content-type` header to an\n" + " appropriate value for the format of the content.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " let body =\n" + " response(201)\n" + " |> string_body(\"Hello, Joe!\")\n" + " // -> Response(201, [], Text(\"Hello, Joe\"))\n" + " ```\n" +). +-spec string_body(gleam@http@response:response(body()), binary()) -> gleam@http@response:response(body()). +string_body(Response, Content) -> + _pipe = Response, + gleam@http@response:set_body(_pipe, {text, Content}). + +-file("src/wisp.gleam", 307). +?DOC( + " Escape a string so that it can be safely included in a HTML document.\n" + "\n" + " Any content provided by the user should be escaped before being included in\n" + " a HTML document to prevent cross-site scripting attacks.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " escape_html(\"

Hello, Joe!

\")\n" + " // -> \"<h1>Hello, Joe!</h1>\"\n" + " ```\n" +). +-spec escape_html(binary()) -> binary(). +escape_html(Content) -> + houdini:escape(Content). + +-file("src/wisp.gleam", 324). +?DOC( + " Create a response with status code 405: Method Not Allowed. Use this\n" + " when a request does not have an appropriate method to be handled.\n" + "\n" + " The `allow` header will be set to a comma separated list of the permitted\n" + " methods.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " method_not_allowed(allowed: [Get, Post])\n" + " // -> Response(405, [#(\"allow\", \"GET, POST\")], Text(\"Method not allowed\"))\n" + " ```\n" +). +-spec method_not_allowed(list(gleam@http:method())) -> gleam@http@response:response(body()). +method_not_allowed(Methods) -> + Allowed = begin + _pipe = Methods, + _pipe@1 = gleam@list:map(_pipe, fun gleam@http:method_to_string/1), + _pipe@2 = gleam@list:sort(_pipe@1, fun gleam@string:compare/2), + _pipe@3 = gleam@string:join(_pipe@2, <<", "/utf8>>), + string:uppercase(_pipe@3) + end, + {response, + 405, + [{<<"allow"/utf8>>, Allowed}], + {text, <<"Method not allowed"/utf8>>}}. + +-file("src/wisp.gleam", 430). +?DOC( + " Create a response with status code 204: No content.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " no_content()\n" + " // -> Response(204, [], Text(\"\"))\n" + " ```\n" +). +-spec no_content() -> gleam@http@response:response(body()). +no_content() -> + {response, 204, [], {text, <<""/utf8>>}}. + +-file("src/wisp.gleam", 577). +-spec buffered_read(buffered_reader(), integer()) -> {ok, wisp@internal:read()} | + {error, nil}. +buffered_read(Reader, Chunk_size) -> + case erlang:element(3, Reader) of + <<>> -> + (erlang:element(2, Reader))(Chunk_size); + + _ -> + {ok, {chunk, erlang:element(3, Reader), erlang:element(2, Reader)}} + end. + +-file("src/wisp.gleam", 597). +?DOC( + " Set the maximum permitted size of a request body of the request in bytes.\n" + "\n" + " If a body is larger than this size attempting to read the body will result\n" + " in a response with status code 413: Content too large will be returned to the\n" + " client.\n" + "\n" + " This limit only applies for headers and bodies that get read into memory.\n" + " Part of a multipart body that contain files and so are streamed to disc\n" + " instead use the `max_files_size` limit.\n" +). +-spec set_max_body_size( + gleam@http@request:request(wisp@internal:connection()), + integer() +) -> gleam@http@request:request(wisp@internal:connection()). +set_max_body_size(Request, Size) -> + _pipe = begin + _record = erlang:element(4, Request), + {connection, + erlang:element(2, _record), + Size, + erlang:element(4, _record), + erlang:element(5, _record), + erlang:element(6, _record), + erlang:element(7, _record)} + end, + gleam@http@request:set_body(Request, _pipe). + +-file("src/wisp.gleam", 604). +?DOC(" Get the maximum permitted size of a request body of the request in bytes.\n"). +-spec get_max_body_size(gleam@http@request:request(wisp@internal:connection())) -> integer(). +get_max_body_size(Request) -> + erlang:element(3, erlang:element(4, Request)). + +-file("src/wisp.gleam", 618). +?DOC( + " Set the secret key base used to sign cookies and other sensitive data.\n" + "\n" + " This key must be at least 64 bytes long and should be kept secret. Anyone\n" + " with this secret will be able to manipulate signed cookies and other sensitive\n" + " data.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic if the key is less than 64 bytes long.\n" +). +-spec set_secret_key_base( + gleam@http@request:request(wisp@internal:connection()), + binary() +) -> gleam@http@request:request(wisp@internal:connection()). +set_secret_key_base(Request, Key) -> + case erlang:byte_size(Key) < 64 of + true -> + erlang:error(#{gleam_error => panic, + message => <<"Secret key base must be at least 64 bytes long"/utf8>>, + file => <>, + module => <<"wisp"/utf8>>, + function => <<"set_secret_key_base"/utf8>>, + line => 620}); + + false -> + _pipe = begin + _record = erlang:element(4, Request), + {connection, + erlang:element(2, _record), + erlang:element(3, _record), + erlang:element(4, _record), + erlang:element(5, _record), + Key, + erlang:element(7, _record)} + end, + gleam@http@request:set_body(Request, _pipe) + end. + +-file("src/wisp.gleam", 629). +?DOC(" Get the secret key base used to sign cookies and other sensitive data.\n"). +-spec get_secret_key_base( + gleam@http@request:request(wisp@internal:connection()) +) -> binary(). +get_secret_key_base(Request) -> + erlang:element(6, erlang:element(4, Request)). + +-file("src/wisp.gleam", 643). +?DOC( + " Set the maximum permitted size of all files uploaded by a request, in bytes.\n" + "\n" + " If a request contains fails which are larger in total than this size\n" + " then attempting to read the body will result in a response with status code\n" + " 413: Content too large will be returned to the client.\n" + "\n" + " This limit only applies for files in a multipart body that get streamed to\n" + " disc. For headers and other content that gets read into memory use the\n" + " `max_body_size` limit.\n" +). +-spec set_max_files_size( + gleam@http@request:request(wisp@internal:connection()), + integer() +) -> gleam@http@request:request(wisp@internal:connection()). +set_max_files_size(Request, Size) -> + _pipe = begin + _record = erlang:element(4, Request), + {connection, + erlang:element(2, _record), + erlang:element(3, _record), + Size, + erlang:element(5, _record), + erlang:element(6, _record), + erlang:element(7, _record)} + end, + gleam@http@request:set_body(Request, _pipe). + +-file("src/wisp.gleam", 651). +?DOC( + " Get the maximum permitted total size of a files uploaded by a request in\n" + " bytes.\n" +). +-spec get_max_files_size(gleam@http@request:request(wisp@internal:connection())) -> integer(). +get_max_files_size(Request) -> + erlang:element(4, erlang:element(4, Request)). + +-file("src/wisp.gleam", 663). +?DOC( + " The the size limit for each chunk of the request body when read from the\n" + " client.\n" + "\n" + " This value is passed to the underlying web server when reading the body and\n" + " the exact size of chunks read depends on the server implementation. It most\n" + " likely will read chunks smaller than this size if not yet enough data has\n" + " been received from the client.\n" +). +-spec set_read_chunk_size( + gleam@http@request:request(wisp@internal:connection()), + integer() +) -> gleam@http@request:request(wisp@internal:connection()). +set_read_chunk_size(Request, Size) -> + _pipe = begin + _record = erlang:element(4, Request), + {connection, + erlang:element(2, _record), + erlang:element(3, _record), + erlang:element(4, _record), + Size, + erlang:element(6, _record), + erlang:element(7, _record)} + end, + gleam@http@request:set_body(Request, _pipe). + +-file("src/wisp.gleam", 671). +?DOC( + " Get the size limit for each chunk of the request body when read from the\n" + " client.\n" +). +-spec get_read_chunk_size( + gleam@http@request:request(wisp@internal:connection()) +) -> integer(). +get_read_chunk_size(Request) -> + erlang:element(5, erlang:element(4, Request)). + +-file("src/wisp.gleam", 693). +?DOC( + " This middleware function ensures that the request has a specific HTTP\n" + " method, returning a response with status code 405: Method not allowed\n" + " if the method is not correct.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use <- wisp.require_method(request, http.Patch)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec require_method( + gleam@http@request:request(any()), + gleam@http:method(), + fun(() -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_method(Request, Method, Next) -> + case erlang:element(2, Request) =:= Method of + true -> + Next(); + + false -> + method_not_allowed([Method]) + end. + +-file("src/wisp.gleam", 738). +?DOC( + " Parse the query parameters of a request into a list of key-value pairs. The\n" + " `key_find` function in the `gleam/list` stdlib module may be useful for\n" + " finding values in the list.\n" + "\n" + " Query parameter names do not have to be unique and so may appear multiple\n" + " times in the list.\n" +). +-spec get_query(gleam@http@request:request(wisp@internal:connection())) -> list({binary(), + binary()}). +get_query(Request) -> + _pipe = gleam@http@request:get_query(Request), + gleam@result:unwrap(_pipe, []). + +-file("src/wisp.gleam", 768). +?DOC( + " This function overrides an incoming POST request with a method given in\n" + " the request's `_method` query paramerter. This is useful as web browsers\n" + " typically only support GET and POST requests, but our application may\n" + " expect other HTTP methods that are more semantically correct.\n" + "\n" + " The methods PUT, PATCH, and DELETE are accepted for overriding, all others\n" + " are ignored.\n" + "\n" + " The `_method` query paramerter can be specified in a HTML form like so:\n" + "\n" + " ```html\n" + " \n" + " \n" + " \n" + " ```\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " let request = wisp.method_override(request)\n" + " // The method has now been overridden if appropriate\n" + " }\n" + " ```\n" +). +-spec method_override(gleam@http@request:request(OZL)) -> gleam@http@request:request(OZL). +method_override(Request) -> + gleam@bool:guard( + erlang:element(2, Request) /= post, + Request, + fun() -> + _pipe = begin + gleam@result:'try'( + gleam@http@request:get_query(Request), + fun(Query) -> + gleam@result:'try'( + gleam@list:key_find(Query, <<"_method"/utf8>>), + fun(Value) -> + gleam@result:map( + gleam@http:parse_method(Value), + fun(Method) -> case Method of + put -> + gleam@http@request:set_method( + Request, + Method + ); + + patch -> + gleam@http@request:set_method( + Request, + Method + ); + + delete -> + gleam@http@request:set_method( + Request, + Method + ); + + _ -> + Request + end end + ) + end + ) + end + ) + end, + gleam@result:unwrap(_pipe, Request) + end + ). + +-file("src/wisp.gleam", 881). +-spec read_body_loop( + fun((integer()) -> {ok, wisp@internal:read()} | {error, nil}), + integer(), + integer(), + bitstring() +) -> {ok, bitstring()} | {error, nil}. +read_body_loop(Reader, Read_chunk_size, Max_body_size, Accumulator) -> + gleam@result:'try'(Reader(Read_chunk_size), fun(Chunk) -> case Chunk of + reading_finished -> + {ok, Accumulator}; + + {chunk, Chunk@1, Next} -> + Accumulator@1 = gleam@bit_array:append(Accumulator, Chunk@1), + case erlang:byte_size(Accumulator@1) > Max_body_size of + true -> + {error, nil}; + + false -> + read_body_loop( + Next, + Read_chunk_size, + Max_body_size, + Accumulator@1 + ) + end + end end). + +-file("src/wisp.gleam", 871). +?DOC( + " Read the entire body of the request as a bit array.\n" + "\n" + " You may instead wish to use the `require_bit_array_body` or the\n" + " `require_string_body` middleware functions instead.\n" + "\n" + " This function does not cache the body in any way, so if you call this\n" + " function (or any other body reading function) more than once it may hang or\n" + " return an incorrect value, depending on the underlying web server. It is the\n" + " responsibility of the caller to cache the body if it is needed multiple\n" + " times.\n" + "\n" + " If the body is larger than the `max_body_size` limit then a response\n" + " with status code 413: Content too large will be returned to the client.\n" +). +-spec read_body_bits(gleam@http@request:request(wisp@internal:connection())) -> {ok, + bitstring()} | + {error, nil}. +read_body_bits(Request) -> + Connection = erlang:element(4, Request), + read_body_loop( + erlang:element(2, Connection), + erlang:element(5, Connection), + erlang:element(3, Connection), + <<>> + ). + +-file("src/wisp.gleam", 1253). +-spec sort_keys(list({binary(), PBC})) -> list({binary(), PBC}). +sort_keys(Pairs) -> + gleam@list:sort( + Pairs, + fun(A, B) -> + gleam@string:compare(erlang:element(1, A), erlang:element(1, B)) + end + ). + +-file("src/wisp.gleam", 1321). +-spec atom_dict_decoder() -> gleam@dynamic@decode:decoder(gleam@dict:dict(gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_())). +atom_dict_decoder() -> + Atom@1 = gleam@dynamic@decode:new_primitive_decoder( + <<"Atom"/utf8>>, + fun(Data) -> case wisp_ffi:atom_from_dynamic(Data) of + {ok, Atom} -> + {ok, Atom}; + + {error, _} -> + {error, erlang:binary_to_atom(<<"nil"/utf8>>)} + end end + ), + gleam@dynamic@decode:dict( + Atom@1, + {decoder, fun gleam@dynamic@decode:decode_dynamic/1} + ). + +-file("src/wisp.gleam", 1489). +?DOC( + " Parses the content of a range header.\n" + "\n" + " # Example\n" + "\n" + " ```gleam\n" + " wisp.parse_range_header(\"bytes=-64\")\n" + " // -> Ok(Range(offset: -64, limit: option.None))\n" + " ```\n" +). +-spec parse_range_header(binary()) -> {ok, range()} | {error, nil}. +parse_range_header(Range_header) -> + case Range_header of + <<"bytes="/utf8, Range/binary>> -> + gleam@result:'try'( + begin + _pipe = Range, + gleam@string:split_once(_pipe, <<"-"/utf8>>) + end, + fun(_use0) -> + {Start_str, End_str} = _use0, + case {Start_str, End_str} of + {<<""/utf8>>, _} -> + _pipe@1 = gleam_stdlib:parse_int(End_str), + gleam@result:map( + _pipe@1, + fun(Tail_offset) -> + {range, - Tail_offset, none} + end + ); + + {_, <<""/utf8>>} -> + _pipe@2 = gleam_stdlib:parse_int(Start_str), + gleam@result:map( + _pipe@2, + fun(Offset) -> {range, Offset, none} end + ); + + {_, _} -> + gleam@result:'try'( + gleam_stdlib:parse_int(Start_str), + fun(Offset@1) -> + gleam@result:'try'( + gleam_stdlib:parse_int(End_str), + fun(End) -> + {ok, + {range, + Offset@1, + {some, (End - Offset@1) + 1}}} + end + ) + end + ) + end + end + ); + + _ -> + {error, nil} + end. + +-file("src/wisp.gleam", 1637). +?DOC( + " A middleware function that converts `HEAD` requests to `GET` requests,\n" + " handles the request, and then discards the response body. This is useful so\n" + " that your application can handle `HEAD` requests without having to implement\n" + " handlers for them.\n" + "\n" + " The `x-original-method` header is set to `\"HEAD\"` for requests that were\n" + " originally `HEAD` requests.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(req: Request) -> Response {\n" + " use req <- wisp.handle_head(req)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec handle_head( + gleam@http@request:request(wisp@internal:connection()), + fun((gleam@http@request:request(wisp@internal:connection())) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +handle_head(Req, Handler) -> + case erlang:element(2, Req) of + head -> + _pipe = Req, + _pipe@1 = gleam@http@request:set_method(_pipe, get), + _pipe@2 = gleam@http@request:prepend_header( + _pipe@1, + <<"x-original-method"/utf8>>, + <<"HEAD"/utf8>> + ), + Handler(_pipe@2); + + _ -> + Handler(Req) + end. + +-file("src/wisp.gleam", 1662). +?DOC( + " Create a new temporary directory for the given request.\n" + "\n" + " If you are using the Mist adapter or another compliant web server\n" + " adapter then this file will be deleted for you when the request is complete.\n" + " Otherwise you will need to call the `delete_temporary_files` function\n" + " yourself.\n" +). +-spec new_temporary_file(gleam@http@request:request(wisp@internal:connection())) -> {ok, + binary()} | + {error, simplifile:file_error()}. +new_temporary_file(Request) -> + Directory = erlang:element(7, erlang:element(4, Request)), + gleam@result:'try'( + simplifile:create_directory_all(Directory), + fun(_) -> + Path = filepath:join(Directory, wisp@internal:random_slug()), + gleam@result:map(simplifile:create_file(Path), fun(_) -> Path end) + end + ). + +-file("src/wisp.gleam", 1678). +?DOC( + " Delete any temporary files created for the given request.\n" + "\n" + " If you are using the Mist adapter or another compliant web server\n" + " adapter then this file will be deleted for you when the request is complete.\n" + " Otherwise you will need to call this function yourself.\n" +). +-spec delete_temporary_files( + gleam@http@request:request(wisp@internal:connection()) +) -> {ok, nil} | {error, simplifile:file_error()}. +delete_temporary_files(Request) -> + case simplifile_erl:delete(erlang:element(7, erlang:element(4, Request))) of + {error, enoent} -> + {ok, nil}; + + Other -> + Other + end. + +-file("src/wisp.gleam", 1713). +?DOC( + " Configure the Erlang logger, setting the minimum log level to `info`, to be\n" + " called when your application starts.\n" + "\n" + " You may wish to use an alternative for this such as one provided by a more\n" + " sophisticated logging library.\n" + "\n" + " In future this function may be extended to change the output format.\n" +). +-spec configure_logger() -> nil. +configure_logger() -> + logging_ffi:configure(). + +-file("src/wisp.gleam", 1734). +-spec log_level_to_logging_log_level(log_level()) -> logging:log_level(). +log_level_to_logging_log_level(Log_level) -> + case Log_level of + emergency_level -> + emergency; + + alert_level -> + alert; + + critical_level -> + critical; + + error_level -> + error; + + warning_level -> + warning; + + notice_level -> + notice; + + info_level -> + info; + + debug_level -> + debug + end. + +-file("src/wisp.gleam", 1753). +?DOC( + " Set the log level of the Erlang logger to `log_level`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec set_logger_level(log_level()) -> nil. +set_logger_level(Log_level) -> + logging:set_level(log_level_to_logging_log_level(Log_level)). + +-file("src/wisp.gleam", 1763). +?DOC( + " Log a message to the Erlang logger with the level of `emergency`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_emergency(binary()) -> nil. +log_emergency(Message) -> + logging:log(emergency, Message). + +-file("src/wisp.gleam", 1773). +?DOC( + " Log a message to the Erlang logger with the level of `alert`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_alert(binary()) -> nil. +log_alert(Message) -> + logging:log(alert, Message). + +-file("src/wisp.gleam", 1783). +?DOC( + " Log a message to the Erlang logger with the level of `critical`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_critical(binary()) -> nil. +log_critical(Message) -> + logging:log(critical, Message). + +-file("src/wisp.gleam", 1793). +?DOC( + " Log a message to the Erlang logger with the level of `error`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_error(binary()) -> nil. +log_error(Message) -> + logging:log(error, Message). + +-file("src/wisp.gleam", 1803). +?DOC( + " Log a message to the Erlang logger with the level of `warning`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_warning(binary()) -> nil. +log_warning(Message) -> + logging:log(warning, Message). + +-file("src/wisp.gleam", 1813). +?DOC( + " Log a message to the Erlang logger with the level of `notice`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_notice(binary()) -> nil. +log_notice(Message) -> + logging:log(notice, Message). + +-file("src/wisp.gleam", 1823). +?DOC( + " Log a message to the Erlang logger with the level of `info`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_info(binary()) -> nil. +log_info(Message) -> + logging:log(info, Message). + +-file("src/wisp.gleam", 1361). +?DOC( + " A middleware function that logs details about the request and response.\n" + "\n" + " The format used logged by this middleware may change in future versions of\n" + " Wisp.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(req: Request) -> Response {\n" + " use <- wisp.log_request(req)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec log_request( + gleam@http@request:request(wisp@internal:connection()), + fun(() -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +log_request(Req, Handler) -> + Response = Handler(), + _pipe = [erlang:integer_to_binary(erlang:element(2, Response)), + <<" "/utf8>>, + string:uppercase(gleam@http:method_to_string(erlang:element(2, Req))), + <<" "/utf8>>, + erlang:element(8, Req)], + _pipe@1 = erlang:list_to_binary(_pipe), + log_info(_pipe@1), + Response. + +-file("src/wisp.gleam", 1833). +?DOC( + " Log a message to the Erlang logger with the level of `debug`.\n" + "\n" + " See the [Erlang logger documentation][1] for more information.\n" + "\n" + " [1]: https://www.erlang.org/doc/man/logger\n" +). +-spec log_debug(binary()) -> nil. +log_debug(Message) -> + logging:log(debug, Message). + +-file("src/wisp.gleam", 1846). +?DOC( + " Generate a random string of the given length.\n" + "\n" + " This string is URL safe and generated in a strong-random fashion, making it\n" + " suitable for security purposes.\n" +). +-spec random_string(integer()) -> binary(). +random_string(Length) -> + wisp@internal:random_string(Length). + +-file("src/wisp.gleam", 1859). +?DOC( + " Sign a message which can later be verified using the `verify_signed_message`\n" + " function to detect if the message has been tampered with.\n" + "\n" + " Signed messages are not encrypted and can be read by anyone. They are not\n" + " suitable for storing sensitive information.\n" + "\n" + " This function uses the secret key base from the request. If the secret\n" + " changes then the signature will no longer be verifiable.\n" +). +-spec sign_message( + gleam@http@request:request(wisp@internal:connection()), + bitstring(), + gleam@crypto:hash_algorithm() +) -> binary(). +sign_message(Request, Message, Algorithm) -> + gleam@crypto:sign_message( + Message, + <<(erlang:element(6, erlang:element(4, Request)))/binary>>, + Algorithm + ). + +-file("src/wisp.gleam", 1875). +?DOC( + " Verify a signed message which was signed using the `sign_message` function.\n" + "\n" + " Returns the content of the message if the signature is valid, otherwise\n" + " returns an error.\n" + "\n" + " This function uses the secret key base from the request. If the secret\n" + " changes then the signature will no longer be verifiable.\n" +). +-spec verify_signed_message( + gleam@http@request:request(wisp@internal:connection()), + binary() +) -> {ok, bitstring()} | {error, nil}. +verify_signed_message(Request, Message) -> + gleam@crypto:verify_signed_message( + Message, + <<(erlang:element(6, erlang:element(4, Request)))/binary>> + ). + +-file("src/wisp.gleam", 1932). +?DOC( + " Set a cookie on the response. After `max_age` seconds the cookie will be\n" + " expired by the client.\n" + "\n" + " If you wish for more control over the cookie attributes then you may want\n" + " to use the `gleam/http/cookie` module from the `gleam_http` package.\n" + "\n" + " # Security\n" + "\n" + " - `PlainText`: the cookie value is base64 encoded. This permits use of any\n" + " characters in the cookie, but it is possible for the client to edit the\n" + " cookie, potentially maliciously.\n" + " - `Signed`: the cookie value will be signed with `sign_message` and so\n" + " cannot be tampered with by the client.\n" + "\n" + " # `Secure` cookie attribute\n" + "\n" + " This function sets the `Secure` cookie attribute (with one exception detailed\n" + " below), ensuring that browsers will only send the cookie over HTTPS\n" + " connections.\n" + "\n" + " Most browsers consider localhost to be secure and permit `Secure` cookies\n" + " for those requests as well, but Safari does not. For cookies to work in\n" + " development for programmers using a browser like Safari the `Secure`\n" + " attribute will not be set if all these conditions are met:\n" + "\n" + " - The request scheme is `http://`.\n" + " - The request host is `localhost`, `127.0.0.1`, or `[::1]`.\n" + " - The `x-forwarded-proto` header has not been set, indicating that the\n" + " request is not from a reverse proxy such as Caddy or Nginx.\n" + "\n" + " # Examples\n" + "\n" + " Setting a plain text cookie that the client can read and modify:\n" + "\n" + " ```gleam\n" + " wisp.ok()\n" + " |> wisp.set_cookie(request, \"id\", \"123\", wisp.PlainText, 60 * 60)\n" + " ```\n" + "\n" + " Setting a signed cookie that the client can read but not modify:\n" + "\n" + " ```gleam\n" + " wisp.ok()\n" + " |> wisp.set_cookie(request, \"id\", value, wisp.Signed, 60 * 60)\n" + " ```\n" +). +-spec set_cookie( + gleam@http@response:response(body()), + gleam@http@request:request(wisp@internal:connection()), + binary(), + binary(), + security(), + integer() +) -> gleam@http@response:response(body()). +set_cookie(Response, Request, Name, Value, Security, Max_age) -> + Scheme = case erlang:element(6, Request) of + <<"localhost"/utf8>> when erlang:element(5, Request) =:= http -> + case gleam@http@request:get_header( + Request, + <<"x-forwarded-proto"/utf8>> + ) of + {ok, _} -> + https; + + {error, _} -> + http + end; + + <<"127.0.0.1"/utf8>> when erlang:element(5, Request) =:= http -> + case gleam@http@request:get_header( + Request, + <<"x-forwarded-proto"/utf8>> + ) of + {ok, _} -> + https; + + {error, _} -> + http + end; + + <<"[::1]"/utf8>> when erlang:element(5, Request) =:= http -> + case gleam@http@request:get_header( + Request, + <<"x-forwarded-proto"/utf8>> + ) of + {ok, _} -> + https; + + {error, _} -> + http + end; + + _ -> + https + end, + Attributes = begin + _record = gleam@http@cookie:defaults(Scheme), + {attributes, + {some, Max_age}, + erlang:element(3, _record), + erlang:element(4, _record), + erlang:element(5, _record), + erlang:element(6, _record), + erlang:element(7, _record)} + end, + Value@1 = case Security of + plain_text -> + gleam_stdlib:base64_encode(<>, false); + + signed -> + sign_message(Request, <>, sha512) + end, + _pipe = Response, + gleam@http@response:set_cookie(_pipe, Name, Value@1, Attributes). + +-file("src/wisp.gleam", 1985). +?DOC( + " Get a cookie from the request.\n" + "\n" + " If a cookie is missing, found to be malformed, or the signature is invalid\n" + " for a signed cookie, then `Error(Nil)` is returned.\n" + "\n" + " # Security\n" + "\n" + " - `PlainText`: the cookie value is expected to be base64 encoded.\n" + " - `Signed`: the cookie value is expected to be signed with `sign_message`.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " wisp.get_cookie(request, \"group\", wisp.PlainText)\n" + " // -> Ok(\"A\")\n" + " ```\n" +). +-spec get_cookie( + gleam@http@request:request(wisp@internal:connection()), + binary(), + security() +) -> {ok, binary()} | {error, nil}. +get_cookie(Request, Name, Security) -> + gleam@result:'try'( + begin + _pipe = Request, + _pipe@1 = gleam@http@request:get_cookies(_pipe), + gleam@list:key_find(_pipe@1, Name) + end, + fun(Value) -> gleam@result:'try'(case Security of + plain_text -> + gleam@bit_array:base64_decode(Value); + + signed -> + verify_signed_message(Request, Value) + end, fun(Value@1) -> gleam@bit_array:to_string(Value@1) end) end + ). + +-file("src/wisp.gleam", 2014). +-spec canned_reader(bitstring()) -> fun((integer()) -> {ok, + wisp@internal:read()} | + {error, nil}). +canned_reader(Data) -> + fun(Chunk_size) -> case erlang:byte_size(Data) of + 0 -> + {ok, reading_finished}; + + Size -> + Take = gleam@int:min(Chunk_size, Size), + Chunk@1 = case gleam_stdlib:bit_array_slice(Data, 0, Take) of + {ok, Chunk} -> Chunk; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"wisp"/utf8>>, + function => <<"canned_reader"/utf8>>, + line => 2020, + value => _assert_fail, + start => 59231, + 'end' => 59284, + pattern_start => 59242, + pattern_end => 59251}) + end, + Rest@1 = case gleam_stdlib:bit_array_slice( + Data, + Take, + Size - Take + ) of + {ok, Rest} -> Rest; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"wisp"/utf8>>, + function => <<"canned_reader"/utf8>>, + line => 2021, + value => _assert_fail@1, + start => 59293, + 'end' => 59355, + pattern_start => 59304, + pattern_end => 59312}) + end, + {ok, {chunk, Chunk@1, canned_reader(Rest@1)}} + end end. + +-file("src/wisp.gleam", 2007). +?DOC(false). +-spec create_canned_connection(bitstring(), binary()) -> wisp@internal:connection(). +create_canned_connection(Body, Secret_key_base) -> + wisp@internal:make_connection(canned_reader(Body), Secret_key_base). + +-file("src/wisp.gleam", 2165). +?DOC( + " Protects against cross-site-scripting (XSS) attacks using a nonce-based\n" + " content-security-policy (CSP).\n" + "\n" + " This middleware will provide a unique single use random string (a nonce) to\n" + " the handler, and set this CSP header on the response returned by the handler.\n" + "\n" + " ```txt\n" + " Content-Security-Policy:\n" + " script-src 'nonce-{NONCE}' 'strict-dynamic';\n" + " object-src 'none';\n" + " base-uri 'none';\n" + " ```\n" + "\n" + " This header causes the browser to be restricted in these ways:\n" + "\n" + " - Any `\n" + " ```\n" + "\n" + " It is recommended to add this middleware so that it applies to all routes\n" + " in your application.\n" + "\n" + " For more information about CSP see these articles:\n" + "\n" + " - \n" + " - \n" +). +-spec content_security_policy_protection( + fun((binary()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +content_security_policy_protection(Handle_request) -> + Nonce = random_string(24), + Header = <<<<"script-src 'nonce-"/utf8, Nonce/binary>>/binary, + "' 'strict-dynamic'; object-src 'none'; base-uri 'none'"/utf8>>, + _pipe = Handle_request(Nonce), + gleam@http@response:set_header( + _pipe, + <<"content-security-policy"/utf8>>, + Header + ). + +-file("src/wisp.gleam", 343). +?DOC( + " Create a response with status code 200: OK.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " ok()\n" + " // -> Response(200, [#(\"content-type\", \"text/plain\")], Text(\"OK\"))\n" + " ```\n" +). +-spec ok() -> gleam@http@response:response(body()). +ok() -> + {response, + 200, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"OK"/utf8>>}}. + +-file("src/wisp.gleam", 356). +?DOC( + " Create a response with status code 201: Created.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " created()\n" + " // -> Response(201, [#(\"content-type\", \"text/plain\")], Text(\"Created\"))\n" + " ```\n" +). +-spec created() -> gleam@http@response:response(body()). +created() -> + {response, + 201, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Created"/utf8>>}}. + +-file("src/wisp.gleam", 369). +?DOC( + " Create a response with status code 202: Accepted.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " accepted()\n" + " // -> Response(202, [#(\"content-type\", \"text/plain\")], Text(\"Accepted\"))\n" + " ```\n" +). +-spec accepted() -> gleam@http@response:response(body()). +accepted() -> + {response, + 202, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Accepted"/utf8>>}}. + +-file("src/wisp.gleam", 387). +?DOC( + " Create a response with status code 303: See Other, and the `location`\n" + " header set to the given URL. Used to redirect the client to another page.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " redirect(to: \"https://example.com\")\n" + " // -> Response(\n" + " // 303,\n" + " // [#(\"location\", \"https://example.com\"), #(\"context-type\", \"text/plain\")],\n" + " // Text(\"You are being redirected: https://example.com\"),\n" + " // )\n" + " ```\n" +). +-spec redirect(binary()) -> gleam@http@response:response(body()). +redirect(Url) -> + {response, + 303, + [{<<"location"/utf8>>, Url}, + {<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"You are being redirected: "/utf8, Url/binary>>}}. + +-file("src/wisp.gleam", 413). +?DOC( + " Create a response with status code 308: Permanent redirect, and the\n" + " `location` header set to the given URL. Used to redirect the client to\n" + " another page.\n" + "\n" + " This redirect is permanent and the client is expected to cache the new\n" + " location, using it for future requests.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " moved_permanently(to: \"https://example.com\")\n" + " // -> Response(\n" + " // 303,\n" + " // [#(\"location\", \"https://example.com\")],\n" + " // Text(\"You are being redirected: https://example.com\"),\n" + " // )\n" + " ```\n" +). +-spec permanent_redirect(binary()) -> gleam@http@response:response(body()). +permanent_redirect(Url) -> + {response, + 308, + [{<<"location"/utf8>>, Url}, + {<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"You are being redirected: "/utf8, Url/binary>>}}. + +-file("src/wisp.gleam", 443). +?DOC( + " Create a response with status code 404: Not found.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " not_found()\n" + " // -> Response(404, [#(\"content-type\", \"text/plain\")], Text(\"Not found\"))\n" + " ```\n" +). +-spec not_found() -> gleam@http@response:response(body()). +not_found() -> + {response, + 404, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Not found"/utf8>>}}. + +-file("src/wisp.gleam", 456). +?DOC( + " Create a response with status code 400: Bad request.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " bad_request(\"Invalid JSON\")\n" + " // -> Response(400, [#(\"content-type\", \"text/plain\")], Text(\"Bad request: Invalid JSON\"))\n" + " ```\n" +). +-spec bad_request(binary()) -> gleam@http@response:response(body()). +bad_request(Detail) -> + Body = case Detail of + <<""/utf8>> -> + <<"Bad request"/utf8>>; + + _ -> + <<"Bad request: "/utf8, Detail/binary>> + end, + {response, + 400, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, Body}}. + +-file("src/wisp.gleam", 473). +?DOC( + " Create a response with status code 413: Content too large.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " content_too_large()\n" + " // -> Response(413, [#(\"content-type\", \"text/plain\")], Text(\"Content too large\"))\n" + " ```\n" +). +-spec content_too_large() -> gleam@http@response:response(body()). +content_too_large() -> + {response, + 413, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Content too large"/utf8>>}}. + +-file("src/wisp.gleam", 489). +?DOC( + " Create a response with status code 415: Unsupported media type.\n" + "\n" + " The `allow` header will be set to a comma separated list of the permitted\n" + " content-types.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " unsupported_media_type(accept: [\"application/json\", \"text/plain\"])\n" + " // -> Response(415, [#(\"allow\", \"application/json, text/plain\")], Text(\"Unsupported media type\"))\n" + " ```\n" +). +-spec unsupported_media_type(list(binary())) -> gleam@http@response:response(body()). +unsupported_media_type(Acceptable) -> + Acceptable@1 = gleam@string:join(Acceptable, <<", "/utf8>>), + {response, + 415, + [{<<"accept"/utf8>>, Acceptable@1}, + {<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Unsupported media type"/utf8>>}}. + +-file("src/wisp.gleam", 507). +?DOC( + " Create a response with status code 422: Unprocessable content.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " unprocessable_content()\n" + " // -> Response(422, [#(\"content-type\", \"text/plain\")], Text(\"Unprocessable content\"))\n" + " ```\n" +). +-spec unprocessable_content() -> gleam@http@response:response(body()). +unprocessable_content() -> + {response, + 422, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Unprocessable content"/utf8>>}}. + +-file("src/wisp.gleam", 520). +?DOC( + " Create a response with status code 500: Internal server error.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " internal_server_error()\n" + " // -> Response(500, [#(\"content-type\", \"text/plain\")], Text(\"Internal server error\"))\n" + " ```\n" +). +-spec internal_server_error() -> gleam@http@response:response(body()). +internal_server_error() -> + {response, + 500, + [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}], + {text, <<"Internal server error"/utf8>>}}. + +-file("src/wisp.gleam", 562). +-spec decrement_body_quota(quotas(), integer()) -> {ok, quotas()} | + {error, gleam@http@response:response(body())}. +decrement_body_quota(Quotas, Size) -> + Quotas@1 = {quotas, + erlang:element(2, Quotas) - Size, + erlang:element(3, Quotas)}, + case erlang:element(2, Quotas@1) < 0 of + true -> + {error, content_too_large()}; + + false -> + {ok, Quotas@1} + end. + +-file("src/wisp.gleam", 570). +-spec decrement_quota(integer(), integer()) -> {ok, integer()} | + {error, gleam@http@response:response(body())}. +decrement_quota(Quota, Size) -> + case Quota - Size of + Quota@1 when Quota@1 < 0 -> + {error, content_too_large()}; + + Quota@2 -> + {ok, Quota@2} + end. + +-file("src/wisp.gleam", 845). +?DOC( + " A middleware function which reads the entire body of the request as a bit\n" + " string.\n" + "\n" + " This function does not cache the body in any way, so if you call this\n" + " function (or any other body reading function) more than once it may hang or\n" + " return an incorrect value, depending on the underlying web server. It is the\n" + " responsibility of the caller to cache the body if it is needed multiple\n" + " times.\n" + "\n" + " If the body is larger than the `max_body_size` limit then a response\n" + " with status code 413: Content too large will be returned to the client.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use body <- wisp.require_string_body(request)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec require_bit_array_body( + gleam@http@request:request(wisp@internal:connection()), + fun((bitstring()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_bit_array_body(Request, Next) -> + case read_body_bits(Request) of + {ok, Body} -> + Next(Body); + + {error, _} -> + content_too_large() + end. + +-file("src/wisp.gleam", 971). +?DOC( + " This middleware function ensures that the request has a value for the\n" + " `content-type` header, returning a response with status code 415:\n" + " Unsupported media type if the header is not the expected value\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use <- wisp.require_content_type(request, \"application/json\")\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec require_content_type( + gleam@http@request:request(wisp@internal:connection()), + binary(), + fun(() -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_content_type(Request, Expected, Next) -> + case gleam@list:key_find( + erlang:element(3, Request), + <<"content-type"/utf8>> + ) of + {ok, Content_type} -> + case gleam@string:split_once(Content_type, <<";"/utf8>>) of + {ok, {Content_type@1, _}} when Content_type@1 =:= Expected -> + Next(); + + _ when Content_type =:= Expected -> + Next(); + + _ -> + unsupported_media_type([Expected]) + end; + + _ -> + unsupported_media_type([Expected]) + end. + +-file("src/wisp.gleam", 1129). +-spec or_500({ok, OZW} | {error, any()}) -> {ok, OZW} | + {error, gleam@http@response:response(body())}. +or_500(Result) -> + case Result of + {ok, Value} -> + {ok, Value}; + + {error, Error} -> + log_error(gleam@string:inspect(Error)), + {error, internal_server_error()} + end. + +-file("src/wisp.gleam", 1120). +-spec multipart_file_append(binary(), bitstring()) -> {ok, binary()} | + {error, gleam@http@response:response(body())}. +multipart_file_append(Path, Chunk) -> + _pipe = simplifile_erl:append_bits(Path, Chunk), + _pipe@1 = or_500(_pipe), + gleam@result:replace(_pipe@1, Path). + +-file("src/wisp.gleam", 1182). +-spec fn_with_bad_request_error( + fun((PAK) -> {ok, PAL} | {error, any()}), + binary() +) -> fun((PAK) -> {ok, PAL} | {error, gleam@http@response:response(body())}). +fn_with_bad_request_error(F, Error) -> + fun(A) -> case F(A) of + {ok, X} -> + {ok, X}; + + {error, _} -> + {error, bad_request(Error)} + end end. + +-file("src/wisp.gleam", 1296). +?DOC( + " A middleware function that rescues crashes and returns a response\n" + " with status code 500: Internal server error.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(req: Request) -> Response {\n" + " use <- wisp.rescue_crashes\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec rescue_crashes(fun(() -> gleam@http@response:response(body()))) -> gleam@http@response:response(body()). +rescue_crashes(Handler) -> + case exception_ffi:rescue(Handler) of + {ok, Response} -> + Response; + + {error, Error} -> + {Kind, Detail@3} = case Error of + {errored, Detail} -> + {errored, Detail}; + + {thrown, Detail@1} -> + {thrown, Detail@1}; + + {exited, Detail@2} -> + {exited, Detail@2} + end, + case gleam@dynamic@decode:run(Detail@3, atom_dict_decoder()) of + {ok, Details} -> + C = erlang:binary_to_atom(<<"class"/utf8>>), + logger:error( + gleam@dict:insert( + Details, + C, + gleam@function:identity(Kind) + ) + ), + nil; + + {error, _} -> + log_error(gleam@string:inspect(Error)) + end, + internal_server_error() + end. + +-file("src/wisp.gleam", 808). +?DOC( + " A middleware function which reads the entire body of the request as a string.\n" + "\n" + " This function does not cache the body in any way, so if you call this\n" + " function (or any other body reading function) more than once it may hang or\n" + " return an incorrect value, depending on the underlying web server. It is the\n" + " responsibility of the caller to cache the body if it is needed multiple\n" + " times.\n" + "\n" + " If the body is larger than the `max_body_size` limit then a response\n" + " with status code 413: Content too large will be returned to the client.\n" + "\n" + " If the body is found not to be valid UTF-8 then a response with\n" + " status code 400: Bad request will be returned to the client.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use body <- wisp.require_string_body(request)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec require_string_body( + gleam@http@request:request(wisp@internal:connection()), + fun((binary()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_string_body(Request, Next) -> + case read_body_bits(Request) of + {ok, Body} -> + case gleam@bit_array:to_string(Body) of + {ok, Body@1} -> + Next(Body@1); + + {error, _} -> + bad_request(<<"Invalid UTF-8"/utf8>>) + end; + + {error, _} -> + content_too_large() + end. + +-file("src/wisp.gleam", 1013). +?DOC( + " A middleware which extracts JSON from the body of a request.\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use json <- wisp.require_json(request)\n" + " // decode and use JSON here...\n" + " }\n" + " ```\n" + "\n" + " The `set_max_body_size` and `set_read_chunk_size` can be used to configure\n" + " the reading of the request body.\n" + "\n" + " If the request does not have the `content-type` set to `application/json` a\n" + " response with status code 415: Unsupported media type will be returned\n" + " to the client.\n" + "\n" + " If the request body is larger than the `max_body_size` or `max_files_size`\n" + " limits then a response with status code 413: Content too large will be\n" + " returned to the client.\n" + "\n" + " If the body cannot be parsed successfully then a response with status\n" + " code 400: Bad request will be returned to the client.\n" +). +-spec require_json( + gleam@http@request:request(wisp@internal:connection()), + fun((gleam@dynamic:dynamic_()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_json(Request, Next) -> + require_content_type( + Request, + <<"application/json"/utf8>>, + fun() -> + require_string_body( + Request, + fun(Body) -> + case gleam@json:parse( + Body, + {decoder, fun gleam@dynamic@decode:decode_dynamic/1} + ) of + {ok, Json} -> + Next(Json); + + {error, _} -> + bad_request(<<"Invalid JSON"/utf8>>) + end + end + ) + end + ). + +-file("src/wisp.gleam", 1022). +-spec require_urlencoded_form( + gleam@http@request:request(wisp@internal:connection()), + fun((form_data()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_urlencoded_form(Request, Next) -> + require_string_body( + Request, + fun(Body) -> case gleam_stdlib:parse_query(Body) of + {ok, Pairs} -> + Pairs@1 = sort_keys(Pairs), + Next({form_data, Pairs@1, []}); + + {error, _} -> + bad_request(<<"Invalid form encoding"/utf8>>) + end end + ). + +-file("src/wisp.gleam", 1528). +?DOC( + " Checks for the `range` header and handles partial file reads.\n" + "\n" + " If the range request header is present, it will set the `accept-ranges`,\n" + " `content-range`, and `content-length` response headers. If the range\n" + " request header has a range that is out of bounds of the file, it will\n" + " respond with a `416 Range Not Satisfiable`.\n" + "\n" + " If the header isn't present, it returns the input response without changes.\n" +). +-spec handle_file_range_header( + gleam@http@response:response(body()), + gleam@http@request:request(wisp@internal:connection()), + simplifile:file_info(), + binary() +) -> gleam@http@response:response(body()). +handle_file_range_header(Resp, Req, File_info, Path) -> + Result = begin + gleam@result:'try'( + begin + _pipe = gleam@http@request:get_header(Req, <<"range"/utf8>>), + gleam@result:replace_error(_pipe, Resp) + end, + fun(Raw_range) -> + gleam@result:'try'( + begin + _pipe@1 = parse_range_header(Raw_range), + gleam@result:replace_error( + _pipe@1, + bad_request(<<"Invalid range"/utf8>>) + ) + end, + fun(Range) -> + Range@1 = case erlang:element(2, Range) < 0 of + true -> + {range, + erlang:element(2, File_info) + erlang:element( + 2, + Range + ), + erlang:element(3, Range)}; + + false -> + Range + end, + End_is_invalid = begin + _pipe@2 = erlang:element(3, Range@1), + _pipe@3 = gleam@option:map( + _pipe@2, + fun(End) -> + ((End < 0) orelse (End >= erlang:element( + 2, + File_info + ))) + orelse (End < erlang:element(2, Range@1)) + end + ), + gleam@option:unwrap(_pipe@3, false) + end, + gleam@bool:guard( + ((erlang:element(2, Range@1) < 0) orelse (erlang:element( + 2, + Range@1 + ) + >= erlang:element(2, File_info))) + orelse End_is_invalid, + {error, + begin + _pipe@4 = response(416), + gleam@http@response:prepend_header( + _pipe@4, + <<"range"/utf8>>, + <<"bytes=*"/utf8>> + ) + end}, + fun() -> + Content_range = begin + End@1 = case erlang:element(3, Range@1) of + {some, L} -> + _pipe@5 = ((erlang:element( + 2, + Range@1 + ) + + L) + - 1), + erlang:integer_to_binary(_pipe@5); + + none -> + _pipe@6 = (erlang:element( + 2, + File_info + ) + - 1), + _pipe@7 = gleam@int:max(_pipe@6, 0), + erlang:integer_to_binary(_pipe@7) + end, + <<<<<<<<<<"bytes "/utf8, + (erlang:integer_to_binary( + erlang:element( + 2, + Range@1 + ) + ))/binary>>/binary, + "-"/utf8>>/binary, + End@1/binary>>/binary, + "/"/utf8>>/binary, + (erlang:integer_to_binary( + erlang:element(2, File_info) + ))/binary>> + end, + Content_length = case erlang:element(3, Range@1) of + {some, L@1} -> + erlang:integer_to_binary(L@1); + + none -> + erlang:integer_to_binary( + erlang:element(2, File_info) - erlang:element( + 2, + Range@1 + ) + ) + end, + _pipe@8 = {response, + 206, + erlang:element(3, Resp), + {file, + Path, + erlang:element(2, Range@1), + erlang:element(3, Range@1)}}, + _pipe@9 = gleam@http@response:set_header( + _pipe@8, + <<"content-length"/utf8>>, + Content_length + ), + _pipe@10 = gleam@http@response:set_header( + _pipe@9, + <<"accept-ranges"/utf8>>, + <<"bytes"/utf8>> + ), + _pipe@11 = gleam@http@response:set_header( + _pipe@10, + <<"content-range"/utf8>>, + Content_range + ), + {ok, _pipe@11} + end + ) + end + ) + end + ) + end, + case Result of + {error, Response} -> + Response; + + {ok, Response@1} -> + Response@1 + end. + +-file("src/wisp.gleam", 1194). +-spec multipart_content_disposition(list({binary(), binary()})) -> {ok, + {binary(), gleam@option:option(binary())}} | + {error, gleam@http@response:response(body())}. +multipart_content_disposition(Headers) -> + _pipe = begin + gleam@result:'try'( + gleam@list:key_find(Headers, <<"content-disposition"/utf8>>), + fun(Header) -> + gleam@result:'try'( + gleam@http:parse_content_disposition(Header), + fun(Header@1) -> + gleam@result:map( + gleam@list:key_find( + erlang:element(3, Header@1), + <<"name"/utf8>> + ), + fun(Name) -> + Filename = gleam@option:from_result( + gleam@list:key_find( + erlang:element(3, Header@1), + <<"filename"/utf8>> + ) + ), + {Name, Filename} + end + ) + end + ) + end + ) + end, + gleam@result:replace_error( + _pipe, + bad_request(<<"Invalid content-disposition"/utf8>>) + ). + +-file("src/wisp.gleam", 2056). +?DOC( + " Protects against Cross-Site Request Forgery (CSRF) attacks by checking the\n" + " `host` request header against the `origin` header or `referer` header.\n" + "\n" + " - Requests with the `Get` and `Head` methods are accepted.\n" + " - Requests with no `host` header are rejected with status 400: Bad Request.\n" + " - Requests with no `origin` or `referer` headers are accepted, but have the\n" + " `cookie` header removed to prevent CSRF attacks against cookie based\n" + " sessions.\n" + " - Requests with origin/referer headers that match their host header are\n" + " accepted.\n" + " - Requests with headers that don't match are rejected with status 400: Bad\n" + " Request.\n" + "\n" + " This middleware implements the [OWASP Verifying Origin With Standard Headers][1]\n" + " CSRF defense-in-depth technique. **Do not** allow `Get` or `Head` requests \n" + " to trigger side effects if relying only on this function and the SameSite\n" + " cookies feature for CSRF protection.\n" + "\n" + " This middleware and SameSite cookies typically is sufficient to protect\n" + " against CSRF attacks, but you may decide to employ [token based mitigation][2]\n" + " for more complete CSRF defence-in-depth.\n" + "\n" + " [1]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#using-standard-headers-to-verify-origin\n" + " [2]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#token-based-mitigation\n" + "\n" + " If you have routes that should be accessible from other origins then you\n" + " should not use this middleware for those routes.\n" +). +-spec csrf_known_header_protection( + gleam@http@request:request(wisp@internal:connection()), + fun((gleam@http@request:request(wisp@internal:connection())) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +csrf_known_header_protection(Request, Next) -> + Is_pure_method = case erlang:element(2, Request) of + head -> + true; + + get -> + true; + + _ -> + false + end, + gleam@bool:lazy_guard( + Is_pure_method, + fun() -> Next(Request) end, + fun() -> + Origin = case gleam@http@request:get_header( + Request, + <<"origin"/utf8>> + ) of + {error, _} -> + gleam@http@request:get_header(Request, <<"referer"/utf8>>); + + {ok, _} = O -> + O + end, + Host = gleam@http@request:get_header(Request, <<"host"/utf8>>), + case {Origin, Host} of + {_, {error, _}} -> + log_warning(<<"Host header missing from request"/utf8>>), + bad_request(<<"Invalid host"/utf8>>); + + {{error, _}, _} -> + Headers = gleam@list:filter( + erlang:element(3, Request), + fun(H) -> erlang:element(1, H) /= <<"cookie"/utf8>> end + ), + Request@1 = {request, + erlang:element(2, Request), + Headers, + erlang:element(4, Request), + erlang:element(5, Request), + erlang:element(6, Request), + erlang:element(7, Request), + erlang:element(8, Request), + erlang:element(9, Request)}, + Next(Request@1); + + {{ok, Origin@1}, {ok, Host@1}} -> + {Host_host, Host_port} = case gleam@string:split_once( + Host@1, + <<":"/utf8>> + ) of + {ok, {Host@2, Port}} -> + Port@1 = begin + _pipe = Port, + _pipe@1 = gleam_stdlib:parse_int(_pipe), + gleam@option:from_result(_pipe@1) + end, + {{some, Host@2}, Port@1}; + + _ -> + {{some, Host@1}, none} + end, + {uri, _, _, Origin_host, Origin_port, _, _, _} = begin + _pipe@2 = gleam_stdlib:uri_parse(Origin@1), + gleam@result:unwrap( + _pipe@2, + {uri, + none, + none, + none, + none, + <<""/utf8>>, + none, + none} + ) + end, + case (Host_host =:= Origin_host) andalso (Host_port =:= Origin_port) of + true -> + Next(Request); + + false -> + log_warning( + <<<<<<"Origin-host mismatch: "/utf8, + Host@1/binary>>/binary, + " "/utf8>>/binary, + Origin@1/binary>> + ), + bad_request(<<"Invalid origin"/utf8>>) + end + end + end + ). + +-file("src/wisp.gleam", 1208). +-spec read_chunk(buffered_reader(), integer()) -> {ok, + {bitstring(), + fun((integer()) -> {ok, wisp@internal:read()} | {error, nil})}} | + {error, gleam@http@response:response(body())}. +read_chunk(Reader, Chunk_size) -> + case buffered_read(Reader, Chunk_size) of + {error, _} -> + {error, bad_request(<<"Unexpected end of request body"/utf8>>)}; + + {ok, Chunk} -> + case Chunk of + {chunk, Chunk@1, Next} -> + {ok, {Chunk@1, Next}}; + + reading_finished -> + {error, + bad_request(<<"Unexpected end of request body"/utf8>>)} + end + end. + +-file("src/wisp.gleam", 1139). +-spec multipart_body( + buffered_reader(), + fun((bitstring()) -> {ok, gleam@http:multipart_body()} | + {error, gleam@http@response:response(body())}), + binary(), + integer(), + integer(), + fun((PAE, bitstring()) -> {ok, PAE} | + {error, gleam@http@response:response(body())}), + PAE +) -> {ok, {gleam@option:option(buffered_reader()), integer(), PAE}} | + {error, gleam@http@response:response(body())}. +multipart_body(Reader, Parse, Boundary, Chunk_size, Quota, Append, Data) -> + gleam@result:'try'( + read_chunk(Reader, Chunk_size), + fun(_use0) -> + {Chunk, Reader@1} = _use0, + Size_read = erlang:byte_size(Chunk), + gleam@result:'try'(Parse(Chunk), fun(Output) -> case Output of + {multipart_body, Parsed, Done, Remaining} -> + Used = (Size_read - erlang:byte_size(Remaining)) - 2, + Used@1 = case Done of + true -> + (Used - 4) - erlang:byte_size(Boundary); + + false -> + Used + end, + gleam@result:'try'( + decrement_quota(Quota, Used@1), + fun(Quota@1) -> + Reader@2 = {buffered_reader, + Reader@1, + Remaining}, + Reader@3 = case Done of + true -> + none; + + false -> + {some, Reader@2} + end, + gleam@result:map( + Append(Data, Parsed), + fun(Value) -> + {Reader@3, Quota@1, Value} + end + ) + end + ); + + {more_required_for_body, Chunk@1, Parse@1} -> + Parse@2 = fn_with_bad_request_error( + Parse@1, + <<"Invalid form encoding"/utf8>> + ), + Reader@4 = {buffered_reader, Reader@1, <<>>}, + gleam@result:'try'( + decrement_quota(Quota, Size_read), + fun(Quota@2) -> + gleam@result:'try'( + Append(Data, Chunk@1), + fun(Data@1) -> + multipart_body( + Reader@4, + Parse@2, + Boundary, + Chunk_size, + Quota@2, + Append, + Data@1 + ) + end + ) + end + ) + end end) + end + ). + +-file("src/wisp.gleam", 1222). +-spec multipart_headers( + buffered_reader(), + fun((bitstring()) -> {ok, gleam@http:multipart_headers()} | + {error, gleam@http@response:response(body())}), + integer(), + quotas() +) -> {ok, {list({binary(), binary()}), buffered_reader(), quotas()}} | + {error, gleam@http@response:response(body())}. +multipart_headers(Reader, Parse, Chunk_size, Quotas) -> + gleam@result:'try'( + read_chunk(Reader, Chunk_size), + fun(_use0) -> + {Chunk, Reader@1} = _use0, + gleam@result:'try'(Parse(Chunk), fun(Headers) -> case Headers of + {multipart_headers, Headers@1, Remaining} -> + Used = erlang:byte_size(Chunk) - erlang:byte_size( + Remaining + ), + gleam@result:map( + decrement_body_quota(Quotas, Used), + fun(Quotas@1) -> + Reader@2 = {buffered_reader, + Reader@1, + Remaining}, + {Headers@1, Reader@2, Quotas@1} + end + ); + + {more_required_for_headers, Parse@1} -> + Parse@2 = fun(Chunk@1) -> case Parse@1(Chunk@1) of + {ok, Parsed} -> + {ok, Parsed}; + + {error, _} -> + {error, + bad_request( + <<"Invalid form encoding"/utf8>> + )} + end end, + Reader@3 = {buffered_reader, Reader@1, <<>>}, + Size_read = erlang:byte_size(Chunk), + gleam@result:'try'( + decrement_body_quota(Quotas, Size_read), + fun(Quotas@2) -> + multipart_headers( + Reader@3, + Parse@2, + Chunk_size, + Quotas@2 + ) + end + ) + end end) + end + ). + +-file("src/wisp.gleam", 1053). +-spec read_multipart( + gleam@http@request:request(wisp@internal:connection()), + buffered_reader(), + binary(), + quotas(), + form_data() +) -> {ok, form_data()} | {error, gleam@http@response:response(body())}. +read_multipart(Request, Reader, Boundary, Quotas, Data) -> + Read_size = erlang:element(5, erlang:element(4, Request)), + Header_parser = fn_with_bad_request_error( + fun(_capture) -> + gleam@http:parse_multipart_headers(_capture, Boundary) + end, + <<"Invalid form encoding"/utf8>> + ), + Result = multipart_headers(Reader, Header_parser, Read_size, Quotas), + gleam@result:'try'( + Result, + fun(_use0) -> + {Headers, Reader@1, Quotas@1} = _use0, + gleam@result:'try'( + multipart_content_disposition(Headers), + fun(_use0@1) -> + {Name, Filename} = _use0@1, + Parse = fn_with_bad_request_error( + fun(_capture@1) -> + gleam@http:parse_multipart_body( + _capture@1, + Boundary + ) + end, + <<"Invalid form encoding"/utf8>> + ), + gleam@result:'try'(case Filename of + {some, File_name} -> + gleam@result:'try'( + or_500(new_temporary_file(Request)), + fun(Path) -> + Append = fun multipart_file_append/2, + Q = erlang:element(3, Quotas@1), + Result@1 = multipart_body( + Reader@1, + Parse, + Boundary, + Read_size, + Q, + Append, + Path + ), + gleam@result:map( + Result@1, + fun(_use0@2) -> + {Reader@2, Quota, _} = _use0@2, + Quotas@2 = {quotas, + erlang:element(2, Quotas@1), + Quota}, + File = {uploaded_file, + File_name, + Path}, + Data@1 = {form_data, + erlang:element(2, Data), + [{Name, File} | + erlang:element(3, Data)]}, + {Data@1, Reader@2, Quotas@2} + end + ) + end + ); + + none -> + Append@1 = fun(Data@2, Chunk) -> + {ok, gleam@bit_array:append(Data@2, Chunk)} + end, + Q@1 = erlang:element(2, Quotas@1), + Result@2 = multipart_body( + Reader@1, + Parse, + Boundary, + Read_size, + Q@1, + Append@1, + <<>> + ), + gleam@result:'try'( + Result@2, + fun(_use0@3) -> + {Reader@3, Quota@1, Value} = _use0@3, + Quotas@3 = {quotas, + Quota@1, + erlang:element(3, Quotas@1)}, + gleam@result:map( + case gleam@bit_array:to_string( + Value + ) of + {ok, String} -> + {ok, String}; + + {error, _} -> + {error, + bad_request( + <<"Invalid UTF-8"/utf8>> + )} + end, + fun(Value@1) -> + Data@3 = {form_data, + [{Name, Value@1} | + erlang:element(2, Data)], + erlang:element(3, Data)}, + {Data@3, Reader@3, Quotas@3} + end + ) + end + ) + end, fun(_use0@4) -> + {Data@4, Reader@4, Quotas@4} = _use0@4, + case Reader@4 of + {some, Reader@5} -> + read_multipart( + Request, + Reader@5, + Boundary, + Quotas@4, + Data@4 + ); + + none -> + {ok, + {form_data, + sort_keys(erlang:element(2, Data@4)), + sort_keys(erlang:element(3, Data@4))}} + end + end) + end + ) + end + ). + +-file("src/wisp.gleam", 1036). +-spec require_multipart_form( + gleam@http@request:request(wisp@internal:connection()), + binary(), + fun((form_data()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_multipart_form(Request, Boundary, Next) -> + Quotas = {quotas, + erlang:element(3, erlang:element(4, Request)), + erlang:element(4, erlang:element(4, Request))}, + Reader = {buffered_reader, + erlang:element(2, erlang:element(4, Request)), + <<>>}, + Result = read_multipart( + Request, + Reader, + Boundary, + Quotas, + {form_data, [], []} + ), + case Result of + {ok, Form_data} -> + Next(Form_data); + + {error, Response} -> + Response + end. + +-file("src/wisp.gleam", 937). +?DOC( + " A middleware which extracts form data from the body of a request that is\n" + " encoded as either `application/x-www-form-urlencoded` or\n" + " `multipart/form-data`.\n" + "\n" + " Extracted fields are sorted into alphabetical order by key, so if you wish\n" + " to use pattern matching the order can be relied upon.\n" + "\n" + " ```gleam\n" + " fn handle_request(request: Request) -> Response {\n" + " use form <- wisp.require_form(request)\n" + " case form.values {\n" + " [#(\"password\", pass), #(\"username\", username)] -> // ...\n" + " _ -> // ...\n" + " }\n" + " }\n" + " ```\n" + "\n" + " The `set_max_body_size`, `set_max_files_size`, and `set_read_chunk_size` can\n" + " be used to configure the reading of the request body.\n" + "\n" + " Any file uploads will streamed into temporary files on disc. These files are\n" + " automatically deleted when the request handler returns, so if you wish to\n" + " use them after the request has completed you will need to move them to a new\n" + " location.\n" + "\n" + " If the request does not have a recognised `content-type` header then a\n" + " response with status code 415: Unsupported media type will be returned\n" + " to the client.\n" + "\n" + " If the request body is larger than the `max_body_size` or `max_files_size`\n" + " limits then a response with status code 413: Content too large will be\n" + " returned to the client.\n" + "\n" + " If the body cannot be parsed successfully then a response with status\n" + " code 400: Bad request will be returned to the client.\n" +). +-spec require_form( + gleam@http@request:request(wisp@internal:connection()), + fun((form_data()) -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +require_form(Request, Next) -> + case gleam@list:key_find( + erlang:element(3, Request), + <<"content-type"/utf8>> + ) of + {ok, <<"application/x-www-form-urlencoded"/utf8>>} -> + require_urlencoded_form(Request, Next); + + {ok, <<"application/x-www-form-urlencoded;"/utf8, _/binary>>} -> + require_urlencoded_form(Request, Next); + + {ok, <<"multipart/form-data; boundary="/utf8, Boundary/binary>>} -> + require_multipart_form(Request, Boundary, Next); + + {ok, <<"multipart/form-data"/utf8>>} -> + bad_request(<<"Invalid form encoding"/utf8>>); + + _ -> + unsupported_media_type( + [<<"application/x-www-form-urlencoded"/utf8>>, + <<"multipart/form-data"/utf8>>] + ) + end. + +-file("src/wisp.gleam", 1605). +?DOC( + " Calculates etag for requested file and then checks for the request header `if-none-match`.\n" + "\n" + " If the header isn't present or the value doesn't match the newly generated etag, it returns the file with the newly generated etag.\n" + " Otherwise if the etag matches, it returns status 304 without the file, allowing the browser to use the cached version.\n" +). +-spec handle_etag( + gleam@http@response:response(body()), + gleam@http@request:request(wisp@internal:connection()), + simplifile:file_info() +) -> gleam@http@response:response(body()). +handle_etag(Resp, Req, File_info) -> + Etag = wisp@internal:generate_etag( + erlang:element(2, File_info), + erlang:element(10, File_info) + ), + case gleam@http@request:get_header(Req, <<"if-none-match"/utf8>>) of + {ok, Old_etag} when Old_etag =:= Etag -> + _pipe = response(304), + gleam@http@response:set_header(_pipe, <<"etag"/utf8>>, Etag); + + _ -> + gleam@http@response:set_header(Resp, <<"etag"/utf8>>, Etag) + end. + +-file("src/wisp.gleam", 1416). +?DOC( + " A middleware function that serves files from a directory, along with a\n" + " suitable `content-type` header for known file extensions.\n" + "\n" + " Files are sent using the `File` response body type, so they will be sent\n" + " directly to the client from the disc, without being read into memory.\n" + "\n" + " The `under` parameter is the request path prefix that must match for the\n" + " file to be served.\n" + "\n" + " | `under` | `from` | `request.path` | `file` |\n" + " |-----------|---------|--------------------|-------------------------|\n" + " | `/static` | `/data` | `/static/file.txt` | `/data/file.txt` |\n" + " | `` | `/data` | `/static/file.txt` | `/data/static/file.txt` |\n" + " | `/static` | `` | `/static/file.txt` | `file.txt` |\n" + "\n" + " This middleware will discard any `..` path segments in the request path to\n" + " prevent the client from accessing files outside of the directory. It is\n" + " advised not to serve a directory that contains your source code, application\n" + " configuration, database, or other private files.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " fn handle_request(req: Request) -> Response {\n" + " use <- wisp.serve_static(req, under: \"/static\", from: \"/public\")\n" + " // ...\n" + " }\n" + " ```\n" + "\n" + " Typically you static assets may be kept in your project in a directory\n" + " called `priv`. The `priv_directory` function can be used to get a path to\n" + " this directory.\n" + "\n" + " ```gleam\n" + " fn handle_request(req: Request) -> Response {\n" + " let assert Ok(priv) = priv_directory(\"my_application\")\n" + " use <- wisp.serve_static(req, under: \"/static\", from: priv)\n" + " // ...\n" + " }\n" + " ```\n" +). +-spec serve_static( + gleam@http@request:request(wisp@internal:connection()), + binary(), + binary(), + fun(() -> gleam@http@response:response(body())) +) -> gleam@http@response:response(body()). +serve_static(Req, Prefix, Directory, Handler) -> + Path = wisp@internal:remove_preceeding_slashes(erlang:element(8, Req)), + Prefix@1 = wisp@internal:remove_preceeding_slashes(Prefix), + case {erlang:element(2, Req), + gleam_stdlib:string_starts_with(Path, Prefix@1)} of + {get, true} -> + Path@1 = begin + _pipe = Path, + _pipe@1 = gleam@string:drop_start( + _pipe, + string:length(Prefix@1) + ), + _pipe@2 = gleam_stdlib:percent_decode(_pipe@1), + _pipe@3 = gleam@result:unwrap(_pipe@2, Path), + _pipe@4 = gleam@string:replace( + _pipe@3, + <<".."/utf8>>, + <<""/utf8>> + ), + filepath:join(Directory, _pipe@4) + end, + File_type = begin + _pipe@5 = erlang:element(8, Req), + _pipe@6 = gleam@string:split(_pipe@5, <<"."/utf8>>), + _pipe@7 = gleam@list:last(_pipe@6), + gleam@result:unwrap(_pipe@7, <<""/utf8>>) + end, + Mime_type = marceau:extension_to_mime_type(File_type), + Content_type = case Mime_type of + <<"application/json"/utf8>> -> + <>; + + <<"text/"/utf8, _/binary>> -> + <>; + + _ -> + Mime_type + end, + case simplifile_erl:file_info(Path@1) of + {ok, File_info} -> + case simplifile:file_info_type(File_info) of + file -> + _pipe@8 = gleam@http@response:new(200), + _pipe@9 = gleam@http@response:set_header( + _pipe@8, + <<"content-type"/utf8>>, + Content_type + ), + _pipe@10 = gleam@http@response:set_body( + _pipe@9, + {file, Path@1, 0, none} + ), + _pipe@11 = handle_etag(_pipe@10, Req, File_info), + handle_file_range_header( + _pipe@11, + Req, + File_info, + Path@1 + ); + + _ -> + Handler() + end; + + _ -> + Handler() + end; + + {_, _} -> + Handler() + end. diff --git a/examples/http_interactions/build/packages/wisp/src/wisp.gleam b/examples/http_interactions/build/packages/wisp/src/wisp.gleam new file mode 100644 index 0000000..2fb37fb --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp.gleam @@ -0,0 +1,2175 @@ +import exception +import filepath +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree.{type BytesTree} +import gleam/crypto +import gleam/dict.{type Dict} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode +import gleam/erlang/application +import gleam/erlang/atom.{type Atom} +import gleam/http.{type Method} +import gleam/http/cookie +import gleam/http/request.{type Request as HttpRequest} +import gleam/http/response.{ + type Response as HttpResponse, Response as HttpResponse, +} +import gleam/int +import gleam/json +import gleam/list +import gleam/option.{type Option} +import gleam/result +import gleam/string +import gleam/string_tree.{type StringTree} +import gleam/uri +import houdini +import logging +import marceau +import simplifile +import wisp/internal + +// +// Responses +// + +/// The body of a HTTP response, to be sent to the client. +/// +pub type Body { + /// A body of unicode text. + /// + /// If you have a `StringTree` you can use the `bytes_tree.from_string_tree` + /// function with the `Bytes` variant instead, as this will avoid the cost of + /// converting the tree into a string. + /// + Text(String) + /// A body of binary data, stored as a `BytesTree`. + /// + /// If you have a `BitArray` you can use the `bytes_tree.from_bit_array` + /// function to convert it. + /// + /// If you have a `StringTree` you can use the `bytes_tree.from_string_tree` + /// function to convert it. + /// + Bytes(BytesTree) + /// A body of the contents of a file. + /// + /// This will be sent efficiently using the `send_file` function of the + /// underlying HTTP server. The file will not be read into memory so it is + /// safe to send large files this way. + /// + File( + /// The path to the file on the server file system. + path: String, + /// The number of bytes to skip from the start of the file. Set to 0 for the whole file. + offset: Int, + /// The maximum number of bytes to send. Set to `None` for the whole file. + limit: Option(Int), + ) +} + +/// An alias for a HTTP response containing a `Body`. +pub type Response = + HttpResponse(Body) + +/// Create a response with the given status code. +/// +/// # Examples +/// +/// ```gleam +/// response(200) +/// // -> Response(200, [], Text("")) +/// ``` +/// +pub fn response(status: Int) -> Response { + HttpResponse(status, [], Text("")) +} + +/// Set the body of a response. +/// +/// # Examples +/// +/// ```gleam +/// response(200) +/// |> set_body(File("/tmp/myfile.txt", option.None)) +/// // -> Response(200, [], File("/tmp/myfile.txt", option.None)) +/// ``` +/// +pub fn set_body(response: Response, body: Body) -> Response { + response + |> response.set_body(body) +} + +/// Send a file from the disc as a file download. +/// +/// The operating system `send_file` function is used to efficiently send the +/// file over the network socket without reading the entire file into memory. +/// +/// The `content-disposition` header will be set to `attachment; +/// filename="name"` to ensure the file is downloaded by the browser. This is +/// especially good for files that the browser would otherwise attempt to open +/// as this can result in cross-site scripting vulnerabilities. +/// +/// If you wish to not set the `content-disposition` header you could use the +/// `set_body` function with the `File` body variant. +/// +/// # Examples +/// +/// ```gleam +/// response(200) +/// |> file_download(named: "myfile.txt", from: "/tmp/myfile.txt") +/// // -> Response( +/// // 200, +/// // [#("content-disposition", "attachment; filename=\"myfile.txt\"")], +/// // File("/tmp/myfile.txt", option.None), +/// // ) +/// ``` +/// +pub fn file_download( + response: Response, + named name: String, + from path: String, +) -> Response { + let name = uri.percent_encode(name) + response + |> response.set_header( + "content-disposition", + "attachment; filename=\"" <> name <> "\"", + ) + |> response.set_body(File(path:, offset: 0, limit: option.None)) +} + +/// Send a file from memory as a file download. +/// +/// If your file is already on the disc use `file_download` instead, to avoid +/// having to read the file into memory to send it. +/// +/// The `content-disposition` header will be set to `attachment; +/// filename="name"` to ensure the file is downloaded by the browser. This is +/// especially good for files that the browser would otherwise attempt to open +/// as this can result in cross-site scripting vulnerabilities. +/// +/// # Examples +/// +/// ```gleam +/// let content = bytes_tree.from_string("Hello, Joe!") +/// response(200) +/// |> file_download_from_memory(named: "myfile.txt", containing: content) +/// // -> Response( +/// // 200, +/// // [#("content-disposition", "attachment; filename=\"myfile.txt\"")], +/// // File("/tmp/myfile.txt", option.None), +/// // ) +/// ``` +/// +pub fn file_download_from_memory( + response: Response, + named name: String, + containing data: BytesTree, +) -> Response { + let name = uri.percent_encode(name) + response + |> response.set_header( + "content-disposition", + "attachment; filename=\"" <> name <> "\"", + ) + |> response.set_body(Bytes(data)) +} + +/// Create a HTML response. +/// +/// The body is expected to be valid HTML, though this is not validated. +/// The `content-type` header will be set to `text/html; charset=utf-8`. +/// +/// # Examples +/// +/// ```gleam +/// html_response("

Hello, Joe!

", 200) +/// // -> Response(200, [#("content-type", "text/html; charset=utf-8")], Text(body)) +/// ``` +/// +pub fn html_response(html: String, status: Int) -> Response { + HttpResponse( + status, + [#("content-type", "text/html; charset=utf-8")], + Text(html), + ) +} + +/// Create a JSON response. +/// +/// The body is expected to be valid JSON, though this is not validated. +/// The `content-type` header will be set to `application/json`. +/// +/// # Examples +/// +/// ```gleam +/// json_response("{\"name\": \"Joe\"}", 200) +/// // -> Response(200, [#("content-type", "application/json")], Text(body)) +/// ``` +/// +pub fn json_response(json: String, status: Int) -> Response { + HttpResponse( + status, + [#("content-type", "application/json; charset=utf-8")], + Text(json), + ) +} + +/// Set the body of a response to a given HTML document, and set the +/// `content-type` header to `text/html`. +/// +/// The body is expected to be valid HTML, though this is not validated. +/// +/// # Examples +/// +/// ```gleam +/// response(201) +/// |> html_body("

Hello, Joe!

") +/// // -> Response(201, [#("content-type", "text/html; charset=utf-8")], Text(body)) +/// ``` +/// +pub fn html_body(response: Response, html: String) -> Response { + response + |> response.set_body(Text(html)) + |> response.set_header("content-type", "text/html; charset=utf-8") +} + +/// Set the body of a response to a given JSON document, and set the +/// `content-type` header to `application/json`. +/// +/// The body is expected to be valid JSON, though this is not validated. +/// +/// # Examples +/// +/// ```gleam +/// response(201) +/// |> json_body("{\"name\": \"Joe\"}") +/// // -> Response(201, [#("content-type", "application/json; charset=utf-8")], Text(body)) +/// ``` +/// +pub fn json_body(response: Response, json: String) -> Response { + response + |> response.set_body(Text(json)) + |> response.set_header("content-type", "application/json; charset=utf-8") +} + +/// Set the body of a response to a given string tree. +/// +/// You likely want to also set the request `content-type` header to an +/// appropriate value for the format of the content. +/// +/// # Examples +/// +/// ```gleam +/// let body = string_tree.from_string("Hello, Joe!") +/// response(201) +/// |> string_tree_body(body) +/// // -> Response(201, [], Text(body)) +/// ``` +/// +pub fn string_tree_body(response: Response, content: StringTree) -> Response { + response + |> response.set_body(Bytes(bytes_tree.from_string_tree(content))) +} + +/// Set the body of a response to a given string. +/// +/// You likely want to also set the request `content-type` header to an +/// appropriate value for the format of the content. +/// +/// # Examples +/// +/// ```gleam +/// let body = +/// response(201) +/// |> string_body("Hello, Joe!") +/// // -> Response(201, [], Text("Hello, Joe")) +/// ``` +/// +pub fn string_body(response: Response, content: String) -> Response { + response + |> response.set_body(Text(content)) +} + +/// Escape a string so that it can be safely included in a HTML document. +/// +/// Any content provided by the user should be escaped before being included in +/// a HTML document to prevent cross-site scripting attacks. +/// +/// # Examples +/// +/// ```gleam +/// escape_html("

Hello, Joe!

") +/// // -> "<h1>Hello, Joe!</h1>" +/// ``` +/// +pub fn escape_html(content: String) -> String { + houdini.escape(content) +} + +/// Create a response with status code 405: Method Not Allowed. Use this +/// when a request does not have an appropriate method to be handled. +/// +/// The `allow` header will be set to a comma separated list of the permitted +/// methods. +/// +/// # Examples +/// +/// ```gleam +/// method_not_allowed(allowed: [Get, Post]) +/// // -> Response(405, [#("allow", "GET, POST")], Text("Method not allowed")) +/// ``` +/// +pub fn method_not_allowed(allowed methods: List(Method)) -> Response { + let allowed = + methods + |> list.map(http.method_to_string) + |> list.sort(string.compare) + |> string.join(", ") + |> string.uppercase + HttpResponse(405, [#("allow", allowed)], Text("Method not allowed")) +} + +/// Create a response with status code 200: OK. +/// +/// # Examples +/// +/// ```gleam +/// ok() +/// // -> Response(200, [#("content-type", "text/plain")], Text("OK")) +/// ``` +/// +pub fn ok() -> Response { + HttpResponse(200, [content_text], Text("OK")) +} + +/// Create a response with status code 201: Created. +/// +/// # Examples +/// +/// ```gleam +/// created() +/// // -> Response(201, [#("content-type", "text/plain")], Text("Created")) +/// ``` +/// +pub fn created() -> Response { + HttpResponse(201, [content_text], Text("Created")) +} + +/// Create a response with status code 202: Accepted. +/// +/// # Examples +/// +/// ```gleam +/// accepted() +/// // -> Response(202, [#("content-type", "text/plain")], Text("Accepted")) +/// ``` +/// +pub fn accepted() -> Response { + HttpResponse(202, [content_text], Text("Accepted")) +} + +/// Create a response with status code 303: See Other, and the `location` +/// header set to the given URL. Used to redirect the client to another page. +/// +/// # Examples +/// +/// ```gleam +/// redirect(to: "https://example.com") +/// // -> Response( +/// // 303, +/// // [#("location", "https://example.com"), #("context-type", "text/plain")], +/// // Text("You are being redirected: https://example.com"), +/// // ) +/// ``` +/// +pub fn redirect(to url: String) -> Response { + HttpResponse( + 303, + [#("location", url), content_text], + Text("You are being redirected: " <> url), + ) +} + +/// Create a response with status code 308: Permanent redirect, and the +/// `location` header set to the given URL. Used to redirect the client to +/// another page. +/// +/// This redirect is permanent and the client is expected to cache the new +/// location, using it for future requests. +/// +/// # Examples +/// +/// ```gleam +/// moved_permanently(to: "https://example.com") +/// // -> Response( +/// // 303, +/// // [#("location", "https://example.com")], +/// // Text("You are being redirected: https://example.com"), +/// // ) +/// ``` +/// +pub fn permanent_redirect(to url: String) -> Response { + HttpResponse( + 308, + [#("location", url), content_text], + Text("You are being redirected: " <> url), + ) +} + +/// Create a response with status code 204: No content. +/// +/// # Examples +/// +/// ```gleam +/// no_content() +/// // -> Response(204, [], Text("")) +/// ``` +/// +pub fn no_content() -> Response { + HttpResponse(204, [], Text("")) +} + +/// Create a response with status code 404: Not found. +/// +/// # Examples +/// +/// ```gleam +/// not_found() +/// // -> Response(404, [#("content-type", "text/plain")], Text("Not found")) +/// ``` +/// +pub fn not_found() -> Response { + HttpResponse(404, [content_text], Text("Not found")) +} + +/// Create a response with status code 400: Bad request. +/// +/// # Examples +/// +/// ```gleam +/// bad_request("Invalid JSON") +/// // -> Response(400, [#("content-type", "text/plain")], Text("Bad request: Invalid JSON")) +/// ``` +/// +pub fn bad_request(detail: String) -> Response { + let body = case detail { + "" -> "Bad request" + _ -> "Bad request: " <> detail + } + HttpResponse(400, [content_text], Text(body)) +} + +/// Create a response with status code 413: Content too large. +/// +/// # Examples +/// +/// ```gleam +/// content_too_large() +/// // -> Response(413, [#("content-type", "text/plain")], Text("Content too large")) +/// ``` +/// +pub fn content_too_large() -> Response { + HttpResponse(413, [content_text], Text("Content too large")) +} + +/// Create a response with status code 415: Unsupported media type. +/// +/// The `allow` header will be set to a comma separated list of the permitted +/// content-types. +/// +/// # Examples +/// +/// ```gleam +/// unsupported_media_type(accept: ["application/json", "text/plain"]) +/// // -> Response(415, [#("allow", "application/json, text/plain")], Text("Unsupported media type")) +/// ``` +/// +pub fn unsupported_media_type(accept acceptable: List(String)) -> Response { + let acceptable = string.join(acceptable, ", ") + HttpResponse( + 415, + [#("accept", acceptable), content_text], + Text("Unsupported media type"), + ) +} + +/// Create a response with status code 422: Unprocessable content. +/// +/// # Examples +/// +/// ```gleam +/// unprocessable_content() +/// // -> Response(422, [#("content-type", "text/plain")], Text("Unprocessable content")) +/// ``` +/// +pub fn unprocessable_content() -> Response { + HttpResponse(422, [content_text], Text("Unprocessable content")) +} + +/// Create a response with status code 500: Internal server error. +/// +/// # Examples +/// +/// ```gleam +/// internal_server_error() +/// // -> Response(500, [#("content-type", "text/plain")], Text("Internal server error")) +/// ``` +/// +pub fn internal_server_error() -> Response { + HttpResponse(500, [content_text], Text("Internal server error")) +} + +const content_text = #("content-type", "text/plain") + +const invalid_json = "Invalid JSON" + +const invalid_form = "Invalid form encoding" + +const invalid_utf8 = "Invalid UTF-8" + +const invalid_range = "Invalid range" + +const invalid_content_disposition = "Invalid content-disposition" + +const invalid_host = "Invalid host" + +const invalid_origin = "Invalid origin" + +const unexpected_end = "Unexpected end of request body" + +// +// Requests +// + +/// The connection to the client for a HTTP request. +/// +/// The body of the request can be read from this connection using functions +/// such as `require_multipart_body`. +/// +pub type Connection = + internal.Connection + +type BufferedReader { + BufferedReader(reader: internal.Reader, buffer: BitArray) +} + +type Quotas { + Quotas(body: Int, files: Int) +} + +fn decrement_body_quota(quotas: Quotas, size: Int) -> Result(Quotas, Response) { + let quotas = Quotas(..quotas, body: quotas.body - size) + case quotas.body < 0 { + True -> Error(content_too_large()) + False -> Ok(quotas) + } +} + +fn decrement_quota(quota: Int, size: Int) -> Result(Int, Response) { + case quota - size { + quota if quota < 0 -> Error(content_too_large()) + quota -> Ok(quota) + } +} + +fn buffered_read( + reader: BufferedReader, + chunk_size: Int, +) -> Result(internal.Read, Nil) { + case reader.buffer { + <<>> -> reader.reader(chunk_size) + _ -> Ok(internal.Chunk(reader.buffer, reader.reader)) + } +} + +/// Set the maximum permitted size of a request body of the request in bytes. +/// +/// If a body is larger than this size attempting to read the body will result +/// in a response with status code 413: Content too large will be returned to the +/// client. +/// +/// This limit only applies for headers and bodies that get read into memory. +/// Part of a multipart body that contain files and so are streamed to disc +/// instead use the `max_files_size` limit. +/// +pub fn set_max_body_size(request: Request, size: Int) -> Request { + internal.Connection(..request.body, max_body_size: size) + |> request.set_body(request, _) +} + +/// Get the maximum permitted size of a request body of the request in bytes. +/// +pub fn get_max_body_size(request: Request) -> Int { + request.body.max_body_size +} + +/// Set the secret key base used to sign cookies and other sensitive data. +/// +/// This key must be at least 64 bytes long and should be kept secret. Anyone +/// with this secret will be able to manipulate signed cookies and other sensitive +/// data. +/// +/// # Panics +/// +/// This function will panic if the key is less than 64 bytes long. +/// +pub fn set_secret_key_base(request: Request, key: String) -> Request { + case string.byte_size(key) < 64 { + True -> panic as "Secret key base must be at least 64 bytes long" + False -> + internal.Connection(..request.body, secret_key_base: key) + |> request.set_body(request, _) + } +} + +/// Get the secret key base used to sign cookies and other sensitive data. +/// +pub fn get_secret_key_base(request: Request) -> String { + request.body.secret_key_base +} + +/// Set the maximum permitted size of all files uploaded by a request, in bytes. +/// +/// If a request contains fails which are larger in total than this size +/// then attempting to read the body will result in a response with status code +/// 413: Content too large will be returned to the client. +/// +/// This limit only applies for files in a multipart body that get streamed to +/// disc. For headers and other content that gets read into memory use the +/// `max_body_size` limit. +/// +pub fn set_max_files_size(request: Request, size: Int) -> Request { + internal.Connection(..request.body, max_files_size: size) + |> request.set_body(request, _) +} + +/// Get the maximum permitted total size of a files uploaded by a request in +/// bytes. +/// +pub fn get_max_files_size(request: Request) -> Int { + request.body.max_files_size +} + +/// The the size limit for each chunk of the request body when read from the +/// client. +/// +/// This value is passed to the underlying web server when reading the body and +/// the exact size of chunks read depends on the server implementation. It most +/// likely will read chunks smaller than this size if not yet enough data has +/// been received from the client. +/// +pub fn set_read_chunk_size(request: Request, size: Int) -> Request { + internal.Connection(..request.body, read_chunk_size: size) + |> request.set_body(request, _) +} + +/// Get the size limit for each chunk of the request body when read from the +/// client. +/// +pub fn get_read_chunk_size(request: Request) -> Int { + request.body.read_chunk_size +} + +/// A convenient alias for a HTTP request with a Wisp connection as the body. +/// +pub type Request = + HttpRequest(internal.Connection) + +/// This middleware function ensures that the request has a specific HTTP +/// method, returning a response with status code 405: Method not allowed +/// if the method is not correct. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use <- wisp.require_method(request, http.Patch) +/// // ... +/// } +/// ``` +/// +pub fn require_method( + request: HttpRequest(t), + method: Method, + next: fn() -> Response, +) -> Response { + case request.method == method { + True -> next() + False -> method_not_allowed(allowed: [method]) + } +} + +// TODO: re-export once Gleam has a syntax for that +/// Return the non-empty segments of a request path. +/// +/// # Examples +/// +/// ```gleam +/// > request.new() +/// > |> request.set_path("/one/two/three") +/// > |> wisp.path_segments +/// ["one", "two", "three"] +/// ``` +/// +pub const path_segments = request.path_segments + +// TODO: re-export once Gleam has a syntax for that +/// Set a given header to a given value, replacing any existing value. +/// +/// # Examples +/// +/// ```gleam +/// > wisp.ok() +/// > |> wisp.set_header("content-type", "application/json") +/// Request(200, [#("content-type", "application/json")], Text("OK")) +/// ``` +/// +pub const set_header = response.set_header + +/// Parse the query parameters of a request into a list of key-value pairs. The +/// `key_find` function in the `gleam/list` stdlib module may be useful for +/// finding values in the list. +/// +/// Query parameter names do not have to be unique and so may appear multiple +/// times in the list. +/// +pub fn get_query(request: Request) -> List(#(String, String)) { + request.get_query(request) + |> result.unwrap([]) +} + +/// This function overrides an incoming POST request with a method given in +/// the request's `_method` query paramerter. This is useful as web browsers +/// typically only support GET and POST requests, but our application may +/// expect other HTTP methods that are more semantically correct. +/// +/// The methods PUT, PATCH, and DELETE are accepted for overriding, all others +/// are ignored. +/// +/// The `_method` query paramerter can be specified in a HTML form like so: +/// +/// ```html +///
+/// +///
+/// ``` +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// let request = wisp.method_override(request) +/// // The method has now been overridden if appropriate +/// } +/// ``` +/// +pub fn method_override(request: HttpRequest(a)) -> HttpRequest(a) { + use <- bool.guard(when: request.method != http.Post, return: request) + { + use query <- result.try(request.get_query(request)) + use value <- result.try(list.key_find(query, "_method")) + use method <- result.map(http.parse_method(value)) + + case method { + http.Put | http.Patch | http.Delete -> request.set_method(request, method) + _ -> request + } + } + |> result.unwrap(request) +} + +// TODO: don't always return content too large. Other errors are possible, such as +// network errors. +/// A middleware function which reads the entire body of the request as a string. +/// +/// This function does not cache the body in any way, so if you call this +/// function (or any other body reading function) more than once it may hang or +/// return an incorrect value, depending on the underlying web server. It is the +/// responsibility of the caller to cache the body if it is needed multiple +/// times. +/// +/// If the body is larger than the `max_body_size` limit then a response +/// with status code 413: Content too large will be returned to the client. +/// +/// If the body is found not to be valid UTF-8 then a response with +/// status code 400: Bad request will be returned to the client. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use body <- wisp.require_string_body(request) +/// // ... +/// } +/// ``` +/// +pub fn require_string_body( + request: Request, + next: fn(String) -> Response, +) -> Response { + case read_body_bits(request) { + Ok(body) -> + case bit_array.to_string(body) { + Ok(body) -> next(body) + Error(_) -> bad_request(invalid_utf8) + } + Error(_) -> content_too_large() + } +} + +// TODO: don't always return content too large. Other errors are possible, such as +// network errors. +/// A middleware function which reads the entire body of the request as a bit +/// string. +/// +/// This function does not cache the body in any way, so if you call this +/// function (or any other body reading function) more than once it may hang or +/// return an incorrect value, depending on the underlying web server. It is the +/// responsibility of the caller to cache the body if it is needed multiple +/// times. +/// +/// If the body is larger than the `max_body_size` limit then a response +/// with status code 413: Content too large will be returned to the client. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use body <- wisp.require_string_body(request) +/// // ... +/// } +/// ``` +/// +pub fn require_bit_array_body( + request: Request, + next: fn(BitArray) -> Response, +) -> Response { + case read_body_bits(request) { + Ok(body) -> next(body) + Error(_) -> content_too_large() + } +} + +// TODO: don't always return content to large. Other errors are possible, such as +// network errors. +/// Read the entire body of the request as a bit array. +/// +/// You may instead wish to use the `require_bit_array_body` or the +/// `require_string_body` middleware functions instead. +/// +/// This function does not cache the body in any way, so if you call this +/// function (or any other body reading function) more than once it may hang or +/// return an incorrect value, depending on the underlying web server. It is the +/// responsibility of the caller to cache the body if it is needed multiple +/// times. +/// +/// If the body is larger than the `max_body_size` limit then a response +/// with status code 413: Content too large will be returned to the client. +/// +pub fn read_body_bits(request: Request) -> Result(BitArray, Nil) { + let connection = request.body + read_body_loop( + connection.reader, + connection.read_chunk_size, + connection.max_body_size, + <<>>, + ) +} + +fn read_body_loop( + reader: internal.Reader, + read_chunk_size: Int, + max_body_size: Int, + accumulator: BitArray, +) -> Result(BitArray, Nil) { + use chunk <- result.try(reader(read_chunk_size)) + case chunk { + internal.ReadingFinished -> Ok(accumulator) + internal.Chunk(chunk, next) -> { + let accumulator = bit_array.append(accumulator, chunk) + case bit_array.byte_size(accumulator) > max_body_size { + True -> Error(Nil) + False -> + read_body_loop(next, read_chunk_size, max_body_size, accumulator) + } + } + } +} + +/// A middleware which extracts form data from the body of a request that is +/// encoded as either `application/x-www-form-urlencoded` or +/// `multipart/form-data`. +/// +/// Extracted fields are sorted into alphabetical order by key, so if you wish +/// to use pattern matching the order can be relied upon. +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use form <- wisp.require_form(request) +/// case form.values { +/// [#("password", pass), #("username", username)] -> // ... +/// _ -> // ... +/// } +/// } +/// ``` +/// +/// The `set_max_body_size`, `set_max_files_size`, and `set_read_chunk_size` can +/// be used to configure the reading of the request body. +/// +/// Any file uploads will streamed into temporary files on disc. These files are +/// automatically deleted when the request handler returns, so if you wish to +/// use them after the request has completed you will need to move them to a new +/// location. +/// +/// If the request does not have a recognised `content-type` header then a +/// response with status code 415: Unsupported media type will be returned +/// to the client. +/// +/// If the request body is larger than the `max_body_size` or `max_files_size` +/// limits then a response with status code 413: Content too large will be +/// returned to the client. +/// +/// If the body cannot be parsed successfully then a response with status +/// code 400: Bad request will be returned to the client. +/// +pub fn require_form( + request: Request, + next: fn(FormData) -> Response, +) -> Response { + case list.key_find(request.headers, "content-type") { + Ok("application/x-www-form-urlencoded") + | Ok("application/x-www-form-urlencoded;" <> _) -> + require_urlencoded_form(request, next) + + Ok("multipart/form-data; boundary=" <> boundary) -> + require_multipart_form(request, boundary, next) + + Ok("multipart/form-data") -> bad_request(invalid_form) + + _ -> + unsupported_media_type([ + "application/x-www-form-urlencoded", "multipart/form-data", + ]) + } +} + +/// This middleware function ensures that the request has a value for the +/// `content-type` header, returning a response with status code 415: +/// Unsupported media type if the header is not the expected value +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use <- wisp.require_content_type(request, "application/json") +/// // ... +/// } +/// ``` +/// +pub fn require_content_type( + request: Request, + expected: String, + next: fn() -> Response, +) -> Response { + case list.key_find(request.headers, "content-type") { + Ok(content_type) -> + // This header may have further such as `; charset=utf-8`, so discard + // that if it exists. + case string.split_once(content_type, ";") { + Ok(#(content_type, _)) if content_type == expected -> next() + _ if content_type == expected -> next() + _ -> unsupported_media_type([expected]) + } + + _ -> unsupported_media_type([expected]) + } +} + +/// A middleware which extracts JSON from the body of a request. +/// +/// ```gleam +/// fn handle_request(request: Request) -> Response { +/// use json <- wisp.require_json(request) +/// // decode and use JSON here... +/// } +/// ``` +/// +/// The `set_max_body_size` and `set_read_chunk_size` can be used to configure +/// the reading of the request body. +/// +/// If the request does not have the `content-type` set to `application/json` a +/// response with status code 415: Unsupported media type will be returned +/// to the client. +/// +/// If the request body is larger than the `max_body_size` or `max_files_size` +/// limits then a response with status code 413: Content too large will be +/// returned to the client. +/// +/// If the body cannot be parsed successfully then a response with status +/// code 400: Bad request will be returned to the client. +/// +pub fn require_json(request: Request, next: fn(Dynamic) -> Response) -> Response { + use <- require_content_type(request, "application/json") + use body <- require_string_body(request) + case json.parse(body, decode.dynamic) { + Ok(json) -> next(json) + Error(_) -> bad_request(invalid_json) + } +} + +fn require_urlencoded_form( + request: Request, + next: fn(FormData) -> Response, +) -> Response { + use body <- require_string_body(request) + case uri.parse_query(body) { + Ok(pairs) -> { + let pairs = sort_keys(pairs) + next(FormData(values: pairs, files: [])) + } + Error(_) -> bad_request(invalid_form) + } +} + +fn require_multipart_form( + request: Request, + boundary: String, + next: fn(FormData) -> Response, +) -> Response { + let quotas = + Quotas(files: request.body.max_files_size, body: request.body.max_body_size) + let reader = BufferedReader(request.body.reader, <<>>) + + let result = + read_multipart(request, reader, boundary, quotas, FormData([], [])) + case result { + Ok(form_data) -> next(form_data) + Error(response) -> response + } +} + +fn read_multipart( + request: Request, + reader: BufferedReader, + boundary: String, + quotas: Quotas, + data: FormData, +) -> Result(FormData, Response) { + let read_size = request.body.read_chunk_size + + // First we read the headers of the multipart part. + let header_parser = + fn_with_bad_request_error( + http.parse_multipart_headers(_, boundary), + invalid_form, + ) + let result = multipart_headers(reader, header_parser, read_size, quotas) + use #(headers, reader, quotas) <- result.try(result) + use #(name, filename) <- result.try(multipart_content_disposition(headers)) + + // Then we read the body of the part. + let parse = + fn_with_bad_request_error( + http.parse_multipart_body(_, boundary), + invalid_form, + ) + use #(data, reader, quotas) <- result.try(case filename { + // There is a file name, so we treat this as a file upload, streaming the + // contents to a temporary file and using the dedicated files size quota. + option.Some(file_name) -> { + use path <- result.try(or_500(new_temporary_file(request))) + let append = multipart_file_append + let q = quotas.files + let result = + multipart_body(reader, parse, boundary, read_size, q, append, path) + use #(reader, quota, _) <- result.map(result) + let quotas = Quotas(..quotas, files: quota) + let file = UploadedFile(path: path, file_name: file_name) + let data = FormData(..data, files: [#(name, file), ..data.files]) + #(data, reader, quotas) + } + + // No file name, this is a regular form value that we hold in memory. + option.None -> { + let append = fn(data, chunk) { Ok(bit_array.append(data, chunk)) } + let q = quotas.body + let result = + multipart_body(reader, parse, boundary, read_size, q, append, <<>>) + use #(reader, quota, value) <- result.try(result) + let quotas = Quotas(..quotas, body: quota) + use value <- result.map(case bit_array.to_string(value) { + Ok(string) -> Ok(string) + Error(_) -> Error(bad_request(invalid_utf8)) + }) + let data = FormData(..data, values: [#(name, value), ..data.values]) + #(data, reader, quotas) + } + }) + + case reader { + // There's at least one more part, read it. + option.Some(reader) -> + read_multipart(request, reader, boundary, quotas, data) + // There are no more parts, we're done. + option.None -> Ok(FormData(sort_keys(data.values), sort_keys(data.files))) + } +} + +fn multipart_file_append( + path: String, + chunk: BitArray, +) -> Result(String, Response) { + simplifile.append_bits(path, chunk) + |> or_500 + |> result.replace(path) +} + +fn or_500(result: Result(a, b)) -> Result(a, Response) { + case result { + Ok(value) -> Ok(value) + Error(error) -> { + log_error(string.inspect(error)) + Error(internal_server_error()) + } + } +} + +fn multipart_body( + reader: BufferedReader, + parse: fn(BitArray) -> Result(http.MultipartBody, Response), + boundary: String, + chunk_size: Int, + quota: Int, + append: fn(t, BitArray) -> Result(t, Response), + data: t, +) -> Result(#(Option(BufferedReader), Int, t), Response) { + use #(chunk, reader) <- result.try(read_chunk(reader, chunk_size)) + let size_read = bit_array.byte_size(chunk) + use output <- result.try(parse(chunk)) + + case output { + http.MultipartBody(parsed, done, remaining) -> { + // Decrement the quota by the number of bytes consumed. + let used = size_read - bit_array.byte_size(remaining) - 2 + let used = case done { + // If this is the last chunk, we need to account for the boundary. + True -> used - 4 - string.byte_size(boundary) + False -> used + } + use quota <- result.try(decrement_quota(quota, used)) + + let reader = BufferedReader(reader, remaining) + let reader = case done { + True -> option.None + False -> option.Some(reader) + } + use value <- result.map(append(data, parsed)) + #(reader, quota, value) + } + + http.MoreRequiredForBody(chunk, parse) -> { + let parse = fn_with_bad_request_error(parse, invalid_form) + let reader = BufferedReader(reader, <<>>) + use quota <- result.try(decrement_quota(quota, size_read)) + use data <- result.try(append(data, chunk)) + multipart_body(reader, parse, boundary, chunk_size, quota, append, data) + } + } +} + +fn fn_with_bad_request_error( + f: fn(a) -> Result(b, c), + error: String, +) -> fn(a) -> Result(b, Response) { + fn(a) { + case f(a) { + Ok(x) -> Ok(x) + Error(_) -> Error(bad_request(error)) + } + } +} + +fn multipart_content_disposition( + headers: List(http.Header), +) -> Result(#(String, Option(String)), Response) { + { + use header <- result.try(list.key_find(headers, "content-disposition")) + use header <- result.try(http.parse_content_disposition(header)) + use name <- result.map(list.key_find(header.parameters, "name")) + let filename = + option.from_result(list.key_find(header.parameters, "filename")) + #(name, filename) + } + |> result.replace_error(bad_request(invalid_content_disposition)) +} + +fn read_chunk( + reader: BufferedReader, + chunk_size: Int, +) -> Result(#(BitArray, internal.Reader), Response) { + case buffered_read(reader, chunk_size) { + Error(_) -> Error(bad_request(unexpected_end)) + Ok(chunk) -> + case chunk { + internal.Chunk(chunk, next) -> Ok(#(chunk, next)) + internal.ReadingFinished -> Error(bad_request(unexpected_end)) + } + } +} + +fn multipart_headers( + reader: BufferedReader, + parse: fn(BitArray) -> Result(http.MultipartHeaders, Response), + chunk_size: Int, + quotas: Quotas, +) -> Result(#(List(http.Header), BufferedReader, Quotas), Response) { + use #(chunk, reader) <- result.try(read_chunk(reader, chunk_size)) + use headers <- result.try(parse(chunk)) + + case headers { + http.MultipartHeaders(headers, remaining) -> { + let used = bit_array.byte_size(chunk) - bit_array.byte_size(remaining) + use quotas <- result.map(decrement_body_quota(quotas, used)) + let reader = BufferedReader(reader, remaining) + #(headers, reader, quotas) + } + http.MoreRequiredForHeaders(parse) -> { + let parse = fn(chunk) { + case parse(chunk) { + Ok(parsed) -> Ok(parsed) + Error(_) -> Error(bad_request(invalid_form)) + } + } + let reader = BufferedReader(reader, <<>>) + let size_read = bit_array.byte_size(chunk) + use quotas <- result.try(decrement_body_quota(quotas, size_read)) + multipart_headers(reader, parse, chunk_size, quotas) + } + } +} + +fn sort_keys(pairs: List(#(String, t))) -> List(#(String, t)) { + list.sort(pairs, fn(a, b) { string.compare(a.0, b.0) }) +} + +/// Data parsed from form sent in a request's body. +/// +pub type FormData { + FormData( + /// String values of the form's fields. + values: List(#(String, String)), + /// Uploaded files. + files: List(#(String, UploadedFile)), + ) +} + +pub type UploadedFile { + UploadedFile( + /// The name that was given to the file in the form. + /// This is user input and should not be trusted. + file_name: String, + /// The location of the file on the server. + /// This is a temporary file and will be deleted when the request has + /// finished being handled. + path: String, + ) +} + +// +// Middleware +// + +/// A middleware function that rescues crashes and returns a response +/// with status code 500: Internal server error. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(req: Request) -> Response { +/// use <- wisp.rescue_crashes +/// // ... +/// } +/// ``` +/// +pub fn rescue_crashes(handler: fn() -> Response) -> Response { + case exception.rescue(handler) { + Ok(response) -> response + Error(error) -> { + let #(kind, detail) = case error { + exception.Errored(detail) -> #(Errored, detail) + exception.Thrown(detail) -> #(Thrown, detail) + exception.Exited(detail) -> #(Exited, detail) + } + case decode.run(detail, atom_dict_decoder()) { + Ok(details) -> { + let c = atom.create("class") + log_error_dict(dict.insert(details, c, error_kind_to_dynamic(kind))) + Nil + } + Error(_) -> log_error(string.inspect(error)) + } + internal_server_error() + } + } +} + +@external(erlang, "gleam@function", "identity") +fn error_kind_to_dynamic(kind: ErrorKind) -> Dynamic + +fn atom_dict_decoder() -> decode.Decoder(Dict(Atom, Dynamic)) { + let atom = + decode.new_primitive_decoder("Atom", fn(data) { + case atom_from_dynamic(data) { + Ok(atom) -> Ok(atom) + Error(_) -> Error(atom.create("nil")) + } + }) + decode.dict(atom, decode.dynamic) +} + +@external(erlang, "wisp_ffi", "atom_from_dynamic") +fn atom_from_dynamic(data: Dynamic) -> Result(Atom, Nil) + +type DoNotLeak + +@external(erlang, "logger", "error") +fn log_error_dict(o: Dict(Atom, Dynamic)) -> DoNotLeak + +type ErrorKind { + Errored + Thrown + Exited +} + +// TODO: test, somehow. +/// A middleware function that logs details about the request and response. +/// +/// The format used logged by this middleware may change in future versions of +/// Wisp. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(req: Request) -> Response { +/// use <- wisp.log_request(req) +/// // ... +/// } +/// ``` +/// +pub fn log_request(req: Request, handler: fn() -> Response) -> Response { + let response = handler() + [ + int.to_string(response.status), + " ", + string.uppercase(http.method_to_string(req.method)), + " ", + req.path, + ] + |> string.concat + |> log_info + response +} + +/// A middleware function that serves files from a directory, along with a +/// suitable `content-type` header for known file extensions. +/// +/// Files are sent using the `File` response body type, so they will be sent +/// directly to the client from the disc, without being read into memory. +/// +/// The `under` parameter is the request path prefix that must match for the +/// file to be served. +/// +/// | `under` | `from` | `request.path` | `file` | +/// |-----------|---------|--------------------|-------------------------| +/// | `/static` | `/data` | `/static/file.txt` | `/data/file.txt` | +/// | `` | `/data` | `/static/file.txt` | `/data/static/file.txt` | +/// | `/static` | `` | `/static/file.txt` | `file.txt` | +/// +/// This middleware will discard any `..` path segments in the request path to +/// prevent the client from accessing files outside of the directory. It is +/// advised not to serve a directory that contains your source code, application +/// configuration, database, or other private files. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(req: Request) -> Response { +/// use <- wisp.serve_static(req, under: "/static", from: "/public") +/// // ... +/// } +/// ``` +/// +/// Typically you static assets may be kept in your project in a directory +/// called `priv`. The `priv_directory` function can be used to get a path to +/// this directory. +/// +/// ```gleam +/// fn handle_request(req: Request) -> Response { +/// let assert Ok(priv) = priv_directory("my_application") +/// use <- wisp.serve_static(req, under: "/static", from: priv) +/// // ... +/// } +/// ``` +/// +pub fn serve_static( + req: Request, + under prefix: String, + from directory: String, + next handler: fn() -> Response, +) -> Response { + let path = internal.remove_preceeding_slashes(req.path) + let prefix = internal.remove_preceeding_slashes(prefix) + case req.method, string.starts_with(path, prefix) { + http.Get, True -> { + let path = + path + |> string.drop_start(string.length(prefix)) + |> uri.percent_decode + |> result.unwrap(path) + |> string.replace(each: "..", with: "") + |> filepath.join(directory, _) + + let file_type = + req.path + |> string.split(on: ".") + |> list.last + |> result.unwrap("") + + let mime_type = marceau.extension_to_mime_type(file_type) + let content_type = case mime_type { + "application/json" | "text/" <> _ -> mime_type <> "; charset=utf-8" + _ -> mime_type + } + + case simplifile.file_info(path) { + Ok(file_info) -> + case simplifile.file_info_type(file_info) { + simplifile.File -> { + response.new(200) + |> response.set_header("content-type", content_type) + |> response.set_body(File(path:, offset: 0, limit: option.None)) + |> handle_etag(req, file_info) + |> handle_file_range_header(req, file_info, path) + } + _ -> handler() + } + _ -> handler() + } + } + _, _ -> handler() + } +} + +/// The value of a `range` request header. +/// +pub type Range { + Range( + /// The number of bytes to skip from the start of the content. 0 would be + /// the start of the file. + /// + /// A negative offset is an offset backwards from the end of the content. + offset: Int, + /// The maximum number of bytes in the range. `None` would mean the rest of + /// the file. + limit: Option(Int), + ) +} + +/// Parses the content of a range header. +/// +/// # Example +/// +/// ```gleam +/// wisp.parse_range_header("bytes=-64") +/// // -> Ok(Range(offset: -64, limit: option.None)) +/// ``` +/// +pub fn parse_range_header(range_header: String) -> Result(Range, Nil) { + case range_header { + "bytes=" <> range -> { + use #(start_str, end_str) <- result.try(range |> string.split_once("-")) + + case start_str, end_str { + // "range: bytes=-[tail]" + "", _ -> + int.parse(end_str) + |> result.map(fn(tail_offset) { + Range(offset: -tail_offset, limit: option.None) + }) + + // "range: bytes=[start]-" + _, "" -> + int.parse(start_str) + |> result.map(fn(offset) { Range(offset:, limit: option.None) }) + + // "range: bytes=[start]-[end]" + _, _ -> { + use offset <- result.try(int.parse(start_str)) + use end <- result.try(int.parse(end_str)) + + Ok(Range(offset:, limit: option.Some(end - offset + 1))) + } + } + } + _ -> Error(Nil) + } +} + +/// Checks for the `range` header and handles partial file reads. +/// +/// If the range request header is present, it will set the `accept-ranges`, +/// `content-range`, and `content-length` response headers. If the range +/// request header has a range that is out of bounds of the file, it will +/// respond with a `416 Range Not Satisfiable`. +/// +/// If the header isn't present, it returns the input response without changes. +fn handle_file_range_header( + resp: Response, + req: Request, + file_info: simplifile.FileInfo, + path: String, +) -> Response { + let result = { + use raw_range <- result.try( + request.get_header(req, "range") |> result.replace_error(resp), + ) + + use range <- result.try( + parse_range_header(raw_range) + |> result.replace_error(bad_request(invalid_range)), + ) + + let range = case range.offset < 0 { + True -> Range(..range, offset: file_info.size + range.offset) + False -> range + } + + let end_is_invalid = + range.limit + |> option.map(fn(end) { + end < 0 || end >= file_info.size || end < range.offset + }) + |> option.unwrap(False) + + use <- bool.guard( + range.offset < 0 || range.offset >= file_info.size || end_is_invalid, + Error( + response(416) + |> response.prepend_header("range", "bytes=*"), + ), + ) + + let content_range = { + let end = case range.limit { + option.Some(l) -> { range.offset + l - 1 } |> int.to_string + option.None -> { file_info.size - 1 } |> int.max(0) |> int.to_string + } + + "bytes " + <> int.to_string(range.offset) + <> "-" + <> end + <> "/" + <> int.to_string(file_info.size) + } + + let content_length = case range.limit { + option.Some(l) -> int.to_string(l) + option.None -> int.to_string(file_info.size - range.offset) + } + + response.Response( + 206, + resp.headers, + File(path:, offset: range.offset, limit: range.limit), + ) + |> response.set_header("content-length", content_length) + |> response.set_header("accept-ranges", "bytes") + |> response.set_header("content-range", content_range) + |> Ok + } + + case result { + Error(response) -> response + Ok(response) -> response + } +} + +/// Calculates etag for requested file and then checks for the request header `if-none-match`. +/// +/// If the header isn't present or the value doesn't match the newly generated etag, it returns the file with the newly generated etag. +/// Otherwise if the etag matches, it returns status 304 without the file, allowing the browser to use the cached version. +/// +fn handle_etag( + resp: Response, + req: Request, + file_info: simplifile.FileInfo, +) -> Response { + let etag = internal.generate_etag(file_info.size, file_info.mtime_seconds) + + case request.get_header(req, "if-none-match") { + Ok(old_etag) if old_etag == etag -> + response(304) + |> set_header("etag", etag) + _ -> response.set_header(resp, "etag", etag) + } +} + +/// A middleware function that converts `HEAD` requests to `GET` requests, +/// handles the request, and then discards the response body. This is useful so +/// that your application can handle `HEAD` requests without having to implement +/// handlers for them. +/// +/// The `x-original-method` header is set to `"HEAD"` for requests that were +/// originally `HEAD` requests. +/// +/// # Examples +/// +/// ```gleam +/// fn handle_request(req: Request) -> Response { +/// use req <- wisp.handle_head(req) +/// // ... +/// } +/// ``` +/// +pub fn handle_head( + req: Request, + next handler: fn(Request) -> Response, +) -> Response { + case req.method { + http.Head -> + req + |> request.set_method(http.Get) + |> request.prepend_header("x-original-method", "HEAD") + |> handler + _ -> handler(req) + } +} + +// +// File uploads +// + +/// Create a new temporary directory for the given request. +/// +/// If you are using the Mist adapter or another compliant web server +/// adapter then this file will be deleted for you when the request is complete. +/// Otherwise you will need to call the `delete_temporary_files` function +/// yourself. +/// +pub fn new_temporary_file( + request: Request, +) -> Result(String, simplifile.FileError) { + let directory = request.body.temporary_directory + use _ <- result.try(simplifile.create_directory_all(directory)) + let path = filepath.join(directory, internal.random_slug()) + use _ <- result.map(simplifile.create_file(path)) + path +} + +/// Delete any temporary files created for the given request. +/// +/// If you are using the Mist adapter or another compliant web server +/// adapter then this file will be deleted for you when the request is complete. +/// Otherwise you will need to call this function yourself. +/// +pub fn delete_temporary_files( + request: Request, +) -> Result(Nil, simplifile.FileError) { + case simplifile.delete(request.body.temporary_directory) { + Error(simplifile.Enoent) -> Ok(Nil) + other -> other + } +} + +/// Returns the path of a package's `priv` directory, where extra non-Gleam +/// or Erlang files are typically kept. +/// +/// Returns an error if no package was found with the given name. +/// +/// # Example +/// +/// ```gleam +/// > erlang.priv_directory("my_app") +/// // -> Ok("/some/location/my_app/priv") +/// ``` +/// +pub const priv_directory = application.priv_directory + +// +// Logging +// + +/// Configure the Erlang logger, setting the minimum log level to `info`, to be +/// called when your application starts. +/// +/// You may wish to use an alternative for this such as one provided by a more +/// sophisticated logging library. +/// +/// In future this function may be extended to change the output format. +/// +pub fn configure_logger() -> Nil { + logging.configure() +} + +/// Type to set the log level of the Erlang's logger +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub type LogLevel { + EmergencyLevel + AlertLevel + CriticalLevel + ErrorLevel + WarningLevel + NoticeLevel + InfoLevel + DebugLevel +} + +fn log_level_to_logging_log_level(log_level: LogLevel) -> logging.LogLevel { + case log_level { + EmergencyLevel -> logging.Emergency + AlertLevel -> logging.Alert + CriticalLevel -> logging.Critical + ErrorLevel -> logging.Error + WarningLevel -> logging.Warning + NoticeLevel -> logging.Notice + InfoLevel -> logging.Info + DebugLevel -> logging.Debug + } +} + +/// Set the log level of the Erlang logger to `log_level`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn set_logger_level(log_level: LogLevel) -> Nil { + logging.set_level(log_level_to_logging_log_level(log_level)) +} + +/// Log a message to the Erlang logger with the level of `emergency`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_emergency(message: String) -> Nil { + logging.log(logging.Emergency, message) +} + +/// Log a message to the Erlang logger with the level of `alert`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_alert(message: String) -> Nil { + logging.log(logging.Alert, message) +} + +/// Log a message to the Erlang logger with the level of `critical`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_critical(message: String) -> Nil { + logging.log(logging.Critical, message) +} + +/// Log a message to the Erlang logger with the level of `error`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_error(message: String) -> Nil { + logging.log(logging.Error, message) +} + +/// Log a message to the Erlang logger with the level of `warning`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_warning(message: String) -> Nil { + logging.log(logging.Warning, message) +} + +/// Log a message to the Erlang logger with the level of `notice`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_notice(message: String) -> Nil { + logging.log(logging.Notice, message) +} + +/// Log a message to the Erlang logger with the level of `info`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_info(message: String) -> Nil { + logging.log(logging.Info, message) +} + +/// Log a message to the Erlang logger with the level of `debug`. +/// +/// See the [Erlang logger documentation][1] for more information. +/// +/// [1]: https://www.erlang.org/doc/man/logger +/// +pub fn log_debug(message: String) -> Nil { + logging.log(logging.Debug, message) +} + +// +// Cryptography +// + +/// Generate a random string of the given length. +/// +/// This string is URL safe and generated in a strong-random fashion, making it +/// suitable for security purposes. +/// +pub fn random_string(length: Int) -> String { + internal.random_string(length) +} + +/// Sign a message which can later be verified using the `verify_signed_message` +/// function to detect if the message has been tampered with. +/// +/// Signed messages are not encrypted and can be read by anyone. They are not +/// suitable for storing sensitive information. +/// +/// This function uses the secret key base from the request. If the secret +/// changes then the signature will no longer be verifiable. +/// +pub fn sign_message( + request: Request, + message: BitArray, + algorithm: crypto.HashAlgorithm, +) -> String { + crypto.sign_message(message, <>, algorithm) +} + +/// Verify a signed message which was signed using the `sign_message` function. +/// +/// Returns the content of the message if the signature is valid, otherwise +/// returns an error. +/// +/// This function uses the secret key base from the request. If the secret +/// changes then the signature will no longer be verifiable. +/// +pub fn verify_signed_message( + request: Request, + message: String, +) -> Result(BitArray, Nil) { + crypto.verify_signed_message(message, <>) +} + +// +// Cookies +// + +/// Set a cookie on the response. After `max_age` seconds the cookie will be +/// expired by the client. +/// +/// If you wish for more control over the cookie attributes then you may want +/// to use the `gleam/http/cookie` module from the `gleam_http` package. +/// +/// # Security +/// +/// - `PlainText`: the cookie value is base64 encoded. This permits use of any +/// characters in the cookie, but it is possible for the client to edit the +/// cookie, potentially maliciously. +/// - `Signed`: the cookie value will be signed with `sign_message` and so +/// cannot be tampered with by the client. +/// +/// # `Secure` cookie attribute +/// +/// This function sets the `Secure` cookie attribute (with one exception detailed +/// below), ensuring that browsers will only send the cookie over HTTPS +/// connections. +/// +/// Most browsers consider localhost to be secure and permit `Secure` cookies +/// for those requests as well, but Safari does not. For cookies to work in +/// development for programmers using a browser like Safari the `Secure` +/// attribute will not be set if all these conditions are met: +/// +/// - The request scheme is `http://`. +/// - The request host is `localhost`, `127.0.0.1`, or `[::1]`. +/// - The `x-forwarded-proto` header has not been set, indicating that the +/// request is not from a reverse proxy such as Caddy or Nginx. +/// +/// # Examples +/// +/// Setting a plain text cookie that the client can read and modify: +/// +/// ```gleam +/// wisp.ok() +/// |> wisp.set_cookie(request, "id", "123", wisp.PlainText, 60 * 60) +/// ``` +/// +/// Setting a signed cookie that the client can read but not modify: +/// +/// ```gleam +/// wisp.ok() +/// |> wisp.set_cookie(request, "id", value, wisp.Signed, 60 * 60) +/// ``` +/// +pub fn set_cookie( + response response: Response, + request request: Request, + name name: String, + value value: String, + security security: Security, + max_age max_age: Int, +) -> Response { + let scheme = case request.host { + "localhost" | "127.0.0.1" | "[::1]" if request.scheme == http.Http -> + case request.get_header(request, "x-forwarded-proto") { + Ok(_) -> http.Https + Error(_) -> http.Http + } + _ -> http.Https + } + let attributes = + cookie.Attributes(..cookie.defaults(scheme), max_age: option.Some(max_age)) + let value = case security { + PlainText -> bit_array.base64_encode(<>, False) + Signed -> sign_message(request, <>, crypto.Sha512) + } + response + |> response.set_cookie(name, value, attributes) +} + +pub type Security { + /// The value is store as plain text without any additional security. + /// The client will be able to read and modify the value, and create new values. + PlainText + /// The value is signed to prevent modification. + /// The client will be able to read the value but not modify it, or create new + /// values. + Signed +} + +/// Get a cookie from the request. +/// +/// If a cookie is missing, found to be malformed, or the signature is invalid +/// for a signed cookie, then `Error(Nil)` is returned. +/// +/// # Security +/// +/// - `PlainText`: the cookie value is expected to be base64 encoded. +/// - `Signed`: the cookie value is expected to be signed with `sign_message`. +/// +/// # Examples +/// +/// ```gleam +/// wisp.get_cookie(request, "group", wisp.PlainText) +/// // -> Ok("A") +/// ``` +/// +pub fn get_cookie( + request request: Request, + name name: String, + security security: Security, +) -> Result(String, Nil) { + use value <- result.try( + request + |> request.get_cookies + |> list.key_find(name), + ) + use value <- result.try(case security { + PlainText -> bit_array.base64_decode(value) + Signed -> verify_signed_message(request, value) + }) + bit_array.to_string(value) +} + +// +// Testing +// + +@internal +pub fn create_canned_connection( + body: BitArray, + secret_key_base: String, +) -> internal.Connection { + internal.make_connection(canned_reader(body), secret_key_base) +} + +fn canned_reader(data: BitArray) -> internal.Reader { + fn(chunk_size) { + case bit_array.byte_size(data) { + 0 -> Ok(internal.ReadingFinished) + size -> { + let take = int.min(chunk_size, size) + let assert Ok(chunk) = bit_array.slice(data, 0, take) + let assert Ok(rest) = bit_array.slice(data, take, size - take) + Ok(internal.Chunk(chunk, canned_reader(rest))) + } + } + } +} + +/// Protects against Cross-Site Request Forgery (CSRF) attacks by checking the +/// `host` request header against the `origin` header or `referer` header. +/// +/// - Requests with the `Get` and `Head` methods are accepted. +/// - Requests with no `host` header are rejected with status 400: Bad Request. +/// - Requests with no `origin` or `referer` headers are accepted, but have the +/// `cookie` header removed to prevent CSRF attacks against cookie based +/// sessions. +/// - Requests with origin/referer headers that match their host header are +/// accepted. +/// - Requests with headers that don't match are rejected with status 400: Bad +/// Request. +/// +/// This middleware implements the [OWASP Verifying Origin With Standard Headers][1] +/// CSRF defense-in-depth technique. **Do not** allow `Get` or `Head` requests +/// to trigger side effects if relying only on this function and the SameSite +/// cookies feature for CSRF protection. +/// +/// This middleware and SameSite cookies typically is sufficient to protect +/// against CSRF attacks, but you may decide to employ [token based mitigation][2] +/// for more complete CSRF defence-in-depth. +/// +/// [1]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#using-standard-headers-to-verify-origin +/// [2]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#token-based-mitigation +/// +/// If you have routes that should be accessible from other origins then you +/// should not use this middleware for those routes. +/// +pub fn csrf_known_header_protection( + request: Request, + next: fn(Request) -> Response, +) -> Response { + let is_pure_method = case request.method { + http.Head | http.Get -> True + _ -> False + } + + // GET and HEAD are pure methods, so they SHOULD NOT perform side effects. + // If there are no side effects then there's no risk of CSRF attacks. + use <- bool.lazy_guard(when: is_pure_method, return: fn() { next(request) }) + + // The origin and referer headers are set by the browser. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referer + let origin = case request.get_header(request, "origin") { + Error(_) -> request.get_header(request, "referer") + Ok(_) as o -> o + } + + // The host header is required for HTTP1.1, but not HTTP1.0 or HTTP2/3. + // This would need to be modified to support HTTP3 in future. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Host + let host = request.get_header(request, "host") + + case origin, host { + _, Error(_) -> { + log_warning("Host header missing from request") + bad_request(invalid_host) + } + // If the request does not have origin headers then we cannot perform an + // origin check, so we must remove the cookie headers to ensure that a CSRF + // attack is not possible. + Error(_), _ -> { + let headers = list.filter(request.headers, fn(h) { h.0 != "cookie" }) + let request = request.Request(..request, headers:) + next(request) + } + Ok(origin), Ok(host) -> { + let #(host_host, host_port) = case string.split_once(host, ":") { + Ok(#(host, port)) -> { + let port = port |> int.parse |> option.from_result + #(option.Some(host), port) + } + _ -> #(option.Some(host), option.None) + } + + let uri.Uri(host: origin_host, port: origin_port, ..) = + uri.parse(origin) |> result.unwrap(uri.empty) + + case host_host == origin_host && host_port == origin_port { + True -> next(request) + False -> { + log_warning("Origin-host mismatch: " <> host <> " " <> origin) + bad_request(invalid_origin) + } + } + } + } +} + +/// Protects against cross-site-scripting (XSS) attacks using a nonce-based +/// content-security-policy (CSP). +/// +/// This middleware will provide a unique single use random string (a nonce) to +/// the handler, and set this CSP header on the response returned by the handler. +/// +/// ```txt +/// Content-Security-Policy: +/// script-src 'nonce-{NONCE}' 'strict-dynamic'; +/// object-src 'none'; +/// base-uri 'none'; +/// ``` +/// +/// This header causes the browser to be restricted in these ways: +/// +/// - Any ` +/// ``` +/// +/// It is recommended to add this middleware so that it applies to all routes +/// in your application. +/// +/// For more information about CSP see these articles: +/// +/// - +/// - +/// +pub fn content_security_policy_protection( + handle_request: fn(String) -> Response, +) -> Response { + let nonce = random_string(24) + let header = + "script-src 'nonce-" + <> nonce + <> "' 'strict-dynamic'; object-src 'none'; base-uri 'none'" + handle_request(nonce) + |> response.set_header("content-security-policy", header) +} diff --git a/examples/http_interactions/build/packages/wisp/src/wisp/internal.gleam b/examples/http_interactions/build/packages/wisp/src/wisp/internal.gleam new file mode 100644 index 0000000..5bfffa2 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp/internal.gleam @@ -0,0 +1,90 @@ +import directories +import filepath +import gleam/bit_array +import gleam/crypto +import gleam/int +import gleam/string + +// HELPERS + +// +// Requests +// + +/// The connection to the client for a HTTP request. +/// +/// The body of the request can be read from this connection using functions +/// such as `require_multipart_body`. +/// +pub type Connection { + Connection( + reader: Reader, + max_body_size: Int, + max_files_size: Int, + read_chunk_size: Int, + secret_key_base: String, + temporary_directory: String, + ) +} + +pub fn make_connection( + body_reader: Reader, + secret_key_base: String, +) -> Connection { + // Fallback to current working directory when no valid tmp directory exists + let prefix = case directories.tmp_dir() { + Ok(tmp_dir) -> tmp_dir <> "/gleam-wisp/" + Error(_) -> "./tmp/" + } + let temporary_directory = filepath.join(prefix, random_slug()) + Connection( + reader: body_reader, + max_body_size: 8_000_000, + max_files_size: 32_000_000, + read_chunk_size: 1_000_000, + temporary_directory: temporary_directory, + secret_key_base: secret_key_base, + ) +} + +pub type Reader = + fn(Int) -> Result(Read, Nil) + +pub type Read { + Chunk(BitArray, next: Reader) + ReadingFinished +} + +// +// Middleware Helpers +// + +pub fn remove_preceeding_slashes(string: String) -> String { + case string { + "/" <> rest -> remove_preceeding_slashes(rest) + _ -> string + } +} + +// +// Cryptography +// + +/// Generate a random string of the given length. +/// +pub fn random_string(length: Int) -> String { + crypto.strong_random_bytes(length) + |> bit_array.base64_url_encode(False) + |> string.slice(0, length) +} + +pub fn random_slug() -> String { + random_string(16) +} + +/// Generates etag using file size + file mtime as seconds +/// +/// Exmaple etag value: `2C-67A4D2F1` +pub fn generate_etag(file_size: Int, mtime_seconds: Int) -> String { + int.to_base16(file_size) <> "-" <> int.to_base16(mtime_seconds) +} diff --git a/examples/http_interactions/build/packages/wisp/src/wisp/simulate.gleam b/examples/http_interactions/build/packages/wisp/src/wisp/simulate.gleam new file mode 100644 index 0000000..fc790cd --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp/simulate.gleam @@ -0,0 +1,351 @@ +import gleam/bit_array +import gleam/bytes_tree +import gleam/crypto +import gleam/http +import gleam/http/request +import gleam/json.{type Json} +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import gleam/string +import gleam/uri +import simplifile +import wisp.{type Request, type Response, Bytes, File, Text} + +/// Create a test request that can be used to test your request handler +/// functions. +/// +/// If you are testing handlers that are intended to be accessed from a browser +/// (such as those that use cookies) consider using `browser_request` instead. +/// +pub fn request(method: http.Method, path: String) -> Request { + let #(path, query) = case string.split_once(path, "?") { + Ok(#(path, query)) -> #(path, Some(query)) + _ -> #(path, None) + } + let connection = wisp.create_canned_connection(<<>>, default_secret_key_base) + request.Request( + method: method, + headers: default_headers, + body: connection, + scheme: http.Https, + host: default_host, + port: None, + path: path, + query: query, + ) +} + +/// Create a test request with browser-set headers that can be used to test +/// your request handler functions. +/// +/// The `origin` header is set when using this function. +/// +pub fn browser_request(method: http.Method, path: String) -> Request { + request.Request(..request(method, path), headers: default_browser_headers) +} + +/// Continue a browser session from a previous request and response, adopting +/// the request cookies, and updating the cookies as specified by the response. +/// +pub fn session( + next_request: Request, + previous_request: Request, + previous_response: Response, +) -> Request { + let request = case list.key_find(previous_request.headers, "cookie") { + Ok(cookies) -> header(next_request, "cookie", cookies) + Error(_) -> next_request + } + + let set_cookies = + // Get the newly set cookies + list.key_filter(previous_response.headers, "set-cookie") + // Parse them to get the name, value, and attributes + |> list.map(fn(cookie) { + case string.split_once(cookie, ";") { + Ok(#(cookie, attributes)) -> { + let attributes = + string.split(attributes, ";") |> list.map(string.trim) + #(cookie, attributes) + } + Error(Nil) -> #(cookie, []) + } + }) + |> list.filter_map(fn(cookie) { + string.split_once(cookie.0, "=") + |> result.map(fn(split) { #(split.0, split.1, cookie.1) }) + }) + + // Set or remove the cookies as needed on the request + list.fold(set_cookies, request, fn(request, cookie) { + case list.contains(cookie.2, "Max-Age=0") { + True -> request.remove_cookie(request, cookie.0) + False -> request.set_cookie(request, cookie.0, cookie.1) + } + }) +} + +/// Add a text body to the request. +/// +/// The `content-type` header is set to `text/plain`. You may want to override +/// this with `request.set_header`. +/// +pub fn string_body(request: Request, text: String) -> Request { + let body = + text + |> bit_array.from_string + |> wisp.create_canned_connection(default_secret_key_base) + request + |> request.set_body(body) + |> request.set_header("content-type", "text/plain") +} + +/// Add a binary body to the request. +/// +/// The `content-type` header is set to `application/octet-stream`. You may +/// want to override/ this with `request.set_header`. +/// +pub fn bit_array_body(request: Request, data: BitArray) -> Request { + let body = wisp.create_canned_connection(data, default_secret_key_base) + request + |> request.set_body(body) + |> request.set_header("content-type", "application/octet-stream") +} + +/// Add HTML body to the request. +/// +/// The `content-type` header is set to `text/html; charset=utf-8`. +/// +pub fn html_body(request: Request, html: String) -> Request { + let body = + html + |> bit_array.from_string + |> wisp.create_canned_connection(default_secret_key_base) + request + |> request.set_body(body) + |> request.set_header("content-type", "text/html; charset=utf-8") +} + +/// Add a form data body to the request. +/// +/// The `content-type` header is set to `application/x-www-form-urlencoded`. +/// +pub fn form_body(request: Request, data: List(#(String, String))) -> Request { + let body = + uri.query_to_string(data) + |> bit_array.from_string + |> wisp.create_canned_connection(default_secret_key_base) + request + |> request.set_body(body) + |> request.set_header("content-type", "application/x-www-form-urlencoded") +} + +/// Add a JSON body to the request. +/// +/// The `content-type` header is set to `application/json`. +/// +pub fn json_body(request: Request, data: Json) -> Request { + let body = + json.to_string(data) + |> bit_array.from_string + |> wisp.create_canned_connection(default_secret_key_base) + request + |> request.set_body(body) + |> request.set_header("content-type", "application/json") +} + +/// Represents a file to be uploaded in a multipart form. +/// +pub type FileUpload { + FileUpload(file_name: String, content_type: String, content: BitArray) +} + +/// Add a multipart/form-data body to the request for testing file uploads +/// and form submissions. +/// +/// The `content-type` header is set to `multipart/form-data` with an +/// appropriate boundary. +/// +/// # Examples +/// +/// ```gleam +/// let file = UploadedFile( +/// file_name: "test.txt", +/// content_type: "text/plain", +/// content: <<"Hello, world!":utf8>> +/// ) +/// +/// simulate.request(http.Post, "/upload") +/// |> simulate.multipart_body([#("user", "joe")], [#("file", file)]) +/// ``` +/// +pub fn multipart_body( + request: Request, + values values: List(#(String, String)), + files files: List(#(String, FileUpload)), +) -> Request { + let boundary = crypto.strong_random_bytes(16) |> bit_array.base16_encode + let body_data = build_multipart_body(values, files, boundary) + let body = wisp.create_canned_connection(body_data, default_secret_key_base) + + request + |> request.set_body(body) + |> request.set_header( + "content-type", + "multipart/form-data; boundary=" <> boundary, + ) +} + +fn build_multipart_body( + form_values: List(#(String, String)), + files: List(#(String, FileUpload)), + boundary: String, +) -> BitArray { + // Append form parts + let body = + list.fold(form_values, <<>>, fn(acc, field) { + let #(name, value) = field + // Append this part to accumulator + << + acc:bits, + "--":utf8, + boundary:utf8, + "\r\n":utf8, + "Content-Disposition: form-data; name=\"":utf8, + name:utf8, + "\"\r\n":utf8, + "\r\n":utf8, + value:utf8, + "\r\n":utf8, + >> + }) + |> list.fold(files, _, fn(acc, file) { + // Append this file part to accumulator + << + acc:bits, + "--":utf8, + boundary:utf8, + "\r\n":utf8, + "Content-Disposition: form-data; name=\"":utf8, + file.0:utf8, + "\"; filename=\"":utf8, + { file.1 }.file_name:utf8, + "\"\r\n":utf8, + "Content-Type: ":utf8, + { file.1 }.content_type:utf8, + "\r\n":utf8, + "\r\n":utf8, + { file.1 }.content:bits, + "\r\n":utf8, + >> + }) + + // Append final boundary + <> +} + +/// Read a text body from a response. +/// +/// # Panics +/// +/// This function will panic if the response body is a file and the file cannot +/// be read, or if it does not contain valid UTF-8. +/// +pub fn read_body(response: Response) -> String { + case response.body { + Text(tree) -> tree + Bytes(bytes) -> { + let data = bytes_tree.to_bit_array(bytes) + let assert Ok(string) = bit_array.to_string(data) + as "the response body was non-UTF8 binary data" + string + } + File(path:, offset: 0, limit: None) -> { + let assert Ok(data) = simplifile.read_bits(path) + as "the body was a file, but the file could not be read" + let assert Ok(contents) = bit_array.to_string(data) + as "the body file was not valid UTF-8" + contents + } + File(path:, offset:, limit:) -> { + let assert Ok(data) = simplifile.read_bits(path) + as "the body was a file, but the file could not be read" + let byte_length = + limit |> option.unwrap(bit_array.byte_size(data) - offset) + let assert Ok(slice) = bit_array.slice(data, offset, byte_length) + as "the body was a file, but the limit and offset were invalid" + let assert Ok(string) = bit_array.to_string(slice) + as "the body file range was not valid UTF-8" + string + } + } +} + +/// Read a binary data body from a response. +/// +/// # Panics +/// +/// This function will panic if the response body is a file and the file cannot +/// be read. +/// +pub fn read_body_bits(response: Response) -> BitArray { + case response.body { + Bytes(tree) -> bytes_tree.to_bit_array(tree) + Text(tree) -> <> + File(path:, offset: 0, limit: None) -> { + let assert Ok(contents) = simplifile.read_bits(path) + as "the response body was a file, but the file could not be read" + contents + } + File(path:, offset:, limit:) -> { + let assert Ok(contents) = simplifile.read_bits(path) + as "the body was a file, but the file could not be read" + let limit = limit |> option.unwrap(bit_array.byte_size(contents)) + let assert Ok(sliced) = contents |> bit_array.slice(offset, limit) + as "the body was a file, but the limit and offset were invalid" + sliced + } + } +} + +/// Set a header on a request. +/// +pub fn header(request: Request, name: String, value: String) -> Request { + request.set_header(request, name, value) +} + +/// Set a cookie on the request. +/// +pub fn cookie( + request: Request, + name: String, + value: String, + security: wisp.Security, +) -> Request { + let value = case security { + wisp.PlainText -> bit_array.base64_encode(<>, False) + wisp.Signed -> wisp.sign_message(request, <>, crypto.Sha512) + } + request.set_cookie(request, name, value) +} + +/// The default secret key base used for test requests. +/// This should never be used outside of tests. +/// +pub const default_secret_key_base: String = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +/// The default host for test requests. +/// +pub const default_host: String = "wisp.example.com" + +/// The default headers for non-browser requests. +/// +pub const default_headers: List(#(String, String)) = [#("host", default_host)] + +/// The default headers for browser requests. +/// +pub const default_browser_headers: List(#(String, String)) = [ + #("origin", "https://" <> default_host), + #("host", default_host), +] diff --git a/examples/http_interactions/build/packages/wisp/src/wisp/wisp_mist.gleam b/examples/http_interactions/build/packages/wisp/src/wisp/wisp_mist.gleam new file mode 100644 index 0000000..5e80dec --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp/wisp_mist.gleam @@ -0,0 +1,105 @@ +import exception +import gleam/bytes_tree +import gleam/http/request.{type Request as HttpRequest} +import gleam/http/response.{type Response as HttpResponse} +import gleam/option +import gleam/result +import gleam/string +import mist +import wisp +import wisp/internal + +// +// Running the server +// + +/// Convert a Wisp request handler into a function that can be run with the Mist +/// web server. +/// +/// # Examples +/// +/// ```gleam +/// pub fn main() { +/// let secret_key_base = "..." +/// let assert Ok(_) = +/// handle_request +/// |> wisp_mist.handler(secret_key_base) +/// |> mist.new +/// |> mist.port(8000) +/// |> mist.start_http +/// process.sleep_forever() +/// } +/// ``` +/// +/// The secret key base is used for signing and encryption. To be able to +/// verify and decrypt messages you will need to use the same key each time +/// your program is run. Keep this value secret! Malicious people with this +/// value will likely be able to hack your application. +/// +pub fn handler( + handler: fn(wisp.Request) -> wisp.Response, + secret_key_base: String, +) -> fn(HttpRequest(mist.Connection)) -> HttpResponse(mist.ResponseData) { + fn(request: HttpRequest(_)) { + let connection = + internal.make_connection(mist_body_reader(request), secret_key_base) + let request = request.set_body(request, connection) + + use <- exception.defer(fn() { + let assert Ok(_) = wisp.delete_temporary_files(request) + }) + + let response = + request + |> handler + |> mist_response + + response + } +} + +fn mist_body_reader(request: HttpRequest(mist.Connection)) -> internal.Reader { + case mist.stream(request) { + Error(_) -> fn(_) { Ok(internal.ReadingFinished) } + Ok(stream) -> fn(size) { wrap_mist_chunk(stream(size)) } + } +} + +fn wrap_mist_chunk( + chunk: Result(mist.Chunk, mist.ReadError), +) -> Result(internal.Read, Nil) { + chunk + |> result.replace_error(Nil) + |> result.map(fn(chunk) { + case chunk { + mist.Done -> internal.ReadingFinished + mist.Chunk(data, consume) -> + internal.Chunk(data, fn(size) { wrap_mist_chunk(consume(size)) }) + } + }) +} + +fn mist_response(response: wisp.Response) -> HttpResponse(mist.ResponseData) { + let body = case response.body { + wisp.Text(text) -> mist.Bytes(bytes_tree.from_string(text)) + wisp.Bytes(bytes) -> mist.Bytes(bytes) + wisp.File(path:, offset:, limit:) -> mist_send_file(path, offset, limit) + } + response + |> response.set_body(body) +} + +fn mist_send_file( + path: String, + offset: Int, + limit: option.Option(Int), +) -> mist.ResponseData { + case mist.send_file(path, offset:, limit:) { + Ok(body) -> body + Error(error) -> { + wisp.log_error(string.inspect(error)) + // TODO: return 500 + mist.Bytes(bytes_tree.new()) + } + } +} diff --git a/examples/http_interactions/build/packages/wisp/src/wisp@internal.erl b/examples/http_interactions/build/packages/wisp/src/wisp@internal.erl new file mode 100644 index 0000000..e2c3c72 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp@internal.erl @@ -0,0 +1,81 @@ +-module(wisp@internal). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/wisp/internal.gleam"). +-export([remove_preceeding_slashes/1, random_string/1, random_slug/0, make_connection/2, generate_etag/2]). +-export_type([connection/0, read/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +?MODULEDOC(false). + +-type connection() :: {connection, + fun((integer()) -> {ok, read()} | {error, nil}), + integer(), + integer(), + integer(), + binary(), + binary()}. + +-type read() :: {chunk, + bitstring(), + fun((integer()) -> {ok, read()} | {error, nil})} | + reading_finished. + +-file("src/wisp/internal.gleam", 62). +?DOC(false). +-spec remove_preceeding_slashes(binary()) -> binary(). +remove_preceeding_slashes(String) -> + case String of + <<"/"/utf8, Rest/binary>> -> + remove_preceeding_slashes(Rest); + + _ -> + String + end. + +-file("src/wisp/internal.gleam", 75). +?DOC(false). +-spec random_string(integer()) -> binary(). +random_string(Length) -> + _pipe = crypto:strong_rand_bytes(Length), + _pipe@1 = gleam@bit_array:base64_url_encode(_pipe, false), + gleam@string:slice(_pipe@1, 0, Length). + +-file("src/wisp/internal.gleam", 81). +?DOC(false). +-spec random_slug() -> binary(). +random_slug() -> + random_string(16). + +-file("src/wisp/internal.gleam", 30). +?DOC(false). +-spec make_connection(fun((integer()) -> {ok, read()} | {error, nil}), binary()) -> connection(). +make_connection(Body_reader, Secret_key_base) -> + Prefix = case directories:tmp_dir() of + {ok, Tmp_dir} -> + <>; + + {error, _} -> + <<"./tmp/"/utf8>> + end, + Temporary_directory = filepath:join(Prefix, random_slug()), + {connection, + Body_reader, + 8000000, + 32000000, + 1000000, + Secret_key_base, + Temporary_directory}. + +-file("src/wisp/internal.gleam", 88). +?DOC(false). +-spec generate_etag(integer(), integer()) -> binary(). +generate_etag(File_size, Mtime_seconds) -> + <<<<(gleam@int:to_base16(File_size))/binary, "-"/utf8>>/binary, + (gleam@int:to_base16(Mtime_seconds))/binary>>. diff --git a/examples/http_interactions/build/packages/wisp/src/wisp@simulate.erl b/examples/http_interactions/build/packages/wisp/src/wisp@simulate.erl new file mode 100644 index 0000000..738397b --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp@simulate.erl @@ -0,0 +1,607 @@ +-module(wisp@simulate). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/wisp/simulate.gleam"). +-export([read_body/1, read_body_bits/1, header/3, session/3, cookie/4, string_body/2, bit_array_body/2, html_body/2, form_body/2, json_body/2, multipart_body/3, request/2, browser_request/2]). +-export_type([file_upload/0]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-type file_upload() :: {file_upload, binary(), binary(), bitstring()}. + +-file("src/wisp/simulate.gleam", 200). +-spec build_multipart_body( + list({binary(), binary()}), + list({binary(), file_upload()}), + binary() +) -> bitstring(). +build_multipart_body(Form_values, Files, Boundary) -> + Body = begin + _pipe = gleam@list:fold( + Form_values, + <<>>, + fun(Acc, Field) -> + {Name, Value} = Field, + <> + end + ), + gleam@list:fold( + Files, + _pipe, + fun(Acc@1, File) -> + <> + end + ) + end, + <>. + +-file("src/wisp/simulate.gleam", 255). +?DOC( + " Read a text body from a response.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic if the response body is a file and the file cannot\n" + " be read, or if it does not contain valid UTF-8.\n" +). +-spec read_body(gleam@http@response:response(wisp:body())) -> binary(). +read_body(Response) -> + case erlang:element(4, Response) of + {text, Tree} -> + Tree; + + {bytes, Bytes} -> + Data = erlang:list_to_bitstring(Bytes), + String@1 = case gleam@bit_array:to_string(Data) of + {ok, String} -> String; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"the response body was non-UTF8 binary data"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 260, + value => _assert_fail, + start => 7570, + 'end' => 7619, + pattern_start => 7581, + pattern_end => 7591}) + end, + String@1; + + {file, Path, 0, none} -> + Data@2 = case simplifile_erl:read_bits(Path) of + {ok, Data@1} -> Data@1; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body was a file, but the file could not be read"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 265, + value => _assert_fail@1, + start => 7746, + 'end' => 7794, + pattern_start => 7757, + pattern_end => 7765}) + end, + Contents@1 = case gleam@bit_array:to_string(Data@2) of + {ok, Contents} -> Contents; + _assert_fail@2 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body file was not valid UTF-8"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 267, + value => _assert_fail@2, + start => 7866, + 'end' => 7917, + pattern_start => 7877, + pattern_end => 7889}) + end, + Contents@1; + + {file, Path@1, Offset, Limit} -> + Data@4 = case simplifile_erl:read_bits(Path@1) of + {ok, Data@3} -> Data@3; + _assert_fail@3 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body was a file, but the file could not be read"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 272, + value => _assert_fail@3, + start => 8030, + 'end' => 8078, + pattern_start => 8041, + pattern_end => 8049}) + end, + Byte_length = begin + _pipe = Limit, + gleam@option:unwrap(_pipe, erlang:byte_size(Data@4) - Offset) + end, + Slice@1 = case gleam_stdlib:bit_array_slice( + Data@4, + Offset, + Byte_length + ) of + {ok, Slice} -> Slice; + _assert_fail@4 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body was a file, but the limit and offset were invalid"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 276, + value => _assert_fail@4, + start => 8241, + 'end' => 8306, + pattern_start => 8252, + pattern_end => 8261}) + end, + String@3 = case gleam@bit_array:to_string(Slice@1) of + {ok, String@2} -> String@2; + _assert_fail@5 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body file range was not valid UTF-8"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body"/utf8>>, + line => 278, + value => _assert_fail@5, + start => 8385, + 'end' => 8435, + pattern_start => 8396, + pattern_end => 8406}) + end, + String@3 + end. + +-file("src/wisp/simulate.gleam", 292). +?DOC( + " Read a binary data body from a response.\n" + "\n" + " # Panics\n" + "\n" + " This function will panic if the response body is a file and the file cannot\n" + " be read.\n" +). +-spec read_body_bits(gleam@http@response:response(wisp:body())) -> bitstring(). +read_body_bits(Response) -> + case erlang:element(4, Response) of + {bytes, Tree} -> + erlang:list_to_bitstring(Tree); + + {text, Tree@1} -> + <>; + + {file, Path, 0, none} -> + Contents@1 = case simplifile_erl:read_bits(Path) of + {ok, Contents} -> Contents; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"the response body was a file, but the file could not be read"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body_bits"/utf8>>, + line => 297, + value => _assert_fail, + start => 8889, + 'end' => 8941, + pattern_start => 8900, + pattern_end => 8912}) + end, + Contents@1; + + {file, Path@1, Offset, Limit} -> + Contents@3 = case simplifile_erl:read_bits(Path@1) of + {ok, Contents@2} -> Contents@2; + _assert_fail@1 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body was a file, but the file could not be read"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body_bits"/utf8>>, + line => 302, + value => _assert_fail@1, + start => 9081, + 'end' => 9133, + pattern_start => 9092, + pattern_end => 9104}) + end, + Limit@1 = begin + _pipe = Limit, + gleam@option:unwrap(_pipe, erlang:byte_size(Contents@3)) + end, + Sliced@1 = case begin + _pipe@1 = Contents@3, + gleam_stdlib:bit_array_slice(_pipe@1, Offset, Limit@1) + end of + {ok, Sliced} -> Sliced; + _assert_fail@2 -> + erlang:error(#{gleam_error => let_assert, + message => <<"the body was a file, but the limit and offset were invalid"/utf8>>, + file => <>, + module => <<"wisp/simulate"/utf8>>, + function => <<"read_body_bits"/utf8>>, + line => 305, + value => _assert_fail@2, + start => 9277, + 'end' => 9343, + pattern_start => 9288, + pattern_end => 9298}) + end, + Sliced@1 + end. + +-file("src/wisp/simulate.gleam", 314). +?DOC(" Set a header on a request.\n"). +-spec header( + gleam@http@request:request(wisp@internal:connection()), + binary(), + binary() +) -> gleam@http@request:request(wisp@internal:connection()). +header(Request, Name, Value) -> + gleam@http@request:set_header(Request, Name, Value). + +-file("src/wisp/simulate.gleam", 51). +?DOC( + " Continue a browser session from a previous request and response, adopting\n" + " the request cookies, and updating the cookies as specified by the response.\n" +). +-spec session( + gleam@http@request:request(wisp@internal:connection()), + gleam@http@request:request(wisp@internal:connection()), + gleam@http@response:response(wisp:body()) +) -> gleam@http@request:request(wisp@internal:connection()). +session(Next_request, Previous_request, Previous_response) -> + Request = case gleam@list:key_find( + erlang:element(3, Previous_request), + <<"cookie"/utf8>> + ) of + {ok, Cookies} -> + header(Next_request, <<"cookie"/utf8>>, Cookies); + + {error, _} -> + Next_request + end, + Set_cookies = begin + _pipe = gleam@list:key_filter( + erlang:element(3, Previous_response), + <<"set-cookie"/utf8>> + ), + _pipe@2 = gleam@list:map( + _pipe, + fun(Cookie) -> case gleam@string:split_once(Cookie, <<";"/utf8>>) of + {ok, {Cookie@1, Attributes}} -> + Attributes@1 = begin + _pipe@1 = gleam@string:split( + Attributes, + <<";"/utf8>> + ), + gleam@list:map(_pipe@1, fun gleam@string:trim/1) + end, + {Cookie@1, Attributes@1}; + + {error, nil} -> + {Cookie, []} + end end + ), + gleam@list:filter_map( + _pipe@2, + fun(Cookie@2) -> + _pipe@3 = gleam@string:split_once( + erlang:element(1, Cookie@2), + <<"="/utf8>> + ), + gleam@result:map( + _pipe@3, + fun(Split) -> + {erlang:element(1, Split), + erlang:element(2, Split), + erlang:element(2, Cookie@2)} + end + ) + end + ) + end, + gleam@list:fold( + Set_cookies, + Request, + fun(Request@1, Cookie@3) -> + case gleam@list:contains( + erlang:element(3, Cookie@3), + <<"Max-Age=0"/utf8>> + ) of + true -> + gleam@http@request:remove_cookie( + Request@1, + erlang:element(1, Cookie@3) + ); + + false -> + gleam@http@request:set_cookie( + Request@1, + erlang:element(1, Cookie@3), + erlang:element(2, Cookie@3) + ) + end + end + ). + +-file("src/wisp/simulate.gleam", 320). +?DOC(" Set a cookie on the request.\n"). +-spec cookie( + gleam@http@request:request(wisp@internal:connection()), + binary(), + binary(), + wisp:security() +) -> gleam@http@request:request(wisp@internal:connection()). +cookie(Request, Name, Value, Security) -> + Value@1 = case Security of + plain_text -> + gleam_stdlib:base64_encode(<>, false); + + signed -> + wisp:sign_message(Request, <>, sha512) + end, + gleam@http@request:set_cookie(Request, Name, Value@1). + +-file("src/wisp/simulate.gleam", 94). +?DOC( + " Add a text body to the request.\n" + " \n" + " The `content-type` header is set to `text/plain`. You may want to override\n" + " this with `request.set_header`.\n" +). +-spec string_body( + gleam@http@request:request(wisp@internal:connection()), + binary() +) -> gleam@http@request:request(wisp@internal:connection()). +string_body(Request, Text) -> + Body = begin + _pipe = Text, + _pipe@1 = gleam_stdlib:identity(_pipe), + wisp:create_canned_connection( + _pipe@1, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ) + end, + _pipe@2 = Request, + _pipe@3 = gleam@http@request:set_body(_pipe@2, Body), + gleam@http@request:set_header( + _pipe@3, + <<"content-type"/utf8>>, + <<"text/plain"/utf8>> + ). + +-file("src/wisp/simulate.gleam", 109). +?DOC( + " Add a binary body to the request.\n" + " \n" + " The `content-type` header is set to `application/octet-stream`. You may\n" + " want to override/ this with `request.set_header`.\n" +). +-spec bit_array_body( + gleam@http@request:request(wisp@internal:connection()), + bitstring() +) -> gleam@http@request:request(wisp@internal:connection()). +bit_array_body(Request, Data) -> + Body = wisp:create_canned_connection( + Data, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ), + _pipe = Request, + _pipe@1 = gleam@http@request:set_body(_pipe, Body), + gleam@http@request:set_header( + _pipe@1, + <<"content-type"/utf8>>, + <<"application/octet-stream"/utf8>> + ). + +-file("src/wisp/simulate.gleam", 120). +?DOC( + " Add HTML body to the request.\n" + " \n" + " The `content-type` header is set to `text/html; charset=utf-8`.\n" +). +-spec html_body( + gleam@http@request:request(wisp@internal:connection()), + binary() +) -> gleam@http@request:request(wisp@internal:connection()). +html_body(Request, Html) -> + Body = begin + _pipe = Html, + _pipe@1 = gleam_stdlib:identity(_pipe), + wisp:create_canned_connection( + _pipe@1, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ) + end, + _pipe@2 = Request, + _pipe@3 = gleam@http@request:set_body(_pipe@2, Body), + gleam@http@request:set_header( + _pipe@3, + <<"content-type"/utf8>>, + <<"text/html; charset=utf-8"/utf8>> + ). + +-file("src/wisp/simulate.gleam", 134). +?DOC( + " Add a form data body to the request.\n" + " \n" + " The `content-type` header is set to `application/x-www-form-urlencoded`.\n" +). +-spec form_body( + gleam@http@request:request(wisp@internal:connection()), + list({binary(), binary()}) +) -> gleam@http@request:request(wisp@internal:connection()). +form_body(Request, Data) -> + Body = begin + _pipe = gleam@uri:query_to_string(Data), + _pipe@1 = gleam_stdlib:identity(_pipe), + wisp:create_canned_connection( + _pipe@1, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ) + end, + _pipe@2 = Request, + _pipe@3 = gleam@http@request:set_body(_pipe@2, Body), + gleam@http@request:set_header( + _pipe@3, + <<"content-type"/utf8>>, + <<"application/x-www-form-urlencoded"/utf8>> + ). + +-file("src/wisp/simulate.gleam", 148). +?DOC( + " Add a JSON body to the request.\n" + " \n" + " The `content-type` header is set to `application/json`.\n" +). +-spec json_body( + gleam@http@request:request(wisp@internal:connection()), + gleam@json:json() +) -> gleam@http@request:request(wisp@internal:connection()). +json_body(Request, Data) -> + Body = begin + _pipe = gleam@json:to_string(Data), + _pipe@1 = gleam_stdlib:identity(_pipe), + wisp:create_canned_connection( + _pipe@1, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ) + end, + _pipe@2 = Request, + _pipe@3 = gleam@http@request:set_body(_pipe@2, Body), + gleam@http@request:set_header( + _pipe@3, + <<"content-type"/utf8>>, + <<"application/json"/utf8>> + ). + +-file("src/wisp/simulate.gleam", 183). +?DOC( + " Add a multipart/form-data body to the request for testing file uploads\n" + " and form submissions.\n" + " \n" + " The `content-type` header is set to `multipart/form-data` with an\n" + " appropriate boundary.\n" + " \n" + " # Examples\n" + " \n" + " ```gleam\n" + " let file = UploadedFile(\n" + " file_name: \"test.txt\", \n" + " content_type: \"text/plain\",\n" + " content: <<\"Hello, world!\":utf8>>\n" + " )\n" + " \n" + " simulate.request(http.Post, \"/upload\")\n" + " |> simulate.multipart_body([#(\"user\", \"joe\")], [#(\"file\", file)])\n" + " ```\n" +). +-spec multipart_body( + gleam@http@request:request(wisp@internal:connection()), + list({binary(), binary()}), + list({binary(), file_upload()}) +) -> gleam@http@request:request(wisp@internal:connection()). +multipart_body(Request, Values, Files) -> + Boundary = begin + _pipe = crypto:strong_rand_bytes(16), + gleam_stdlib:base16_encode(_pipe) + end, + Body_data = build_multipart_body(Values, Files, Boundary), + Body = wisp:create_canned_connection( + Body_data, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ), + _pipe@1 = Request, + _pipe@2 = gleam@http@request:set_body(_pipe@1, Body), + gleam@http@request:set_header( + _pipe@2, + <<"content-type"/utf8>>, + <<"multipart/form-data; boundary="/utf8, Boundary/binary>> + ). + +-file("src/wisp/simulate.gleam", 21). +?DOC( + " Create a test request that can be used to test your request handler\n" + " functions.\n" + "\n" + " If you are testing handlers that are intended to be accessed from a browser\n" + " (such as those that use cookies) consider using `browser_request` instead.\n" +). +-spec request(gleam@http:method(), binary()) -> gleam@http@request:request(wisp@internal:connection()). +request(Method, Path) -> + {Path@2, Query@1} = case gleam@string:split_once(Path, <<"?"/utf8>>) of + {ok, {Path@1, Query}} -> + {Path@1, {some, Query}}; + + _ -> + {Path, none} + end, + Connection = wisp:create_canned_connection( + <<>>, + <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/utf8>> + ), + {request, + Method, + [{<<"host"/utf8>>, <<"wisp.example.com"/utf8>>}], + Connection, + https, + <<"wisp.example.com"/utf8>>, + none, + Path@2, + Query@1}. + +-file("src/wisp/simulate.gleam", 44). +?DOC( + " Create a test request with browser-set headers that can be used to test\n" + " your request handler functions.\n" + "\n" + " The `origin` header is set when using this function.\n" +). +-spec browser_request(gleam@http:method(), binary()) -> gleam@http@request:request(wisp@internal:connection()). +browser_request(Method, Path) -> + _record = request(Method, Path), + {request, + erlang:element(2, _record), + [{<<"origin"/utf8>>, <<"https://"/utf8, "wisp.example.com"/utf8>>}, + {<<"host"/utf8>>, <<"wisp.example.com"/utf8>>}], + erlang:element(4, _record), + erlang:element(5, _record), + erlang:element(6, _record), + erlang:element(7, _record), + erlang:element(8, _record), + erlang:element(9, _record)}. diff --git a/examples/http_interactions/build/packages/wisp/src/wisp@wisp_mist.erl b/examples/http_interactions/build/packages/wisp/src/wisp@wisp_mist.erl new file mode 100644 index 0000000..1d3f09e --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp@wisp_mist.erl @@ -0,0 +1,134 @@ +-module(wisp@wisp_mist). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). +-define(FILEPATH, "src/wisp/wisp_mist.gleam"). +-export([handler/2]). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-file("src/wisp/wisp_mist.gleam", 68). +-spec wrap_mist_chunk({ok, mist:chunk()} | {error, mist:read_error()}) -> {ok, + wisp@internal:read()} | + {error, nil}. +wrap_mist_chunk(Chunk) -> + _pipe = Chunk, + _pipe@1 = gleam@result:replace_error(_pipe, nil), + gleam@result:map(_pipe@1, fun(Chunk@1) -> case Chunk@1 of + done -> + reading_finished; + + {chunk, Data, Consume} -> + {chunk, + Data, + fun(Size) -> wrap_mist_chunk(Consume(Size)) end} + end end). + +-file("src/wisp/wisp_mist.gleam", 61). +-spec mist_body_reader( + gleam@http@request:request(mist@internal@http:connection()) +) -> fun((integer()) -> {ok, wisp@internal:read()} | {error, nil}). +mist_body_reader(Request) -> + case mist:stream(Request) of + {error, _} -> + fun(_) -> {ok, reading_finished} end; + + {ok, Stream} -> + fun(Size) -> wrap_mist_chunk(Stream(Size)) end + end. + +-file("src/wisp/wisp_mist.gleam", 92). +-spec mist_send_file(binary(), integer(), gleam@option:option(integer())) -> mist:response_data(). +mist_send_file(Path, Offset, Limit) -> + case mist:send_file(Path, Offset, Limit) of + {ok, Body} -> + Body; + + {error, Error} -> + wisp:log_error(gleam@string:inspect(Error)), + {bytes, gleam@bytes_tree:new()} + end. + +-file("src/wisp/wisp_mist.gleam", 82). +-spec mist_response(gleam@http@response:response(wisp:body())) -> gleam@http@response:response(mist:response_data()). +mist_response(Response) -> + Body = case erlang:element(4, Response) of + {text, Text} -> + {bytes, gleam_stdlib:wrap_list(Text)}; + + {bytes, Bytes} -> + {bytes, Bytes}; + + {file, Path, Offset, Limit} -> + mist_send_file(Path, Offset, Limit) + end, + _pipe = Response, + gleam@http@response:set_body(_pipe, Body). + +-file("src/wisp/wisp_mist.gleam", 39). +?DOC( + " Convert a Wisp request handler into a function that can be run with the Mist\n" + " web server.\n" + "\n" + " # Examples\n" + "\n" + " ```gleam\n" + " pub fn main() {\n" + " let secret_key_base = \"...\"\n" + " let assert Ok(_) =\n" + " handle_request\n" + " |> wisp_mist.handler(secret_key_base)\n" + " |> mist.new\n" + " |> mist.port(8000)\n" + " |> mist.start_http\n" + " process.sleep_forever()\n" + " }\n" + " ```\n" + "\n" + " The secret key base is used for signing and encryption. To be able to\n" + " verify and decrypt messages you will need to use the same key each time\n" + " your program is run. Keep this value secret! Malicious people with this\n" + " value will likely be able to hack your application.\n" +). +-spec handler( + fun((gleam@http@request:request(wisp@internal:connection())) -> gleam@http@response:response(wisp:body())), + binary() +) -> fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist:response_data())). +handler(Handler, Secret_key_base) -> + fun(Request) -> + Connection = wisp@internal:make_connection( + mist_body_reader(Request), + Secret_key_base + ), + Request@1 = gleam@http@request:set_body(Request, Connection), + exception_ffi:defer( + fun() -> _assert_subject = wisp:delete_temporary_files(Request@1), + case _assert_subject of + {ok, _} -> _assert_subject; + _assert_fail -> + erlang:error(#{gleam_error => let_assert, + message => <<"Pattern match failed, no pattern matched the value."/utf8>>, + file => <>, + module => <<"wisp/wisp_mist"/utf8>>, + function => <<"handler"/utf8>>, + line => 49, + value => _assert_fail, + start => 1369, + 'end' => 1424, + pattern_start => 1380, + pattern_end => 1385}) + end end, + fun() -> + Response = begin + _pipe = Request@1, + _pipe@1 = Handler(_pipe), + mist_response(_pipe@1) + end, + Response + end + ) + end. diff --git a/examples/http_interactions/build/packages/wisp/src/wisp_ffi.erl b/examples/http_interactions/build/packages/wisp/src/wisp_ffi.erl new file mode 100644 index 0000000..603d5b6 --- /dev/null +++ b/examples/http_interactions/build/packages/wisp/src/wisp_ffi.erl @@ -0,0 +1,6 @@ +-module(wisp_ffi). + +-export([atom_from_dynamic/1]). + +atom_from_dynamic(Atom) when is_atom(Atom) -> {ok, Atom}; +atom_from_dynamic(_) -> {error, nil}. diff --git a/src/grom.gleam b/src/grom.gleam index 78b57d1..65716f2 100644 --- a/src/grom.gleam +++ b/src/grom.gleam @@ -758,7 +758,6 @@ fn image_data_to_json(image_data: ImageData) -> Json { |> json.string } -/// Look into the `(new_)modify_current_user_*` functions to use this type using a builder pattern. pub opaque type ModifyCurrentUser { ModifyCurrentUser( username: Option(String), @@ -3819,7 +3818,7 @@ pub fn modify_guild_icon( ModifyGuild(..modify, icon: Modify(icon)) } -pub fn delete_guild_icon(modify: ModifyGuild) -> ModifyGuild { +pub fn remove_guild_icon(modify: ModifyGuild) -> ModifyGuild { ModifyGuild(..modify, icon: Delete) } @@ -3833,7 +3832,7 @@ pub fn modify_guild_splash( ModifyGuild(..modify, splash: Modify(splash)) } -pub fn delete_guild_splash(modify: ModifyGuild) -> ModifyGuild { +pub fn remove_guild_splash(modify: ModifyGuild) -> ModifyGuild { ModifyGuild(..modify, splash: Delete) } @@ -3864,7 +3863,7 @@ pub fn modify_guild_banner( ModifyGuild(..modify, banner: Modify(banner)) } -pub fn delete_guild_banner(modify: ModifyGuild) -> ModifyGuild { +pub fn remove_guild_banner(modify: ModifyGuild) -> ModifyGuild { ModifyGuild(..modify, banner: Delete) } @@ -4058,7 +4057,7 @@ pub fn modify_guild_description( ModifyGuild(..modify, description: Modify(description)) } -pub fn delete_guild_description(modify: ModifyGuild) -> ModifyGuild { +pub fn remove_guild_description(modify: ModifyGuild) -> ModifyGuild { ModifyGuild(..modify, description: Delete) } @@ -5502,7 +5501,7 @@ pub fn modify_guild_member_nick( } /// Requires the `AllowManagingNicknames` permission. -pub fn delete_guild_member_nick( +pub fn remove_guild_member_nick( modify: ModifyGuildMember, ) -> ModifyGuildMember { ModifyGuildMember(..modify, nick: Delete) @@ -5521,7 +5520,7 @@ pub fn modify_guild_member_roles( /// Requires the `AllowManagingRoles` permission. /// /// Does not actually delete the roles - only removes them from the member. -pub fn delete_guild_member_roles( +pub fn remove_all_guild_member_roles( modify: ModifyGuildMember, ) -> ModifyGuildMember { ModifyGuildMember(..modify, roles: Delete) @@ -5729,7 +5728,7 @@ pub fn modify_current_member_bio( ModifyCurrentMember(..modify, bio: Modify(bio)) } -/// Modifies your per-guild bio. +/// Deletes your per-guild bio. pub fn delete_current_member_bio( modify: ModifyCurrentMember, ) -> ModifyCurrentMember { @@ -5790,7 +5789,7 @@ pub fn add_guild_member_role( /// Requires the `AllowManagingRoles` permission. /// /// Doesn't actually delete the role, only removes it from the user's profile. -pub fn delete_guild_member_role( +pub fn remove_guild_member_role( token token: Token, in_guild_with_id guild_id: Snowflake(Guild), from_member_with_id user_id: Snowflake(User), @@ -6221,6 +6220,8 @@ pub fn move_role( ModifyRolePosition(id:, position: Modify(position)) } +/// Deleting a role's position makes it lower than any role with a position. +/// /// Roles with the same position are sorted by ID. pub fn delete_role_position( of_role_with_id id: Snowflake(Role), @@ -6229,6 +6230,7 @@ pub fn delete_role_position( } /// Requires the `AllowManagingRoles` permission. +/// /// Returns all of the guild's roles. pub fn modify_role_positions( token token: Token, @@ -6286,7 +6288,7 @@ pub fn modify_role_name(modify: ModifyRole, new name: String) -> ModifyRole { } /// Resets the role's name to "new name". -pub fn delete_role_name(modify: ModifyRole) -> ModifyRole { +pub fn reset_role_name(modify: ModifyRole) -> ModifyRole { ModifyRole(..modify, name: Delete) } @@ -6325,7 +6327,7 @@ pub fn modify_role_icon(modify: ModifyRole, new icon: ImageData) -> ModifyRole { ModifyRole(..modify, icon: Modify(icon)) } -pub fn delete_role_icon(modify: ModifyRole) -> ModifyRole { +pub fn remove_role_icon(modify: ModifyRole) -> ModifyRole { ModifyRole(..modify, icon: Delete) } @@ -8049,7 +8051,7 @@ pub fn modify_text_channel_topic( ModifyTextChannel(..modify, topic: Modify(topic)) } -pub fn delete_text_channel_topic( +pub fn remove_text_channel_topic( modify: ModifyTextChannel, ) -> ModifyTextChannel { ModifyTextChannel(..modify, topic: Delete)

hI>#~K`BU*K#oFCy$1?uv?2ldKveccF=lPQiOp) z7FzG}i83$w_#n%`@D&=yFpc0&B8&sR zs|=QO_VF_?9EP?Z;H5KY=n0;+K>Zt7UP!G-O)f2g7wuc685s6JGbB#OL-RJYHHp@A z5*KG+P!Wd&{Sz?;hL6x7f|hj*(Ab7VEvO)aWkb;MIEryAKA&CiMBrFetOAB!O0~V^_J7Zuja%W^< z@PWEWk(q%(jTxdHTDC#cJfuYka}4A%JZKk$VKD;(!%AqHDr90{D2F=W3=;#xB`BMj znSp^5YO)kF1A_vTy^Mu{VGRq!#%C-H3~!)pBUT0m3(#~N+I0*t7dV5DoPwnc7fA*N zFYrNDh;emrI)W*O?6-vRVkH?ElA!@#Cdt5117%N@WMG&LWpgnzFbF^$Y01jKU=MZ7 zYE}k@jZikuNP{)jASDf~r3JH|3wrq^DDpT285qQ%?c<-K3=HhhM0H7qf#HD+BwUt4 zk4gdg7IYF=DKv~=(Fyh`EU|&sR=^~TWf>UEq1kH=69dBnXk^S_W?+~D^_w6I1A{2k z!1F8&440v7d_^8Ci$OZ|pqWQd(ZpcI#=zhR_2g*|28K(}1o8(oEDlYLZ@Cy4zChUr zxfvKvKwDccc^DWzK$Dy?9|MCGEC>Y|7}%lV>?Fj%;0ew6P$5WWLoP?5_CpWtgoTP5 z^e$FVkX!RHFgQZ%3S(gg21{rWY9|ZX_74hXoW{cpL>_xmgI)j(GTe!cfx!*hwS;;T znu9={+p^elf6yBT_@H;9eX z$*|ahw(~%)1VvXF^uAsY|0Fj9!v$!A;4%{f!!2k+!D%Km){yUc1(^df_YCx|R}lXr z4+Fy=X!~&!GXuk3SfT?dgiQ@0#~w5VKw}S5=D||4AM}P)kilL;3=Cn=4$4|F28Nx` zZ1YQofq@O0;yYOw7^Y%P&7e6b@RB<0tLb1RqXZ)ZgCewKtYBhbXoQ9e%t0vm4z#El z7Nu(>85lM|%aJRR3=B7*?5{Em3_qcnI-H4tAsTAnT4n}@jnJTJVPRnCfVvviRRCKA zb}-JHqF`=*!@$7s1)7@HvM?}gg&GA*d7#Pw=6>)2ZlI(CijAj|3=FTJMJUc_hZPFg zcHhHX&<4FQ78FezB^elwLOl<&73>*UNI}vh%nfHH85k}@mE&{+%p7n71D3YmLN7W7 zS$dg+f#C*pI8R5Mfx!^kta%2#{Rd>5GZO=Y2Q>7PnHd-|prK#P%D_+oE!|-jfgKDo z7!*vNi1C0RW(I~(sJ=JM3=Hp~>>tbw48Ni5-K-1@`=I9G^bxF_K&-2TS*;Ac${J+* nS1tyI|Ili_M1p~#4%%qn%*4R37wVO_EDQ|ap)n3}EeHbubaB|@ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@poll.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@poll.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..ec3010db394b73dc2d7c447f95a14f696b93f94c GIT binary patch literal 1831 zcmZQh`;p1Oz`)S4MTLPu0K{ToU=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&uHq(Fv&fkBsnfk6mEM!zVvur#%}M2~@i!JUDDffGeVW?qR00|P?@hM`%- z`FW8H3=Cx$GC7&WCFLMjGcYjlpcs~4P?DLS*U7-Zunj}5D7CmWr(`<=1H&l>1_ogi zeI=Q>sro6UMTsEorx_R+zG8#}SP6(IE=kNS_{PA%pv1_)zydR-C_h)3k%2*v5h-kn z@^kf5bMvz@^%)r$d>N6#1|*Z2SCU$kmzbkplv-Tk$H>5t&d9(33R{qo=_o2d*2cq} zm%+%u(8b8WAO%tfiULffMQO>##>ST2j0_BG7?A=Tq_VU)wP-CP1H(y128LK~md`&s zt<9Vnp=L2KFo5C_6jPu`0R=xOpg{o&3OG>6fP4<}DacPCSA(1bvIu16DMki{(~JxZ zGE58%vP=vNx=aiVrc4YBUQ7%OsZ0zEEldmyi zl=Cn!RP!(}Y~f*G*v7-au$_m2;UFmI@iH)I^D;2R@G>yW<7HsD$IHO*fR};cAuj_1 zJ0AmsARhyRD<1yU)7!C_CFq{xzU^pefz;IfCf#IqE1A~+x1B0<314EP`14EM_1H*Db z28PXo3=De(85kZ4GB7+9WMJSCVqoACVqoAGVqlOIVql08Vqlmc#K5pkh=E~;5Cg+b zAqEC6VFm_nVFm_KVFm_QVFrc?!VC-(g&7zY3o|gh5N2R_Bh0|?79=miz>p@wz)&i} zz)&f|z)&T^z%WgOf#JFc1H)|*28KH#3=CYN3=BS^3=Gkt3=Hw23=9dP3=9iI85lN+ zGB6w#Wnefe%D`|;l!4)bC83qPUSq276Sq27oSq6qCSq6p{ pSq6qySq6q}vJ4DoWf>UG%Q7%rkY!+aC(FRVB*(zO0>!XW5dcyKA2a{} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@reaction.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@reaction.cache new file mode 100644 index 0000000000000000000000000000000000000000..df7df23740bfa9d70628ba966188b12cbc9806ff GIT binary patch literal 11956 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@reaction.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@message@reaction.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e0d2eaa434c542493dd85d2106e33558416fe701 GIT binary patch literal 844 zcmZQh`;p1Oz`)S4MTLQZ9mHZ_U=V@Q={c#1x%w%Ud5O81$@(d&$@wX%ASr1E1_mw= zgMonoq9&uHq(Fv&fkBsnfq@4_rYNcOXK5;NGFmf_52yrqnh;cG7XmBzxIC3&DICC;ExNtHs zgmW@5aL7j_%!Geo{!IO)DA(o4Q zp_q$-p`D9?VICI)!&)u|hJ9QN42QTF7_M+JFudhrV0h2P!0>^Kfq|8qfkB3wfx(oU zfgymKfgy{VfuVt$fnh#31H&n928Pqz3=CJe85mx3Gcf$&W?ZxMPAKi1pIeZhmzq}s;$L83V0gs9z<}Kt zkO~+^HWE`cR>NUh85mfhOlBzUmkD+zhzD{W2!q&Iq#1CU4AR5|(#62QzzwBCQY%Va zGK&jx5-UN{P*E_2?0#vm2qbv)lVRbZUy@o;5}yLmj>SZ%CNKr_kWXS#Y7RcDM8L{1 ztjYoDLoo!Z4otz^>Xe_45>7Dh!sJ7WN>jlaKm^P*x5S)cgeVV4ih+S4v?R?rKP9yw zKNAx1AYo*8vw;K{7#M<5i%WA#U=~d=f(TEA(jXl$IT-Dq4H5;Z1BrkzOcw(p;$4f1 z@{7>b!AxafV1UIu%oE^n1(^xLAW0MnMg|5ZMg|5xMr;X}iGh&;guoc29fo0{2Fes< zMGM#V`4LA5dDF)}bL0dp7`89^z7 zfpGMb;0LJtky2VtYGN+f3vg3FBKjat!NSR_I6n`r5$rYw2HYhXNF_`sC~?Bgg_r3# z3qg=pFb0Jln1M#X#9@wrNh8}wVIsk2H0k+F6y`z(1_n6>1_pNq28LP&28KEY28Qzt z3=E(e>nTVLBLf3JBLjmVBLjmFBLhP+BLf2{If0VHaz+M*U5pG2{}>q<)R`CaE+>$4vJu?dPJFKsAs5W z02hTZK!p{EjnC)s2nLx8PB=tF0jvasYlf!|EF}jl+Cjy~Cujk)kCB1lB(!YA=Q?(f z^BEWzpcxewy%J0e4DwKOG2H-j9k}3zssb0%?2M4oor#Hoft?AGH8Hj0%=j?dOBop$ z+Mq@e5m-3<@Pd(n;XTwySPme|-LK}0SBshtYfnhef5%A;&^E;@Vg6WoEU|^7eI$!~` LOoav&$Z!S#?TUpi literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@modal.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@modal.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..5a530dd63a707d00d46cf662d84b3a43c2b81913 GIT binary patch literal 238 zcmZQh`;p1Oz`)S4MTLQZ8N^~>VBmt%={c#1x%yef`FS7#Sq26M5fFocfg!ypKUY6F zKer%1FEy`3KPNFMHAjwtfx(S|fk7I(nv&FtlK7O&;)0ySN_PeZh8hM21_eFMIbJ>q zmqnpkKorOrkbErz14A7H1H*X+28Ig^3=B^}l8g)t{EQ3?f{Y9dLW~Ry$&3sPDU1vZ jd5jDU%NZFMb}=$A{9|NbP-kLb&}L#_(1l`)RNSq+{`?X+2t9jB^jwj z3MKgp#o3t!3dJBdWn|{1>nP-;CYFJeWR@tD<|St&=B1~ma4FS=Hw*k zD5Rw3q?UkGmSiNBDC8$)r6!jsq!s1oD!618C+8QXs23}wB$gy5B^IX^>v6F{{f{r? zU{L`INoJ^e810u@4iW~5f?^GXVagd87zIG$3=9kk;h8x(3dLY|CMM`{u|dUMK#om? zau^tep)6$OU~lPh!J4TXi7u&jyKt)PY1m7^aJX0q!W*qN4mFbagOO zk<_t+%wb?)D9Mk{D$dUXna1eL!oUy)^)g5RO%3ks3=7(D76yjPU;`K#85tp28{`(4 zJk(Bju!G7Hin6yYTDbwU03ICapu7kR^5Bx9%)E5C8j#CC7$gAlE(oI=$OV!Fm3km^ zz#)Yqq7Mo?u!k8K7`%!hB_&h~Dg{e4U}(1IFG4JeYqnG9q*R2f5l8iZ1VHlRNz#$i3T#}NLnIr<01dHjXROTh-W+v-H91e4$3&a~R<3Kin zuqAqup_2Jf7f~;5BOD4#F380pD7m1CYceq~=rZA{b(tBMh$u#>?h#lO!N|k_ay7_& zztnON1;QX95C*X!^~*y>28QSOS`Q%QIBOeFe1kB^JWzatFw6xYF%X6Y9L41i%wR?a z23WR4t>r*Y#gJiQVqoCFA5n~W>qn3uK$!aeV1(ypShhfK+kxx_VXE5=G8BwKVFqU4 zAYk%@Vi~607{*{=V31*8V6b9fV2Ea5V2EL0U|7q*z_5;if#Dwm14AYw0|Th#dWw;O z;XWe+!wZlFObiU1ObiU_ObiTqObiSLObiT$ObiT7ObiU5vJq5>-D6^4_|C+@pu^0- zV9U(F5X;QKP{ho@P{GW=(8tWcFo~IgVKOrV!vSUnhJ(xu3_q9|7=AG`F#Kj_VDMpK zU;tISpqewCg@K`ig@Iu@3j@PW76yiEEDQ{9SQr@OSs56dSs56DSQ!|?SQ!{1pcv*Y z11Lj3BR@A)KP@9aEiE@OFHawoy7h~Tk|7Lm!xh?|*8`WEP;H2|gQ1?G9?USfBs|Z; z5(HSUetrR{^@~#3fGb0MB{R$fd}?5sB|k4UK0gf<10a{2VrF2t0F8H?6$`B3hg1Xb zM$i-{28P*83}AoYvl*74OEOaPVD|DeGcX7fvKOWiR6Ik~GQi4W5Pugl1H(RM25`=D zXJugUW`(dZ0}o~rs2L1%f)y(RgB`R101H6mD1hnl%g;*%m35%t@@HUR2!__!$;=E4 z=}_D8c^?*f#ihx~sl~-GquQ7m7Qa=j=!&IoYqbv*zr=e_^5#YoEOC`uH&V4Kl42PhKak?KCFv!&a z%p*G(85oX0J#w0ff#Et6#PQ`U3=GXsgK;NvP;(h(k_iI?g9S8LH?uG>Y=`PT%fi5L g5y~be@j}~tu%KDX#K5ox$~nx$z;Fu6h9(FG0C+o&#{d8T literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@modification.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@modification.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..d49aef701014e5115a8a7f0ca28d632bbb5c60f4 GIT binary patch literal 343 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@sku.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@sku.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..9ffbef6d2dce7b3e97ce459d93b1ea91db451ec7 GIT binary patch literal 908 zcmZQh`;p1Oz`$V2tH!{<4Pr4cFo;0u^qkbhT>X^Fyu{qhWc`%XZUzR1Wef}q zTqtTXN=gcrGcYjhU|?Vn!jRD~N-Zo+EiT!~z`$^cfq{V&MMY*_$z=uxhQ}C&W) zJz-#A_=+KulUZEyje&uIosofo2gR_W)Z)^d5)MWN25CkH1{RoHQGTurBLjmvBLjml zl9+yGUP)?EUSf`ZT25kmu?8apgA*f?4~p`05lV_ui%Xmt85m+185jhS4Jj^7Do!rS zEGWs$&x>VbVCZCIVDL&-Kb&0rgR2dy5JV|6FfeE^Ffep6Fff3E5foscAOZOv3xriHU)shlznu|X7)-bs7)-et z82q>y7*e?z7^=A$7-n)aFdXD&V7SlC!0?Kjfq|KafkBvufgzNKfgzfQfuVqhfuV$l dfuWR#fuWU$fnf^|1H*kD28M@F3`!dy3;Kt}L_2nGg*l+5Df{Gycj)Pnrv446JJ9n8SMkjB8kfITv>>Ia#b$-ux+#lXNI z2;wj>Fc`+07#YMHm>a~K7#PGG7#J|ZoWQW*)FLlN1_nk(uzgVVPzvUs^qkbhTv*h3 zB7&4b45~0awIn{dC^fMpGe0lBBr`X)xFj*R04fRgE`F6@%V7?HIV~ULaU4E}sjXpP zU^of3hY{=pkY11+2!n;u2oxW|QbBM@QD$B`no0y0s~*&p15?ZeWq<<=91tSNBKn}9 z0TbZx;e^VCpa&RK96_Nt0TvNRSq!047{LU`zA7k{GcYiK5>hn-0|O|)&M+`A{9|BX z_|L$=Ai~JNpuot$;LOOtkjTiuki^Kq(8kEX(9X!fu#S;|VFMJyjMarQ^fU5vQ}xp_ z^3&3C6Z7))L1lq{aZxgq0V^)_z##$EgUBETdWL!ia8Vco<}p~ffc5HU=9Sd{MO5D&^t3=9ky zmcybZDYK+Jvp6-rI3qKyB)%v!J);EX6m3QZ20JJRLqEs@kcEjUDNrR0evAwZflv;< zK!TM6X+`C0|Usd z*kT4G4pN$%SOB$+;Ryo+!*h@rQc;i3FR*Zd=Necc92EqUU literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@snowflake.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@snowflake.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..074d10f586176d366e514a085d3a29f72891ebac GIT binary patch literal 214 zcmZQh`;p1Oz`$V2tH!{<3}P`bFmOWY^qkbhT>Z?v5)fa2fq{Vs#9&}xfJhXj7MJFf z2r@7*s4y@vh@i-oWag&ogNWji#M}Z^1_p*e1_p-jjyDd;y*i*E3{?xFK>9)QK@1EG z!3+!x)eH;_H4F?4XBZe5{xL8x{0EuM$iSe$$iU#t$iR@u$iR@q$iUFX$iUFf$iT3U Nk%3_Y6obqIVF23_Ashez literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@soundboard.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@soundboard.cache new file mode 100644 index 0000000000000000000000000000000000000000..13ab923ef198bbbc017ec2f07e13a0dd8ee69ae0 GIT binary patch literal 14583 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@stage_instance.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@stage_instance.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..aca43d00953dbd176a56e2053fb68c3e6bf14dcf GIT binary patch literal 1127 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@sticker.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@sticker.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..465ee3a2a210795f2beaa8c51c7096e34bca0ae9 GIT binary patch literal 1294 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@user.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@user.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..8d7676102646b1c754d2e3d0228916e2efebd671 GIT binary patch literal 2369 zcmZQh`;p1Oz`$V2tH!{<1!6HUFo;0u^qkbhT>X^Fyu{qhWc`%Xep*gqdhs0w28M6QzC=+{lv-T!oq>TtfRTZL=T$-H zdB4cypNvq`7#J8pApr6$$Xg%}fE)^P5XfSX$soOgj0_Ayj0_A(j0_CPj0_CLj0_Cb zj0_Cjj0_A585tP%FfuUQWn^IZ$H>4S!NkB|%EZ7B&cwh_!NkBYor!^AGZO>DB_;-j zpG*u4^2`hjk<1JXjm!)TtC$%W)-f|MY+zR1!7+$b2F#Kd;VBltDV31^GV9;e{U~pn(V2EI4V8~)+V8~%*V8~@1_lE*1_lQ<28IYW28J>=28Mn%28Q))3=Esu z7#Oy)F)(anV_-PP#=vlkje+4g8w0}^HUp%E7=en}dPj z5(fhV4<`eIGbaN>JtqUhR!#~oI5nK!mleicd&T=s@uy8XlaBwp)aB(v* zaC0*-L~}DR#Beh(q;WGaOyp)@*w4+t@ROT?!Ip=Cp^t}w;T8`AgElV%Lnto;LozP| zLoP1^Lq9JA!(?6thC{pz3|Dy>81C~jFfj5lFtGA5Fc|VNFxc}kFnIDYFy!(vFck7J zFwEm)VA#aRz_5ppf#EJ61H(f;1_lm(1_m*H1_lLw1_l>?1_lp)28KL-28L>W28LFC z28QkY3=F&Z85myhGcf$&XJB9xU|=v7U|=v8U|>iQU|^^aU|?txU|`rHz`(FWfPvw? z00RTBAOnNAAOnM+AOk~?AOl0aAOpi>K?a7of(#6&1Q{642{JJJ5oBNx6k=eI7GhxV z6k=fT6=GmW5n^D-6=Gnh5n^DNAjH71NQi-9ix30DK_Lc)i$V+x&x9Bleh4uza0@dq zNC-19s0cGKmqA;z|bVhz_3h|f#HTI z1H&Cr28Mg03=H>085mxQGB9Y0F)##+F)*ZvF)-wbF)+*!V_=vg#=tOFjDg{V7z4v8 zF$RXyVhjvd#TXd)#Tgh}#TghX#TgjZiZd`g6=z_0CeFa1D#5^@F2TT{A;G|4B*DN? zEWyBVP=bNsu>=FdZwUqlSxE*4MM(yRBuNH_HIfVrYb6;NK1wn$e3oQj_#(-`;3~zy z;3mbu5Fo|CP%6d1ut17|VWAWQ!#ybmhWk7V6c^8V6c;6U`Uf;V3;k#z_3b&fnl8t1H*b528M?+ z3=FEW3=Af+3=9^s3=EdC3=FBV3=DH*85mZ}GBB){WnkDK%fN6?mVrS`j)6f{j)6f_ zj)6f-j)B2Pj)9?Bj)7s490S8NIR=L5atsXHAalo%KuDKRkoQ(|BcP-b9IR%T#OS7upfUr)b!7&IXUYr=zmypm{wXss@Tf2_@To8`@T)K|NUAU}q^dA5l&UZ= z%u``tIH|(Ga88AR;erYS!$lPahTAF(46>>W48E!i43kwE7#68AFf3DLU|6ooz_3-7 zf#J9+1H%nf28LUz3=Fqb85o|bGBC)gF)-+V_I@7)>I@8X)EO9Ft1~cYXfQD3X)rJpYcMdBYA`UAX)rJx*I-~c zp~1lLQGR%$aa9MEQ9xT4L#a9^8&;lDNm z1A`6&gRTw(gNF_SLy8UqL!%A@!+aeEhW$DW42N|X7>?*LFuc`aV35^iV9?QJU@*{S zU@+8WVDQvsU}(~1VCd6jV3?%Kz%WIZfnlmH1H)cj28MsS3=GbC3=GkF3=C8B7#J4o zF)%FGV_;aR$H1^kkAZeFlaL`V0)u^%)p`>oYL0889%Y z889%Y8!#}07%(v88!#|*8!#}eFkoOfX28Jk%z%O6l>r08YXb%b5km$B7efYyU_%Cm za6<-$2tx*j0z(Fdm4*xqTMZc)b{aA;>^5Xz*kj1R@XwHe!PAI=A<>9|VYU$i!zv>N PhIK{^3>%;rR@(yrrTBEQ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@user@current_user.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@user@current_user.cache new file mode 100644 index 0000000000000000000000000000000000000000..8fc7387a78be4282b7805e6d79a36e81b3622f32 GIT binary patch literal 19323 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@voice.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@voice.cache new file mode 100644 index 0000000000000000000000000000000000000000..2ba0ce99f137879e7d9d201cbcf14dca78202d45 GIT binary patch literal 13515 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 IgW>>+0d~1kV*mgE literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@voice.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@voice.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..acc4c07a42a9e3b5704e91e281e8d58e67f8ad1f GIT binary patch literal 632 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 Bie&%* literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6de9007051e78562a58649dd6865c493d3519450 GIT binary patch literal 580 zcmZQh`;p1Oz`)SzZ_2>H3Su!ZFo;0u^qkbhT>X^Fyu{qhWc`%Xvnkz`zC52a-!K&CE$j2U*X+ zz`%(tQ(Bx_)W*QTu!w&U|{$L zGK`UdL4=WkL4}cl!IY7KA()YYp_q|@p_`F`VJRa6!!bq%hKGy{46IBH3~Eda4Bku( z3@Ji!e3|UMJ4B1Q!4Ao2w3~fvd3^SM*7?v?HFsxu=U|7k-!0?NSf#Ek30|P5F z1A{R$14AM+1H)8i28Mmi3=HR(85nLbGcf#NW?*1oVPNoQVPGg>VPI%rVPNQDVPII# z!oaYZg@NH43j>1_D+5CYD+5C*D+5D4D+9w~RtARUtPBiiSs54}vobKSvoSDmvoSDO zurVPV81y+A7!08p6b~Q_0NdJB Ar~m)} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook_event.cache b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook_event.cache new file mode 100644 index 0000000000000000000000000000000000000000..b852aff162da64ffd7ebb1b4d8dee2f68de76421 GIT binary patch literal 16442 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook_event.cache_meta b/examples/http_interactions/build/lsp/erlang/grom/_gleam_artefacts/grom@webhook_event.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..552c8d9293824c7de9d9eed5baf55eb66de98878 GIT binary patch literal 876 zcmZQh`;p1Oz`)SzZ_2>H0b(&QFo;0u^qkbhT>X^Fyu{qhWc`%Xz+lP1z`%ndlV4DhnV)CHz`&4*VRA`kZmK?rC@x9NEl6TuVCZ9D zU=V=mFUrr=Pt7aIEXhgDP0cImXJBC1iEJ}SKD{(ECuJ7{1H%ml1_mi)otb$hsYQ8- zIbaLoi_(&fjg2jDGB7awVqjq4L{?c^oLcmofq_Ask%8gR;a{(c5B}(#0962@Kpq2m z0OUZBBS6-H3*W@KP!W@KQP%*ep7l#zkqAR`0AO-2TWPmBx< z-xwJfelRjH{A6Tc5MW|p5NBdwkY{3GP-J3YP-0?WFk)h0@M20|P%R z14Ae)1H*h)28M;K3=E4{85q{EGBCVmWnd6tV_;BaV_r5p?l_c$0B9&s=*JmFwqc*?=R@ScN#L7tO=!GM#2!HkoE zA)Awdp`DX~VJasB!zoS%hD)3b44hmH3;|pW3{_kV4BcD|3^TbH7!GqWFr4OMVEDtu zz#z`ez@Wy>z+leJz~Ii!z!1RAz!1*Oz>vtzz>vw!z_6T~f#ESX1H&6`28Pev3=H47 z85n+YGcf$(W?*39VPG)fVPMGMVPIIx!@zKfhk@Z94+Fys9tH+pUIqq1UIqqdUIqqN yUIqp?UIvEgybKI8co`TL@iH)+;bmaB&C9?b$H%}B$;ZGD%g4YF55=G~1i}D?WRC^_ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/houdini/_gleam_artefacts/houdini.cache b/examples/http_interactions/build/lsp/erlang/houdini/_gleam_artefacts/houdini.cache new file mode 100644 index 0000000000000000000000000000000000000000..591b7a6615427a9924904a59c05047e098bbee86 GIT binary patch literal 4191 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions.cache_meta b/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..5b2655411ce8e342ad1ef00c2b48351e3899b08f GIT binary patch literal 1732 zcmZR3{WFt+fq|j%;z||<0T7FUfq@lDr{AQEG8PeqM1ZBO?QYG$R884~mlff|AVqJQ+p?1~Wzm27Xkzk^=q26v-y4;UF3-XU3&n^|1)o{@p!7m`?cW^uu9Mg|5pCI$vRsCJN;K8T11 z>0oDKV31*AV7OAl7wcQ(J71p>s*C{=Y=#UB3=0_;7(g)%ib+tMfno&|?4aNUg&`gF&tXxdvoA$PQU11_n7M1_o0m28J{y28KmU3=E5z7#LnNF))}gGcZ^( zGceRLGccTHW?(qO%)r3N!obkP!oYBXg@NHD3j+f;D+7ZnD+7ZXD+7ZkD+5CnD+5C{ zD+9w^RtARctPBkQSQ!|E*%%m1*ccdW*cccb*cccb*%%n+urV;~WMg1B!p6Yxij9GR zhn;~zgPnmvkDY-*pPhlhik*RB0XqZ3BX$Oc=j;p&fgB7BF&qpGu^bExdpH;vcsUsu z_&6CDnm8F4mT@vLuyQdl@Nh9O)N?T~Y~^BLVC80D;NfOqaN}lR$mV8X$l+#SDCcHi zc)-oTAj-qQpuoexpvc3(V8z40;K0McFoB1G;T{hI11~QF10OE~LkTYfLkBMd!+c%_ zh9A5P3>JJ03_g4e3@LmJ4CQ zfkB_2fx(`ifgy~afuW0^fngax1H)c^28Mn73=EP23=9?m3=H`K3=As;7#J=KFfe=) zU|{$xz`zhB$iR>z$iR>%$iR><$iQ$ykb&W;AOpicK?VkKAqEC>AqIwIAqIw4AqIw7 zLJSP&gcumE3o$U<6JlU^B*eh*M2LamsSpFhUm*qt0bvFPO<@KGZ(#<81Yrh-CSeAK zrNRshM}!#|jtVm{yb@+$FcD#32oqsoSRlf{a8-nX;hG2ogPAA;Ly{;1!!%I_hUuaV z47_3t4Ax={3^rm64Ao)`3=_l{7!HUrFi43rFa(G*Fhq(oFx(SoV91tWV3;Stz_4C| zfk8-;fx%3YfuTf_f#HxO1H&mv1_moB28L)U28Ki_28NAN3=F5G7#Pk=F)&<|Vqmx= z#lY}Uih)5`nt{Pjnt>rwnt@@XGy{Wz3Tz$TBeOlVxCdAj`n;N0xzsU5p z28Lo~28L2)28J?a28Joh3=F%J85pi9GcY_>W?)cIVPG&(VPLROVPMEqVPKe{!ocuM zg@NIx3Il_fDg%SLDg%RsDg#5hDg(nJRR)H|stgQ=R2di^sWLDyt1&R}sxdGKsWCA4 zs4+0asxdIct1&Pns4+0Kt1&RFRbybdtj55=tj@q-qRzmOqt3uktIoi1R-J+2qB;Y^ zWpxIIE9wjkOd1Rfk{S#Q_8JTf(Haa4-5LxGOEnl69&0c#m}@dHSZFdZ%+h3F*r3V4 za6prR;j$(J!+T8z1~x4Q1{W;`hD;Q%Fr3$7V7RKqz;I2Afk9H6 bfx%syfgwzrfgwVhfnl*W1H%U>hLvIfx#l5U literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions_test.cache b/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions_test.cache new file mode 100644 index 0000000000000000000000000000000000000000..b6e95c3e394e240e491017275ac92aa895412e00 GIT binary patch literal 1201 zcmWe*fPjpWl7jfmypq(S#N?99{Ji4$lGNf7Mg|53L5Knd3>8pu1{Np-Mmy!_=Rk!R zKn5^DIWYNF7MJFfzzn!(4-viurI8JXsqxPSiGtLD zL_ip(iveMoYf({t5xP2*BA7#K2Ab8_e8mHj9CQfe*%D zU|;}+AIKsN1_lNz1_lNj1_lOO1_lODz%(;3Fic}$U^u|Qz;KX(f#C@Q1H&^Yh8YnE zW$0(*=cekXW#p%&8BUv=jx|cB<2?6q!#OABe zp`M|ko&j7K#(;$g2UH+ECpEP+FS7&|Iq2fZ5dqU{%)r244%Lr8Za~2WQiz@;VJ3m% oypDl^0c1OVld!o%f`NfS2C5roBCZq%lUHJ3U;w2O7$4+10L4S26aWAK literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions_test.cache_meta b/examples/http_interactions/build/lsp/erlang/http_interactions/_gleam_artefacts/http_interactions_test.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..679cd2fd6413204953f19ca5d9c963909ec0aa50 GIT binary patch literal 129 zcmcbj_cN1$fq~)s@m>W6Mi7gEfq?@`r{|=mmgZ%afVlh&3=F1Dr>z1kdYAKo1fW=e zfq_Agfq_ATfq}t_fq}t>fq}u6fq@~Nfq|i!fq`Kf0|UbW1_p+M3=9lU7#J9yK`~T2 F0{~#v5;y<= literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@aead.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@aead.cache new file mode 100644 index 0000000000000000000000000000000000000000..e665c2460b7238dcb4bb3673085bdaf7e616d467 GIT binary patch literal 22069 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 W%4En&HBjmY)qCyGHFPjONDKggXrL(o literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@aead.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@aead.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..5372f3d6ca8449dbdc38fb6bb2134b2106d04077 GIT binary patch literal 1546 zcmXSlznaMa18h(RKa@_-NlnbvPs%KbPb?}*tjuL%U}$DyVBkcN&de)mVPasI!ozacNG;awZ0bT}%uNyimF9qRN7j{9^s2oc!eM-AoJ& zkC+%3q|hWY^GZ^S@)C3OGmT*4#m2zU$i~1hmyLm8AsYk3do~6JTXqJ96m|xNsq734Ti6*Gwy`rX zY-eX+_`uG<@P(a$!G(i?VLAr`!)6W!hEp6240kyg82)lFFlccyFobY2FywGDFcfey zFcfhzFcfn#Fs$cfVA#yb!0?Ncfx(iCfgy{Ffnhoq1H()%28LN&3=Frp7#QwxF)&DQ zGce?GGcau7W?6^D{7b@iQ>^@iQ>Y;%8uZ#m~T?FTlW1CcwZjPk@2pr~m`QT>%D$hXM=? zj|CVQo(M27s0lJK1Pd}SBnUDvvm43`If=3@t(o49kTW7>)@sFgz1tV0b0O!0<+hf#IzX1A~$<1B03{14E%O14FYg z1H%Mi28JcV3=DgP85r&fGca(7Fff>kFff>lFfdq%Ffg==FfeqAFfd#dVPKFKWneHB zWnl0WWnf4WWngF)Wnfq$%D`|=l!4*5Cxzpf1J0P$0#?P$9*@&??2iFjIM1V_=vi$H1^%j)CEc90LQdJOhJ>JOe|LJOe|TJOe|KJOe|sJOjgWc?O1`@(c`~ z3JeUv3JeVG3JeUp6&M%}D=;wJR$ySbufV|YP=SHrkpcsQfFc8fhav+*h#~_+h9Uz) zwITyUk0JxZ5=91vt%?i`7Ze#7E-ErG2rDr#Xeco-SSm3v_$Vy zH3o+7Y77jD>I@8e>I@7{>I@8l>I@8_>I@9w>I@7K>I@7?>I@9C)EO9-t1~d1P-kGc TuFk-4Tb+U7E);``Ll6c4EYkC- literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@block.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@block.cache new file mode 100644 index 0000000000000000000000000000000000000000..98cb3b2d9438557e5b95e85103b9815f21d4f06e GIT binary patch literal 29272 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@ecdh.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@ecdh.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..8bb24c0d682d1d7a2abac37d096bcb9cb4f423ee GIT binary patch literal 239 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 SL9JaY!N9N?YAq=8Ko|fam*e#S literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@eddsa.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@eddsa.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e1cc1de26a232d2602bd8849d6d2a82487c9d524 GIT binary patch literal 901 zcmXSlznaMa158i`50p;NNlnbvFG?*g%_&J@Vqhp`Vqid(D=sO@%u6q0VqoZGVqh@- zR1ta0%(nkHR6mHaU|?XdWnf_FVPIgm$-uxMz{tQL%E-W=%E-W=#mK;5!N|Z6#K^!9 z&d9*9gpq;a93uk*3ljr_6B7eN1QP>8A`=4x$Zn7wT}%uN-AoJ&OPLrLb}}(A>|$bI zxXi@B@QjIp;W-lngAg+VgEBJ%LliRuLkcqk!z^Y7hDFQ_3`>|97?v_KFx+8gU|?op zU=U(qU{GOUU@&1}V6b3eV6b9gV6bLkV2Ed7V3^Iqz;K_1fkBCtfkBy-fx(59fx&~7 zfngde1H)rh1_m=W28L8N28MZT3=EIh7#JS2F))a+GcdTbGcYuO2#Y%T_dhg=K{ zeB2BS{M-x-7TgRBHrxyhdE5*PZQKkDbGR887H~5#eBfqa_`=P=;KswiFp-CW;VusY zgBC9XLo6=?!(3hlh9|rX3`~3s3_^Si3_5%a3?6(844!-p3p28OKy3=D4t z7#KbZFffPO}36p1k~>=a{Q_$$W1@K20^ zL06oC!BCumAyS-yVYWB}!*+27hL7S544=gr7`})zFqlX%FjPx0Fzk|GVAw6e!0<|f zf#ID51A~|(14Ec31H)WN28R2R3=9t>85qQ*7#Jd?7#J2xF)%!mVqkbI#lRpe&A{L< m&A>2Ont@@LGy}tKX$FQ@(hLl$G7JnEG7Jnep%|1uKo|f#$bX3d literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hash.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hash.cache new file mode 100644 index 0000000000000000000000000000000000000000..da6a3e98d5c9223215f08c964782ea62c88c0eb8 GIT binary patch literal 14440 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hmac.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hmac.cache new file mode 100644 index 0000000000000000000000000000000000000000..622d30fde7068366f445c1eb0d14f201bc94840b GIT binary patch literal 7203 zcmd;KfPn0x%7T*oV*QNV#AJ{NJBVOlV1SAUFxG)c1_lO&pwxn*)Z)~@l zlEjkC{5(A_E``wIRE31R)ba#{l6-~aqSVBaRFF21QXPea(t?!4l2ouI#@Uo$~W@ph66wAYy`YVDceFrKwOM z1_qdBx5S)cgeWsq-Y*lyB#>z!87$HaFwQy zk(!)ctdN7r7b7FgU~oLc zIZ(q1CGEuG3|^>GkHq2(N0`k}F-!`UR?>4)6LVqljhxyTV4(*|!!Q#-p@k-cEtEjY zKw2KKGca&+FfcF{fq0>mPRz+kO;N}#%_+$&$VpWw z$;?eH1{sl=mt2&Zo0?aWn3GefkXTxhk(yVMnGDK@pzNl{#lQ#xFgGWIiyFGm@r1-S7OugK!Vwhr3$rTFD&>-J|VhBV-wSg%@v6P#b3=3-HtOeGK zOrSUNx>v&6BeD6taCMNs703Ja35)S}EZxUnEVp~^6V+zcxwKx~lJps)ogg}E-Z zq98T7BsB%34unB!vQsNTp@c1cz>Imt$-q#?#lXP09i#!2KEgn*$ppo1Mq-IVBB)qY z$W1Is&HxLgD!?pKC`-&KO;so@&df_!NY2kIE=kNQ(FG+7a1otYlvxZZp`F16HdHsL zTm~1wpkh3+D79E2vjmiiKm{9*c z0XTgXrIwTyKp}`K zgS9&WQU+4KgN=dV6<9r};lRiZ5<_%UK&=8;feXq#V2f}!9$k^P1ARG@O zpxA_gfx(i2fuWs&f#D(p1H%&r28Ndm3=BUR7#RM6x+07W462L_40enR49<)U3>}ON z45t_w7%nq1FkE3|V0gjEz#z!Pz+k|{z+lM4zyRtstYKnc*w4hkaD|D1;T01D0~0d? zgA_9ZgAp?WgC8>kLl!dwLklwl!#rjNhMmj|3|E*L7~V58FmSOjFlewaFu1WWFnF;r zF!-=AF!-`CFx0XzFf_6-Fo3$sk60KO9STW$X+Lpf^KfkBy@ zfk73Dp;5pP3uWkM{vv<>%_BRwU*YXsGumnEHfpwSRof` z0#fY)G6B-GO;mu??&(E|1sR#ike(&1o!|;HFgZUrDKjrMMWH;iBts!lp*S_UD78c( zJGD{|JVXI$CZ_mQAjK()&k!WOUrlVkEqlECXl2vI*nuVR6rY*8Lxrxaj?Fuz^5HleCJ`Eci zrHs^^oP4+@xB_q+N<$M??0|g704tzi1vJ>Z`bFSz5SSR;w~56W;!qdB#J~wOFEK~I zxHPFGCshV2jZG`8k_pa8%uY2jHG>s{hAa#WmQY7w=*vw^hD9TMzyUnk0jqE-nHU(_ znHazx`Nqn?z{17=9&o@gDX};MRslc)8D{@yCI*I|P~))af;x>MI3v*zrspmb1H(f+ zdSJoon_>#na)OD0;WQpCuwHp^Mxv>qQH7C_2~4LoGXsMw)I@9!L<(5r3S$enzPro} z3{Ro@V7&uy{K7ckaWzc;W(J1KQ1(k^28MS~ zHVX>_11AeauM!IbgF2M$z{0@b3T0ceF)-LcBM0UH6xX1Pk~y(4Ft|fiWAigoR3iD9 zo0)+@5b9%Wx{yLDJJrz00v5u;EDQ|N(0E318`LZYzsww%S`Rh`25($0%S}v%X~}~$ zjzAe=H7f(dI#x&;ox#PxFdH;L0IF<3l8Ba=KBxqMRsK#npj=W6v$dC*fnf^NXbhj_ zCMLsN4bJ1xRK~!;$-uzN39S$53MtrOp8v?n!0;XF4-8$2#Tlq!mITvQ%*?=01$8=x zw%o*Im}QWH6BcOC*%=t#vO@yxG8Y5GOY-&U)OidgMc|<&PzX-pVqlmKEe$Z7omiX!l>(JV;DQ2Cl7=%gFeE}v#HJH9juH9} zF*7ing6e}MB(Ni4KJtf;a)07tVE6(J5fr7++`|AGz=zp$o{53sGH6Jdfq~&E69dCb zDElvnh87*7%nS^YP_`~J1A`%y?a9o*;0tA^GBYq_LfK+$3=C3e0Sc>(K=}t|cW_3c zafPX&5iBwM0QnE!;Wen! z&cML%n~j0tAJnx)Tnr4Q(4fQS0<7cRFw2;j7#R3LZEFSwhRI9}3=2R($iTp`i2ErXc>j#8LEAw64|n{Tr+Fg%1B^Nx*y;S1E5R4xXFY;49r zLy`eBngvTGOJOqz3=9l8Tnr2aP>mR#MeD%8oOBmDH3o9jBUT26XHW-yWn*CY33bpH zb_RwY>z@P#3kU19vgFTcT#>K!82W5kDF9-twBkic} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hmac.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@hmac.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e59e00fe5e129b77234537f9fae0fd3024a330bf GIT binary patch literal 524 zcmXSlznaMa1I$nc50p;NNlnbvFG?*g%_+IU$iVQNk%0jwmt9m@P?BG)pOILc@q&?o zL4b*YK^&?NCYPC4l3J9Pn4@1@npBdLD#*mZpwGm>(3>oFWLeM6(0HhF5M{!^z+lP1 zz|hXXz;Ka)f#C@Q1H(%O28N#u3=IDm7#MgM85mR<85rys85o=y85lYk85mA6GB8|b zWMBX}4deun`36i342Db$3`I-~3~QJe81^$UFkE3`V0gvEz`(@Jz#zrUz+lA8z~INs zz>vkvz|g|Xz%Y-Qfng^z1H%<&28Q>{3=CW>3=A493=D283=Cc@3=BRj3=F<33=Fj_ z3=EAd3=CIT7#JS0Ffcr3VPKGAWnhqFWnd^^Wnfss%E0iRm4QKsje)_Gje)_Jje#MM zje#MUje#MAje((&je%hY8v_FaI|BnFI|G9;I|G9`I|D-vI|IWqb_RwA>`&z5W&E}096L%F|a@xFxn|U zKL;wrz{tSBzy#&MX(UP638@=3>IkyHjo4Z14B@1 zacNEo%+4S35aFLt8e{@Y4o3TDgG52%HDXB%7WvTHw`S}G<5pZ~6lO`gTV1clhk%7S-Q zWl3srd`VGiDkoIj2_zCyl!{2iPzgANVm&M-P?IZMEtG+xg}mT|`4cHCK@DeMU@${4 z8LSx?7#tWF82T6(7;Z2yFbFX+FbFd;FlaF{FxW6MFr+dvFjO)!Ff3wZU;w4B?Ticz zml+uto-;Bq{A6Tc;AUcAkY-|F&}Cv^@M2+PAFg#>tV0h2Wz+le8z>vtoz|h3P zz|hCSz%Ye{fnhNV1H(2J28NR?3=B6}7#NWTYw-XXd5nr0N!zq!uV-r&cO}OB}FPg|yNLQ*Bz;AC)Zp^%%OQks*hkd|KrF*-9ZT>&DMUX)mnk(ms#wb&G97}%=fjKreU z6oul{vYth$~#3T|7WK6cW>mQd2?R)YOC55-1r{zX)8^!^B__11hxm zp|*h|K|d+8BtEgID6x_YRX9IC2Uo7PJ{Ce`jJ~_zNy|85l6Ff+d0U z(!`<^n3f1828LLu7PtaX*$Lx->qVIA87vG8b6FU`6$%lx0hUSxY8Asu76yhbP?J_M zGB9j~RuZ_v1Lonxf`Zh%6qqRS}CJ0yCksASJN`RF;54xSW}R zp%z+1!E`|)4HCIfDTb0FP|*X{V#&==#@3h5qB9G7@k5kyk}%!_zq=TGBGeXLS2Mn5KNBwD2~{h`#K0g4WrGtP0|Ns9{<79q literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@concat_kdf.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@concat_kdf.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..cda46e11eec9d63cf01224db40d0058dd1e0a2af GIT binary patch literal 410 zcmZRu&&gzf0ahr3A4;d^q$cL-CuNqzCl(bYRthsRFlaI|FmRzt=jZ2WF)}b%Gcqs; zpvqL1q!!1Q6s4xxFfuTtFfuUkpvV`c7MJFfq%txvR3OP^7gZLNGBPkM zWMp8tmE^zTTiB~(W>Ebg%9??J!GVE+p^t%q;RXW(gAgMF1IUFSw}9*enG4dth>?L| zF(U)Rc18w<%Zv;R&lwpQeljvJa5FJ5NHZ}o=rS=dcrh_B#4|B4R5CFzEN5b1Sk1)1 zu!f0&;XV@sgAy|XLl83qLjp4cLpC!5Ln|`_!%SudhFQ!E3`dw57#=b+FuZ4GU@&K4 zU`S+PU}$1tVCZ9EV3@+fz_6Hwfngg91H(xc28Np~3=Gd%7#NsY85ks385rzY85rDI V85n$785jat85n}0800q)1_1b}K575} literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@der.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@der.cache new file mode 100644 index 0000000000000000000000000000000000000000..6b3505eee184032a2e89ae6936dc0faa5dfb740a GIT binary patch literal 47682 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@der.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@der.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..e404164cab263a996c36ec9a9044f85088303a72 GIT binary patch literal 3494 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@hchacha20.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@hchacha20.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..5a3da9adfc648907cdcfd678dc4c0acc4864b508 GIT binary patch literal 738 zcmZS9+>*%v1B_4xCzMXlNlnbv&&(@%$iTqxk%56B>ejD2%g+jzErTimQT7ZB3@!`| z43iib7#=V%Fo4v4VqjqS%)r1P#mK-A%gDemg^_{b0wV*%e?|rdekKM6EhYvAS0)CA z1SSTCY9T*Kn2mwq0viLvT{Z@WS8NOn-`N-#SlAgD1lbuFev|=de|8lCa^OwOk!tXn9R<=aE_gU;Tbyv z!w+@_26hexhHwrBhB6KYhOHb73|BZ97=$?)7>qd?7>YR=7$$QvFkI$jV7S7`z@Wy( zz>vnpz_5&qf#C@k1A`Jb1A{U*1A{3y1A_}U14A%3149xw14AJ<149Eh14BPI1H(LS z28OlV3=Dg?85mA;GcerZW?*>5&A{-Bn}LCghk-$ghk-$hhk?P0hk?O`hk+rVhk>D# zhk>Dzhk>D*hk>Dnhk;=!4+Fy`9tH+EUIqp`UIvC3UIvDlybKJpco`UW@iH*n;ALPC zEduL@^0u8b}6Vo*fx&};fx(x7fng>C1H*d;28Is|3=Hgy3=9U03=A%e z3=Fx93=EBo3=B<-3=E)DbdiyP;VmNr!#_p_1_34p1|=p2216zW20tbShGZrNhGr%P zhP_M-3@4cw7;Z2zFnnNQU|?ZpU|?ltV31^HV6bCmU)f?H`G z*j<_mnIH$}rskD^CG?;sF}On+3Z5|eVuc_#X9ZIWGfRckijtzl*ATda^)l%K1RnO9I+0^)-dV% z40_PwYaSB=!$PP84!^_dsiex1)cE4es#I7?+{?zma13e=%$;DXVeJo42L)<2Lkuee zLju&zpV$}}zOkXY898F0W-^o(q$HN4!c3@QV_;~2NV0ZE4jdQCKeQ==A}UW$I!;kz|aG=@+b!b!)a&@2Q>_mP+*Gvvq3%rrLPoL28J|f zh}*FUL4G51r7!VZ4QVNCbBRv%wd5z&5ebDArMrdAoVF>DFl@H zVS$&HnU|OYbpyi;HU@^dP{;jYVqjo|+JY;-pwbL^spYU}Zed|yXoqG1s7VaCw8Ii7 zybA+!hbkijgC4ZgDZ$FXpa_jdSO$lC1Xu48>Hvl~Mg|5@uN-9XJw^tG*Nl*mddAGa z@EIC$t63Nrc0fZ47AoLu3{7SXu+Aqe7QQetF#LfU#Ky$Hzy}H`1_lNRCI$utDBG5a zfx#8Z{=>|`zyh^^kA;Cj49YfUVPLR^vJbK_Fr0+4FS9T(+=jBhvoJ9HhqCoq85qo= zA%WshSTy)$=D?WlObiTOQ2T9J7#JL&W>~T^FxX%-0~S}1jsnaWGbRQG8>lflEDQ`r zP-8S$85s1i83WVknO6df105y?27Ra*N-PWvs!%gtu`n>ag|c~A85sDX=7I7q2m=6l Co|+Z_ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@hkdf.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@hkdf.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..1e9c602d1dd6ba17ea74f6eab25b5b63aa5378de GIT binary patch literal 556 zcmY#5nVrc11FTR6Ka@_-NlnbvPs%KbPb?}*to*>hz`(}Hz#xDsU0IS^9A8qDn##_| zz@X2_z`%ndUzA#0np0xH$iU!?B$r)OSx}N+te=rsoZ-UAz>tGYE;lhbmyv;?fsuhh zP;Qpvk$=}@qo8JkC=Uh(244mShM5cu4DT5j7(ng=xdCJs$V`ylMn(pPCPoH^{frC@ z7a18C-ZC;U`~&%ciGe|hiGjh8iGjh7iGd-RiGiV+iGg7+69dCZCI*HZObiSkm>3vX zm>C#YnHd-)nHd=Dm>C!Xm>C!vm>C$BFf%ZmW@cbG%gn%Vj+uesCNl%WS7rtVHWmg3 z2^IzhbruE&7ZwJFZWacHUKR$1J{AUsc`OVJyI2?)PO~sD++|^4c+bMXz{twLAjrzV zpv=m^;K0hj5W&j8(8|idFol(Y;T$Ui0}mSmgC`pULpU1)LlzqYLkAlJ!vZ!2hJ|bl z45!!_7+$e4F#KU-V9;b|U?^l~V3^O&z;J|}f#C`}1H(Ob28NI93=Co%3=Fy)3=A$D z3=E+h3=HWU3=C}?3=C5_7#P-ZFfd%@U|@L2!NBmEgMr~Q2Lr=T4hDw591INqp%@g_ GAPfMJJyzrZ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@pbkdf2.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@pbkdf2.cache new file mode 100644 index 0000000000000000000000000000000000000000..7cefd9cb5aece24a03fc36bf1d746b3a56b71e7d GIT binary patch literal 1931 zcmWe0&^*&$Xy1zX)9&%v1&j1|E=p1_p-Ek~HW1 zl+=R!%)An)5I7Lf*_=>;l>GRV)S}EXxZgqcV@eW;9atz%XJlXy0GYtR2m;(tOP~=J zpO%)%3l)OID%5U9QK+b%p1z)*K9X|1+^k}l*C1M4z^(<^0SXP6#ZdhWXh{U76>6e> zMq+UW%mp5a#Tkw{>G?&OB^kLe{TMXNg7lo!#9R)jv{Pn@V^L9JC6tRvQ9&0hjd>#C z5yKu7Ye?3BJ;kD1f?@}G0RVFuQf@-nB8Xrz1b`AS0|Ub%1_p-T3=9l^7#J8-85tPV z7#SEM7#SEq$!jGe1H(~928P>=3=D5UNq~ugfuD(iK?{mu#$`Ym`WgATsrqRd`Dtmn ziFtYY=|%au`l%I(xdl0?#rhc~B?a-|q9!r9Br`v+SU;&WGbcsAATc>RF+H^ymKODk zi<04NuzEz%qz6tLAg6$>gaw#^o}r!rScHKAUPgIC1r$7?#hXHaledeTk%C4*VsUYK zeo>09Q(|#yih_4)r2;sqCxV=%;8vOkcAtWgra~sj>A9(SC15E%sEG_7P=7$imV>-@?GqLZJX=MH1MG?9@sHaNdWQqYw(R7v{H|$`F`&NtFsE8L0}%;DT8p zH$SB`CsiRWzX+l^GcR2sJGHV{A+0Ds7j8wdLV0FMhC*_FURq{)X;ETQPO3s?Noo<; zs$xBOk%TRS!a|ZjnS)VC!Su~zWMEj%$iM(fcCg5R(cq{;PsJdGAkSW8WMH_-$N+ZS zV@3vs*HE@J69a=X6GR-vAk-WLvtNXXfk6tJa+p1SnK>}Es!R+F8c?;^!UAhy1uFbN x?iFKXU{Gd+RB+oE85j;j-HYNzn5CY1@Ji`7BLl-fs9G*21_oZJ13+eiFaRCPFlqn* literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@pbkdf2.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@pbkdf2.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..c4097454b9e5db850aa0283a5ee769d046e3fe22 GIT binary patch literal 145 zcmY#5nVrc11B_4x50uU>sw^nUFV@dUEYA4Dz`&rw$iT3==ID#-vXcb_p=v->00RR< zC<6n-A_fMA-wX^4AhoKD3=C?F3=9#B3=Gp585mYFGB6xvWMH_>$iVOxWDyes13wc3 LgBBEn%mHBlx^onG literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@rsa_crt.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@rsa_crt.cache new file mode 100644 index 0000000000000000000000000000000000000000..94690a9c337f5440b45b50c5602d2e1933a5e483 GIT binary patch literal 14167 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 Zwovy53NtW-K-o#c3=FAIHZ)f;008841aklY literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@rsa_crt.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@rsa_crt.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..463cf7408a3c2dfd1eda16b187451c5f66a2d6f9 GIT binary patch literal 1307 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 W3=El2cD+9XLo+mSfFp>3fdK%uV!4(8 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@x509.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@internal@x509.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..4c7059c4e754f2f8a5326a3e8a09943c61ef3792 GIT binary patch literal 2937 zcmexkxFwST2Kb>27@eMznwYDflvxs=SX7i)8O*@I08-2aRR)#L&(FzXU|^_bU|>L% z$;m7(sbOGX=w)DF;6c$-lv-SxQ_{!4z_1WQuDGNqGcSD+0|UcW1_lN}6nzl6_>!X3 z)NKq54Cj!n%r2@dD9JC@Pfb40z`*c;fq{V+sudxVQk?jZfq~&GHp!Hf;>2$t^B571 zh3U*lEY9F&WMGhEWMB|MGc_}>B(*3nF-JcowMd?kfx(KAfk6zHn$nWYoMLN628IYm z1_o|4RYk>#k&Fxs*;pJg1FqASgFqAPf zFzjY#VA#XV!0?)xf#D4^1A`HD3-g7W81amSlV+5>b_y{t zd=p||FcxNDC=g~~SRu^7@Kl(AK}m#xAzp-mp+|&)VY>(e!xIq(hBqP%3<9DI3?iZo z3}T`T4C0~;456Y743VM?49i3r7;cI(Fi40oFc^q2FnEhGF!+iwF!+fvFvN;6FcgR} zFtm#?Fsu?|VAv_fz;IiPf#I1L1B1Fa1A~S*14EEF14Fqu1H%S!28NyD3=I3l85jVQB`2FVYMQ7BUPB@iGhysWJ=<^)d_$`(+py zp2{#Vyp~~LV3lQHu#jb7sFP)2SSriFuv(UZ;ixPF!zWn=1|vBJhDbREhIly!hGIDe zhH^OuhDtdGhAKG*h7EEI3|r(F7}(_*7!2hZ7z*SW7<%Oy82aQH7}m=(FkF&nVBk_< zVBl9^U=UPbU=UJZVDM64V8~WrV3@7Iz_40@f#HGz1H)AX1_m)j1_pOU1_n<>1_m!h z28KLE28J1m3=CTp85o`_GB9W>F)(B+F)(aYVqn;;#K5pciGksh5(C3GB?g8hWd?>d z$_xxgl^GcBDl;&!sxUB!s4y@{sW334sxUCDQDI;>q{6^(T7`i@LY09bSe1bxM3sRd zO_hNmN0otLnkoasbX5k11*!}Tw^SJz?x->_+*M^@_^ryoAgIQ`psB{d5U9q$kfg@I zP@%@aFjI|z;gA{w!wWSA21#`W20e8KhG2CDhIVxZhIQ%;4ENO;7+5tJ7z{KR7@{;7 z7#cMg7&d7zFx=8$U|`i`U@+8VU&A^bP&A`y2&A`yF&A@O+n}I=G zhk-#>hk-#+hk-#^hk-#whk+qnhk+qlhk;?84gz9T?Ph6T?U2_T?U3sT?U4E zx(p0=bQu`#>M}6A(q&-K)MH>U*JEI4(qmw_sK>w{rO&{ipwGZyr_aFPqtC#QtIxo& zSf7Dmr9K109(@LeQ~C@Huk{%ilnoddGz}OSQVkdwiVYYT4jM2puo*Hi*cvi0WEwIs zLyZvw!(k%^1|DMu25(~q zhCpKmhA3kOhGJs|hCX8khNH#|3}=lQ7%mz!FkCifV7Ow;z#wMAz#wJ9z>s0Wz_8YY zf#HS;0|T2W14Ec814FVY14FGT1H%zh28O$)3=FDf3=HXJ3=B193=D_N7#NVR3^VK)7|z%+Fr2evU^s8bz`$nDz`$kCz>s9m zz%ap{f#I<|1A~ME1B0al14F3;1H&W-28PED3=D#f3=E=<3=HCq3=9&E3=9d53=Aob z3=A6`85mwUGB8*%)s#6 znStS#GXn#s3j>3q3j>3>3j;%l3j;%o3j@Of7Y2q!E({EdT^JaixiB!i0?E5FFyy;3 zFtoWcFidr2U|8$Qz;M`=f#I1e0|Sp61B1C614FPI1H%+I28J1K3=9X{7#Lo=F);AD zGcX9bGcbs_Gcbs{GcXvqGcdTjGcd%vGcc68GcZhWXJFXw&cN{3oq<8ugMlH~gMp#Y zgMs0Y2Lr<~4+e%49t;d8Js247c`z`1^9zk`Y4P=EK17#)pC7 zmk$Gjt}g?FwJ!rhye|VomoEcDw=V<3d|w8Jv%U-r+?w2J-+0 z2G0NnhJpYFhDiYo4BG=37#;*LFfau&Fo*>*FsKAFFcbtbFsun=VAvGMz_2}#f#FUd L1H(NiW`F_!mlXS| literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@rsa.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@rsa.cache new file mode 100644 index 0000000000000000000000000000000000000000..e6b47d05c2d4b9925e30aaf59cf9a2ea4f66979f GIT binary patch literal 36634 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 C(9L52 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@x509.cache_meta b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@x509.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..0e2d57b0ac700344c1f64addc146c6326680e6b0 GIT binary patch literal 1725 zcmXSlznaMa16)uBCzMXlNlnbv&&(^C#>T*~jE#W-rT`+5lUZD{oQ;8D2O9$e4~mxj zf|AVqyq#ajC0II$y)hDm1Tm82HsCFba-q!u}|Gcd%l zGca(Y8BkQ57|+hYP>4mUA|<1Uoq?f+oq-|btC=3_gk8DOj8N?i3=Ad=3=EbG3=E|V z3=CBa3=BsZ7#PGD85mL-85p)PGBEsQWME)oVqg$tVql15VqjRz#K5qEiGkq=69dCZ zCI$v+W(J0QW(I~s%nS_onHd7#LbP z7#P|(7#I$7Ffbh9U|x z0bT}%WL^e_N?rzrW4sIuw|E&Cl=v7JJoy+HdiWR^&hjxZeBxtZ_`=7)@Rg5&L7bm~ zL6@I_A(@|nA(fwjA&sAbVLd+s!x4T4hQIs_43Yv23`PPB3;_ZR40Qqw408n-7_JI1 zFnktZVE8J)!0=6gfk8u%fx%6XfuT~6fuUKDfnk;)1H&>w28O+Y3=Ag)85kZ2GBCUm zWMEJiVqh>9VqowQVql08Vqhp1Vqj<#Vqlmh#K5pZh=Jjm5Cg*pAqEB=VFm^hVFm^> zVFm_sVFrd0VFrc?!VC=Og&7zw3NtWV5@uldAW<*&7uqpKSdcB*u@wa9K;wHyu=t7CWtXG%ok%|cq+!g z@I{P);hPu(!*?+T1_^Nn1`}}xhEQ<^h9q$YhGuaFh6&;f412^G7*2^ZFfd6lFo;Mn zFc?WNFgQpsFgQstFgQyvF!V|=Ff5c{V7Mv4z;IiFf#Hq>1B03*1B0I=14Dr%14EZ2 z1H%SM28NfC3=FbT3=E1=3=GOr3=Aq#3=COP3=DZv3=Dgu7#P^485ne=85nY;85kBz zGcYWbW?)z*&A@O$nt|bpGy{XO3Cda^_F2}&&BgeoHAjiPaA;-YbBgepS zTaJN2Q=WmLMxKFTn>+)5URnz5Tn7skf_1HP@%!VP^H1ZP_4nhut{46`*E7+z>HFuc)ZV0Z_`uu>NQD{u_g literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@x509@certificate.cache b/examples/http_interactions/build/lsp/erlang/kryptos/_gleam_artefacts/kryptos@x509@certificate.cache new file mode 100644 index 0000000000000000000000000000000000000000..32c9450b24e80183e0f29b000a84fe8c9f2ab816 GIT binary patch literal 119865 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 V290okfX*Kmr0CJ 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/logging/_gleam_artefacts/logging.cache_meta b/examples/http_interactions/build/lsp/erlang/logging/_gleam_artefacts/logging.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..6c31ea6d4943d5e6f6c2a2a43622d25003c991ea GIT binary patch literal 249 zcmea(ElFcQ2Qy5QHb2btSz&}EA;Q4GpuoVupuxbvV9db4V9mh5;L585YNECkifvekk7!tP{6>zu$zH_;VlCL!$$@N1_4F}1`$REhImE>hV_gL4F4Dz z7(|&E7$lh(7~Gi{7!sKn7?PM67#1=yFf3(aV7Sf1!0>~Kf#Dw$0|Nsy0|O&71A_`P z14AM+149xs1H%+%28J8V3=B7!85p=(7#R3j7#OTr7#NaS7#K2H7#Oly7#MO`7#PY~ P7#QkV7#JF%80IAapxqkf literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/marceau/_gleam_artefacts/marceau.cache b/examples/http_interactions/build/lsp/erlang/marceau/_gleam_artefacts/marceau.cache new file mode 100644 index 0000000000000000000000000000000000000000..1ed18cdc0a87f6728558a4470e953bab0dd0a0c5 GIT binary patch literal 8549 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/marceau/_gleam_artefacts/marceau.cache_meta b/examples/http_interactions/build/lsp/erlang/marceau/_gleam_artefacts/marceau.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..731a1353e008ed9a66551d4dbad2400bb6421c30 GIT binary patch literal 7277 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist.cache new file mode 100644 index 0000000000000000000000000000000000000000..6024987368bb9acb8cf3d8dee60aac9e89dbcb56 GIT binary patch literal 74948 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@clock.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@clock.cache new file mode 100644 index 0000000000000000000000000000000000000000..0ba7c55e3d9d4267956a6216b6bde98678c9d8ea GIT binary patch literal 10946 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 R28L}s3=HR?7?eIh7yzF6cFzC+ literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@encoder.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@encoder.cache new file mode 100644 index 0000000000000000000000000000000000000000..6e943867864b13aff3fd59c0ec93bd0589789b8e GIT binary patch literal 3952 zcmWe-fPmc0;u8JLypq(Syu=*+)V$>Ul++@S6bp!8U|;|#fHE0iVmwfR(2_Lg{FKy! z{LH)(s1O4qjKRRbzzk*kW#&LxU}=~VEYb{YP=!IM#icnVOiO`fkPf*G$$pK`a#M?o6Vp?<7*Nvz z%t)v~3|vqKILpRol#~>}@)}q~A0(m=Gar_&gP_S5oX(&IfGFyOB*-KbO#jG)S)T=C zFfcG^GB7ZBGB7ZJs?Syi28QVj3=A_E7#Kj2BgV+Upu)(&5Xi{DP{GK+P{qi=0LqC| z7#SEAGcqvjVPs%9&&a^=fRTaW8z>@~7#PHu7#K{L7#O0N7#JopF)&PGVqjRx#K7>3 ziGe|onSsHQnSmjXnSo&%GXuj6W(EdO5q6%Lf#D}J0|O@u1A_(&149rC149W514Azh z1H)<-28L5C3=IER7#QSP85o>d85lBI85kz8GBE65Wnj3%%E0i2m4QK=je)^|je#MP zje((?je%hu8w0~tHU@^TYzz#d>*vZAfaGQ&Pft8zqL7SU_A%>fQp^KY=VJkNS!%c1mhCkd43`RT*3{gA` z3~@XR3<*3844^7=E)N64bsh$WZ#)bP-+34qn0Xl(w0RjAf_WJjs(2X~mhdt#tmI{2 zSPjLnC`*Pi^fU5vQ}xp_^3&3C6Z7))(~I(R^;0Vna|?1(i^1tT9^9x(OfJdH&nwmk z7s>hsiOJcC>8Zu~phlH`aZxga0al6B{L%yG5_mpTZ22rRkyyZFe)+1CWLk156Lm4Q6GcYiOGBYqFGc$nO;CPLMr5P*@ zD44kmm>C#0GeesApk{hI6U1CX;R8t);Cc)ePv1aVp&UFQpU^xxc`G5t>Iz|SDO;A@h@Gvm6@IVYl(FiMHARP~w z*8iYh9#rdbMh1qnP&R572emw4`aQrsF_61JA>_!wz~Bz`7cBjQ-3cxjakf@q)`Kc_ UP`??(f5^bV@DdsTpHx|rS{z?el$r_>)?i>@-~uri7#JY(86_nJnhXpK z9t;c&!Wc68MXALF`FX{uo(v2O#S9D#oG3~%^GZq>7#LbGOwGwGE@@?8V3@|hz;K0o z#+^si?t8PKT0s=ZMv##p-5_<-85kI5FfcG2V_;wqV`N}ZVPs$kWMp8dU}RvZVq{>b zW@KQP!pOj|n2~{D4?fx(%TfgzKXfnfqG1H%qh28JuF3=Cgb85qRb z7#JMb7#K3y7#OnIx49S?Sh*P(w7D4=Vz?O?y0{q_wsJEt+~j6p_`}V> zV8p|~5XHm55XZy7kif&hkjTTpFqemc;W`fk!#5rVhVMKK49vU?4BEU548gn%3{|`g U3`=+!7*_H!Fsz1RP+Wm90A4Fyw*UYD literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@file.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@file.cache new file mode 100644 index 0000000000000000000000000000000000000000..72c5b897e77cf31dd11a0ad4bfd56b7a0eb755a0 GIT binary patch literal 11327 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 Q_k*V5n4!f1NDBx90H^S14FCWD literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@file.cache_meta b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@file.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..80183889aacbc84780589ab95b16a9d174875a4f GIT binary patch literal 529 zcmaExw?2~r23VjB0VtiGlbV>TpHx|rS{z?el$r_>7H42!-~llh7#JY(MXAN5IVBPd z3=G-~3=HgWxy<5{)I1#q28L(`1_nV?Vf~V##Ju8y{GyT=1_p+H1_lP(M~iFi!|eBg z6hqZAfXoCL08%r7fq`Kn0|UcK1_p*z3=9nW85kIjGcYh*Vqjpn!@$7so`HekBLf4& zCk6%vbw&mTGe!mmZ$<`&G)4x7dPW9@Rg4S_>lhgrHZU?UY-D6$IL64paGQ~V;VUBp z!*@moh98U!3>-`h4Ax8x47N-R40cQm4Ao2w3~fvd3=^0b7*;YdFq~mxV0gmB!0?iZ zf#Ee11H&6828O>(3=E>o3=AI33=E0P3=HMW3=G}O3=GSe85oW-Gcep>W?*>9%)p?< z!oc9q!oZNj!oX0(!oV3tIW`7{_iPLdAJ`Zeq}dr5V%QlNV%ZrO+SwTxma#K1EN5q6xXjMLAix2^ HurLPzo6}OR literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@handler.cache b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@handler.cache new file mode 100644 index 0000000000000000000000000000000000000000..11b455590a09b7093349905dd8b5626c2f37b6fc GIT binary patch literal 12787 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 literal 0 HcmV?d00001 diff --git a/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@handler.cache_meta b/examples/http_interactions/build/lsp/erlang/mist/_gleam_artefacts/mist@internal@handler.cache_meta new file mode 100644 index 0000000000000000000000000000000000000000..7e8a07fbdb88dcea17919763e3b29545e1ad4262 GIT binary patch literal 1295 zcmb1Ydz{Gt0|HP6j84xWMD90MCi-OPfyRx zOE+X>V6bOoU=W4s%grn<(a+2)NiE7t%+XKHOU_S8EplLFV2EL4U=YHp2oyrGj0_B4 z85tO)aH`PHNX$#gNiF)u$iSe^ga~C+3qfWXX)rM`I59CWNaHlj2xgiy69YpT6Fz&5 z^ovW1QWJB_nHU&0GBGgNwaaxxTiY6~hZ+u|Krs)BT2K^%;t3S~pl}8SDk$7QfdmQz zkS{A485oW+GBA8$WMBaK1>{(eD?sLNVq##}%*4QOnu&qoB@+X~7bXUVZ%hmf-!IOo7A%%s3 zp@D^gp^=4wVHpbp!wMD#hLtP~47*tv7|ydWFuY}9U|?cpU~pq)V2EU8U`SwPU`S!JMCgA(Eegp^u+|VIMyO!*zZJhNt`t3>*Rs z47LIc47maf49x-z3`+$V7@i9-FvtlqF!%{FFia3+U|1%|z_3M-f#I$o1B0j#1B0s& z14En;14E7w14D%n14FYA1H)n=28JC%3=GeO7#P%q85n|u85r7x85pJuGcfEFW?*