diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3893aecd1..eb88844e1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -44,4 +44,8 @@ jobs: env: GOTD_TEST_EXTERNAL: 1 GOTD_MTPROXY_ADDR: "127.0.0.1:3128" - GOTD_E2E_DIALOGS_BROKEN: 1 # TODO(ernado): enable when fixed + TEST_ACCOUNTS_BROKEN: 1 # use external test accounts + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_JOB_ID: ${{ github.job }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} diff --git a/examples/go.mod b/examples/go.mod index 1c565b69c..fc0582132 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -11,8 +11,8 @@ require ( go.etcd.io/bbolt v1.3.9 go.uber.org/atomic v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.9.0 - golang.org/x/term v0.26.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.27.0 golang.org/x/time v0.5.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -47,11 +47,11 @@ require ( go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.30.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.33.0 // indirect nhooyr.io/websocket v1.8.17 // indirect rsc.io/qr v0.2.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index b75898bc0..48423dd05 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -99,6 +99,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -109,23 +110,28 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/go.mod b/go.mod index 390b9362e..ce411a34a 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,18 @@ require ( github.com/go-faster/jx v1.1.0 github.com/go-faster/xor v1.0.0 github.com/go-openapi/inflect v0.21.0 + github.com/google/uuid v1.6.0 github.com/gotd/getdoc v0.44.0 github.com/gotd/ige v0.2.2 github.com/gotd/neo v0.1.5 github.com/gotd/tl v0.4.0 github.com/k0kubun/pp/v3 v3.4.1 github.com/klauspost/compress v1.17.11 + github.com/ogen-go/ogen v1.8.1 github.com/rogpeppe/go-internal v1.13.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/metric v1.32.0 go.opentelemetry.io/otel/trace v1.32.0 go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 @@ -36,15 +39,21 @@ require ( github.com/andybalholm/cascadia v1.3.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-faster/yaml v0.4.6 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect + golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 902582852..a27edb4c8 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,15 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= @@ -19,10 +24,19 @@ github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb github.com/go-faster/xor v0.3.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ= github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38= github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gotd/getdoc v0.44.0 h1:hSJzDY313wysMqmu6+sWg4pbKc4nJF1/316lxjmrPKk= github.com/gotd/getdoc v0.44.0/go.mod h1:tPqC2xq2IHhcxvmhdRfTA4ZK0YQKtJtbyZgbQcSuAXI= github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk= @@ -36,7 +50,6 @@ github.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZU github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -45,13 +58,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ogen-go/ogen v1.8.1 h1:7TZ+oIeLkcBiyl0qu0fHPrFUrGWDj3Fi/zKSWg2i2Tg= +github.com/ogen-go/ogen v1.8.1/go.mod h1:2ShRm6u/nXUHuwdVKv2SeaG8enBKPKAE3kSbHwwFh6o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= @@ -70,6 +84,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -86,8 +102,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0= -golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= @@ -112,6 +128,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -139,6 +156,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/telegram/client_external_test.go b/telegram/client_external_test.go index 4d30600d6..0f084d87e 100644 --- a/telegram/client_external_test.go +++ b/telegram/client_external_test.go @@ -2,8 +2,6 @@ package telegram_test import ( "context" - "os" - "strconv" "strings" "testing" "time" @@ -81,10 +79,6 @@ const dialog = `— Да? func TestExternalE2EUsersDialog(t *testing.T) { testutil.SkipExternal(t) - if v, _ := strconv.ParseBool(os.Getenv("GOTD_E2E_DIALOGS_BROKEN")); v { - // TODO(ernado): enable when fixed - t.Skip("Dialogs are broken.") - } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() diff --git a/telegram/internal/e2etest/auth.go b/telegram/internal/e2etest/auth.go index 64279882c..19fd06099 100644 --- a/telegram/internal/e2etest/auth.go +++ b/telegram/internal/e2etest/auth.go @@ -2,6 +2,7 @@ package e2etest import ( "context" + "time" "github.com/cenkalti/backoff/v4" "github.com/go-faster/errors" @@ -10,6 +11,15 @@ import ( ) func (s *Suite) createFlow(ctx context.Context) (auth.Flow, error) { + if s.manager != nil { + account, err := s.manager.Acquire(ctx) + if err != nil { + return auth.Flow{}, errors.Wrap(err, "acquire account") + } + s.closers = append(s.closers, account.Close) + return auth.NewFlow(account.UserAuthenticator, auth.SendCodeOptions{}), nil + } + var ua auth.UserAuthenticator for { ua = auth.Test(s.rand, s.dc) @@ -51,7 +61,10 @@ func (s *Suite) Authenticate(ctx context.Context, client auth.FlowClient) error // RetryAuthenticate authenticates client on test server. func (s *Suite) RetryAuthenticate(ctx context.Context, client auth.FlowClient) error { - bck := backoff.WithContext(backoff.NewExponentialBackOff(), ctx) + bc := backoff.NewExponentialBackOff() + bc.MaxElapsedTime = time.Minute + bc.MaxInterval = time.Second * 3 + bck := backoff.WithContext(bc, ctx) return backoff.Retry(func() error { return s.Authenticate(ctx, client) }, bck) diff --git a/telegram/internal/e2etest/auth_test.go b/telegram/internal/e2etest/auth_test.go index 5e3b68525..6c3a625b9 100644 --- a/telegram/internal/e2etest/auth_test.go +++ b/telegram/internal/e2etest/auth_test.go @@ -55,6 +55,9 @@ func TestSuite_Authenticate(t *testing.T) { s := NewSuite(t, TestOptions{ Logger: logger, }) + if s.manager != nil { + t.Skip("Not testing external manager") + } flow := &mockFlow{} require.NoError(t, s.Authenticate(ctx, flow)) diff --git a/telegram/internal/e2etest/suite.go b/telegram/internal/e2etest/suite.go index 0a222ccf1..ae2487d12 100644 --- a/telegram/internal/e2etest/suite.go +++ b/telegram/internal/e2etest/suite.go @@ -2,13 +2,17 @@ package e2etest import ( "io" + "os" + "strconv" "sync" + "testing" "github.com/stretchr/testify/require" "go.uber.org/zap" "github.com/gotd/td/telegram" "github.com/gotd/td/telegram/dcs" + "github.com/gotd/td/tgacc" ) // Suite is struct which contains external E2E test parameters. @@ -18,6 +22,8 @@ type Suite struct { appHash string dc int logger *zap.Logger + manager *tgacc.TestAccountManager + closers []func() error rand io.Reader // already used phone numbers @@ -25,18 +31,42 @@ type Suite struct { usedMux sync.Mutex } +// Close closes all resources. +func (s *Suite) Close() error { + var err error + for _, closer := range s.closers { + if e := closer(); e != nil { + err = e + } + } + return err +} + // NewSuite creates new Suite. -func NewSuite(tb require.TestingT, config TestOptions) *Suite { +func NewSuite(t *testing.T, config TestOptions) *Suite { config.setDefaults() - return &Suite{ - TB: tb, + manager, err := tgacc.NewTestAccountManager() + require.NoError(t, err) + s := &Suite{ + TB: t, appID: config.AppID, appHash: config.AppHash, dc: config.DC, logger: config.Logger, + manager: manager, rand: config.Random, - used: map[string]struct{}{}, + used: make(map[string]struct{}), } + if managerEnabled, _ := strconv.ParseBool(os.Getenv("TEST_ACCOUNTS_BROKEN")); managerEnabled { + t.Log("External test accounts are used as per TEST_ACCOUNTS_BROKEN") + } else { + t.Log("Normal test accounts are used") + s.manager = nil // disable manager + } + t.Cleanup(func() { + require.NoError(t, s.Close()) + }) + return s } // Client creates new *telegram.Client using this suite. diff --git a/testutil/tools.go b/testutil/tools.go new file mode 100644 index 000000000..bb0c4c6e9 --- /dev/null +++ b/testutil/tools.go @@ -0,0 +1,13 @@ +//go:build testutil + +package bot + +import ( + _ "github.com/ogen-go/ogen" + _ "github.com/ogen-go/ogen/conv" + _ "github.com/ogen-go/ogen/gen" + _ "github.com/ogen-go/ogen/gen/genfs" + _ "github.com/ogen-go/ogen/middleware" + _ "github.com/ogen-go/ogen/ogenerrors" + _ "github.com/ogen-go/ogen/otelogen" +) diff --git a/tgacc/.ogen.yml b/tgacc/.ogen.yml new file mode 100644 index 000000000..186849c98 --- /dev/null +++ b/tgacc/.ogen.yml @@ -0,0 +1,6 @@ +generator: + features: + enable: + - "ogen/otel" + disable: + - "paths/server" diff --git a/tgacc/generate.go b/tgacc/generate.go new file mode 100644 index 000000000..23091a007 --- /dev/null +++ b/tgacc/generate.go @@ -0,0 +1,3 @@ +package tgacc + +//go:generate go run -mod=mod github.com/ogen-go/ogen/cmd/ogen --target oas --package oas --clean tgacc.openapi.yaml diff --git a/tgacc/oas/oas_cfg_gen.go b/tgacc/oas/oas_cfg_gen.go new file mode 100644 index 000000000..8b4059e7f --- /dev/null +++ b/tgacc/oas/oas_cfg_gen.go @@ -0,0 +1,143 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenregex" + "github.com/ogen-go/ogen/otelogen" +) + +var regexMap = map[string]ogenregex.Regexp{ + "[[:xdigit:]]{16}": ogenregex.MustCompile("[[:xdigit:]]{16}"), + "[[:xdigit:]]{32}": ogenregex.MustCompile("[[:xdigit:]]{32}"), + "^[0-9]{3,6}$": ogenregex.MustCompile("^[0-9]{3,6}$"), + "^[0-9]{7,15}$": ogenregex.MustCompile("^[0-9]{7,15}$"), +} +var ( + // Allocate option closure once. + clientSpanKind = trace.WithSpanKind(trace.SpanKindClient) +) + +type ( + optionFunc[C any] func(*C) + otelOptionFunc func(*otelConfig) +) + +type otelConfig struct { + TracerProvider trace.TracerProvider + Tracer trace.Tracer + MeterProvider metric.MeterProvider + Meter metric.Meter +} + +func (cfg *otelConfig) initOTEL() { + if cfg.TracerProvider == nil { + cfg.TracerProvider = otel.GetTracerProvider() + } + if cfg.MeterProvider == nil { + cfg.MeterProvider = otel.GetMeterProvider() + } + cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name, + trace.WithInstrumentationVersion(otelogen.SemVersion()), + ) + cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name, + metric.WithInstrumentationVersion(otelogen.SemVersion()), + ) +} + +type clientConfig struct { + otelConfig + Client ht.Client +} + +// ClientOption is client config option. +type ClientOption interface { + applyClient(*clientConfig) +} + +var _ ClientOption = (optionFunc[clientConfig])(nil) + +func (o optionFunc[C]) applyClient(c *C) { + o(c) +} + +var _ ClientOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyClient(c *clientConfig) { + o(&c.otelConfig) +} + +func newClientConfig(opts ...ClientOption) clientConfig { + cfg := clientConfig{ + Client: http.DefaultClient, + } + for _, opt := range opts { + opt.applyClient(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseClient struct { + cfg clientConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (cfg clientConfig) baseClient() (c baseClient, err error) { + c = baseClient{cfg: cfg} + if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil { + return c, err + } + return c, nil +} + +// Option is config option. +type Option interface { + ClientOption +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a meter. +// +// If none is specified, the otel.GetMeterProvider() is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.MeterProvider = provider + } + }) +} + +// WithClient specifies http client to use. +func WithClient(client ht.Client) ClientOption { + return optionFunc[clientConfig](func(cfg *clientConfig) { + if client != nil { + cfg.Client = client + } + }) +} diff --git a/tgacc/oas/oas_client_gen.go b/tgacc/oas/oas_client_gen.go new file mode 100644 index 000000000..b7d60c603 --- /dev/null +++ b/tgacc/oas/oas_client_gen.go @@ -0,0 +1,478 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "context" + "net/url" + "strings" + "time" + + "github.com/go-faster/errors" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" + + "github.com/ogen-go/ogen/conv" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" + "github.com/ogen-go/ogen/uri" +) + +// Invoker invokes operations described by OpenAPI v3 specification. +type Invoker interface { + // AcquireTelegramAccount invokes acquireTelegramAccount operation. + // + // Acquire telegram account. + // + // POST /api/telegram/account/acquire + AcquireTelegramAccount(ctx context.Context, request *AcquireTelegramAccountReq) (*AcquireTelegramAccountOK, error) + // GetHealth invokes getHealth operation. + // + // Get health. + // + // GET /api/health + GetHealth(ctx context.Context) (*Health, error) + // HeartbeatTelegramAccount invokes heartbeatTelegramAccount operation. + // + // Heartbeat telegram account. + // + // GET /api/telegram/account/heartbeat/{token} + HeartbeatTelegramAccount(ctx context.Context, params HeartbeatTelegramAccountParams) error + // ReceiveTelegramCode invokes receiveTelegramCode operation. + // + // Receive telegram code. + // + // GET /api/telegram/code/receive/{token} + ReceiveTelegramCode(ctx context.Context, params ReceiveTelegramCodeParams) (*ReceiveTelegramCodeOK, error) +} + +// Client implements OAS client. +type Client struct { + serverURL *url.URL + sec SecuritySource + baseClient +} + +func trimTrailingSlashes(u *url.URL) { + u.Path = strings.TrimRight(u.Path, "/") + u.RawPath = strings.TrimRight(u.RawPath, "/") +} + +// NewClient initializes new Client defined by OAS. +func NewClient(serverURL string, sec SecuritySource, opts ...ClientOption) (*Client, error) { + u, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + trimTrailingSlashes(u) + + c, err := newClientConfig(opts...).baseClient() + if err != nil { + return nil, err + } + return &Client{ + serverURL: u, + sec: sec, + baseClient: c, + }, nil +} + +type serverURLKey struct{} + +// WithServerURL sets context key to override server URL. +func WithServerURL(ctx context.Context, u *url.URL) context.Context { + return context.WithValue(ctx, serverURLKey{}, u) +} + +func (c *Client) requestURL(ctx context.Context) *url.URL { + u, ok := ctx.Value(serverURLKey{}).(*url.URL) + if !ok { + return c.serverURL + } + return u +} + +// AcquireTelegramAccount invokes acquireTelegramAccount operation. +// +// Acquire telegram account. +// +// POST /api/telegram/account/acquire +func (c *Client) AcquireTelegramAccount(ctx context.Context, request *AcquireTelegramAccountReq) (*AcquireTelegramAccountOK, error) { + res, err := c.sendAcquireTelegramAccount(ctx, request) + return res, err +} + +func (c *Client) sendAcquireTelegramAccount(ctx context.Context, request *AcquireTelegramAccountReq) (res *AcquireTelegramAccountOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("acquireTelegramAccount"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/telegram/account/acquire"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, AcquireTelegramAccountOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/telegram/account/acquire" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeAcquireTelegramAccountRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:TokenAuth" + switch err := c.securityTokenAuth(ctx, AcquireTelegramAccountOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"TokenAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeAcquireTelegramAccountResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetHealth invokes getHealth operation. +// +// Get health. +// +// GET /api/health +func (c *Client) GetHealth(ctx context.Context) (*Health, error) { + res, err := c.sendGetHealth(ctx) + return res, err +} + +func (c *Client) sendGetHealth(ctx context.Context) (res *Health, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getHealth"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/health"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetHealthOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/health" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetHealthResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// HeartbeatTelegramAccount invokes heartbeatTelegramAccount operation. +// +// Heartbeat telegram account. +// +// GET /api/telegram/account/heartbeat/{token} +func (c *Client) HeartbeatTelegramAccount(ctx context.Context, params HeartbeatTelegramAccountParams) error { + _, err := c.sendHeartbeatTelegramAccount(ctx, params) + return err +} + +func (c *Client) sendHeartbeatTelegramAccount(ctx context.Context, params HeartbeatTelegramAccountParams) (res *HeartbeatTelegramAccountOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("heartbeatTelegramAccount"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/telegram/account/heartbeat/{token}"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, HeartbeatTelegramAccountOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/api/telegram/account/heartbeat/" + { + // Encode "token" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "token", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.UUIDToString(params.Token)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "forget" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "forget", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Forget.Get(); ok { + return e.EncodeValue(conv.BoolToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeHeartbeatTelegramAccountResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ReceiveTelegramCode invokes receiveTelegramCode operation. +// +// Receive telegram code. +// +// GET /api/telegram/code/receive/{token} +func (c *Client) ReceiveTelegramCode(ctx context.Context, params ReceiveTelegramCodeParams) (*ReceiveTelegramCodeOK, error) { + res, err := c.sendReceiveTelegramCode(ctx, params) + return res, err +} + +func (c *Client) sendReceiveTelegramCode(ctx context.Context, params ReceiveTelegramCodeParams) (res *ReceiveTelegramCodeOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("receiveTelegramCode"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/telegram/code/receive/{token}"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ReceiveTelegramCodeOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/api/telegram/code/receive/" + { + // Encode "token" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "token", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.UUIDToString(params.Token)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeReceiveTelegramCodeResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} diff --git a/tgacc/oas/oas_json_gen.go b/tgacc/oas/oas_json_gen.go new file mode 100644 index 000000000..76e401dab --- /dev/null +++ b/tgacc/oas/oas_json_gen.go @@ -0,0 +1,850 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "math/bits" + "strconv" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/json" + "github.com/ogen-go/ogen/validate" +) + +// Encode implements json.Marshaler. +func (s *AcquireTelegramAccountOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *AcquireTelegramAccountOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("account_id") + s.AccountID.Encode(e) + } + { + e.FieldStart("token") + json.EncodeUUID(e, s.Token) + } +} + +var jsonFieldsNameOfAcquireTelegramAccountOK = [2]string{ + 0: "account_id", + 1: "token", +} + +// Decode decodes AcquireTelegramAccountOK from json. +func (s *AcquireTelegramAccountOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode AcquireTelegramAccountOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "account_id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.AccountID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"account_id\"") + } + case "token": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := json.DecodeUUID(d) + s.Token = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"token\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode AcquireTelegramAccountOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfAcquireTelegramAccountOK) { + name = jsonFieldsNameOfAcquireTelegramAccountOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *AcquireTelegramAccountOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *AcquireTelegramAccountOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *AcquireTelegramAccountReq) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *AcquireTelegramAccountReq) encodeFields(e *jx.Encoder) { + { + e.FieldStart("repo_owner") + e.Str(s.RepoOwner) + } + { + e.FieldStart("repo_name") + e.Str(s.RepoName) + } + { + e.FieldStart("job") + e.Str(s.Job) + } + { + e.FieldStart("run_id") + e.Int64(s.RunID) + } + { + e.FieldStart("run_attempt") + e.Int(s.RunAttempt) + } +} + +var jsonFieldsNameOfAcquireTelegramAccountReq = [5]string{ + 0: "repo_owner", + 1: "repo_name", + 2: "job", + 3: "run_id", + 4: "run_attempt", +} + +// Decode decodes AcquireTelegramAccountReq from json. +func (s *AcquireTelegramAccountReq) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode AcquireTelegramAccountReq to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "repo_owner": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.RepoOwner = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"repo_owner\"") + } + case "repo_name": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.RepoName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"repo_name\"") + } + case "job": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Job = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"job\"") + } + case "run_id": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Int64() + s.RunID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"run_id\"") + } + case "run_attempt": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Int() + s.RunAttempt = int(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"run_attempt\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode AcquireTelegramAccountReq") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00011111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfAcquireTelegramAccountReq) { + name = jsonFieldsNameOfAcquireTelegramAccountReq[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *AcquireTelegramAccountReq) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *AcquireTelegramAccountReq) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Error) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Error) encodeFields(e *jx.Encoder) { + { + e.FieldStart("error_message") + e.Str(s.ErrorMessage) + } + { + if s.TraceID.Set { + e.FieldStart("trace_id") + s.TraceID.Encode(e) + } + } + { + if s.SpanID.Set { + e.FieldStart("span_id") + s.SpanID.Encode(e) + } + } +} + +var jsonFieldsNameOfError = [3]string{ + 0: "error_message", + 1: "trace_id", + 2: "span_id", +} + +// Decode decodes Error from json. +func (s *Error) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Error to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "error_message": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.ErrorMessage = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"error_message\"") + } + case "trace_id": + if err := func() error { + s.TraceID.Reset() + if err := s.TraceID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"trace_id\"") + } + case "span_id": + if err := func() error { + s.SpanID.Reset() + if err := s.SpanID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"span_id\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Error") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfError) { + name = jsonFieldsNameOfError[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Error) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Error) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Health) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Health) encodeFields(e *jx.Encoder) { + { + e.FieldStart("status") + e.Str(s.Status) + } + { + e.FieldStart("version") + e.Str(s.Version) + } + { + e.FieldStart("commit") + e.Str(s.Commit) + } + { + e.FieldStart("build_date") + json.EncodeDateTime(e, s.BuildDate) + } +} + +var jsonFieldsNameOfHealth = [4]string{ + 0: "status", + 1: "version", + 2: "commit", + 3: "build_date", +} + +// Decode decodes Health from json. +func (s *Health) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Health to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "status": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.Status = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"status\"") + } + case "version": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Version = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"version\"") + } + case "commit": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Commit = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"commit\"") + } + case "build_date": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := json.DecodeDateTime(d) + s.BuildDate = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"build_date\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Health") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00001111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfHealth) { + name = jsonFieldsNameOfHealth[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Health) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Health) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes SpanID as json. +func (o OptSpanID) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes SpanID from json. +func (o *OptSpanID) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptSpanID to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptSpanID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptSpanID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes string as json. +func (o OptString) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Str(string(o.Value)) +} + +// Decode decodes string from json. +func (o *OptString) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptString to nil") + } + o.Set = true + v, err := d.Str() + if err != nil { + return err + } + o.Value = string(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes TraceID as json. +func (o OptTraceID) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes TraceID from json. +func (o *OptTraceID) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptTraceID to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptTraceID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptTraceID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ReceiveTelegramCodeOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ReceiveTelegramCodeOK) encodeFields(e *jx.Encoder) { + { + if s.Code.Set { + e.FieldStart("code") + s.Code.Encode(e) + } + } +} + +var jsonFieldsNameOfReceiveTelegramCodeOK = [1]string{ + 0: "code", +} + +// Decode decodes ReceiveTelegramCodeOK from json. +func (s *ReceiveTelegramCodeOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ReceiveTelegramCodeOK to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "code": + if err := func() error { + s.Code.Reset() + if err := s.Code.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"code\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ReceiveTelegramCodeOK") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ReceiveTelegramCodeOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ReceiveTelegramCodeOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes SpanID as json. +func (s SpanID) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes SpanID from json. +func (s *SpanID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SpanID to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = SpanID(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s SpanID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SpanID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes TelegramAccountID as json. +func (s TelegramAccountID) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes TelegramAccountID from json. +func (s *TelegramAccountID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode TelegramAccountID to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = TelegramAccountID(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s TelegramAccountID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *TelegramAccountID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes TraceID as json. +func (s TraceID) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes TraceID from json. +func (s *TraceID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode TraceID to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = TraceID(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s TraceID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *TraceID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/tgacc/oas/oas_operations_gen.go b/tgacc/oas/oas_operations_gen.go new file mode 100644 index 000000000..4ee567c98 --- /dev/null +++ b/tgacc/oas/oas_operations_gen.go @@ -0,0 +1,13 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +// OperationName is the ogen operation name +type OperationName = string + +const ( + AcquireTelegramAccountOperation OperationName = "AcquireTelegramAccount" + GetHealthOperation OperationName = "GetHealth" + HeartbeatTelegramAccountOperation OperationName = "HeartbeatTelegramAccount" + ReceiveTelegramCodeOperation OperationName = "ReceiveTelegramCode" +) diff --git a/tgacc/oas/oas_parameters_gen.go b/tgacc/oas/oas_parameters_gen.go new file mode 100644 index 000000000..3a6e4ef8e --- /dev/null +++ b/tgacc/oas/oas_parameters_gen.go @@ -0,0 +1,18 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "github.com/google/uuid" +) + +// HeartbeatTelegramAccountParams is parameters of heartbeatTelegramAccount operation. +type HeartbeatTelegramAccountParams struct { + Token uuid.UUID + Forget OptBool +} + +// ReceiveTelegramCodeParams is parameters of receiveTelegramCode operation. +type ReceiveTelegramCodeParams struct { + Token uuid.UUID +} diff --git a/tgacc/oas/oas_request_encoders_gen.go b/tgacc/oas/oas_request_encoders_gen.go new file mode 100644 index 000000000..07fa2f9f6 --- /dev/null +++ b/tgacc/oas/oas_request_encoders_gen.go @@ -0,0 +1,26 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "bytes" + "net/http" + + "github.com/go-faster/jx" + + ht "github.com/ogen-go/ogen/http" +) + +func encodeAcquireTelegramAccountRequest( + req *AcquireTelegramAccountReq, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/tgacc/oas/oas_response_decoders_gen.go b/tgacc/oas/oas_response_decoders_gen.go new file mode 100644 index 000000000..14f8793e4 --- /dev/null +++ b/tgacc/oas/oas_response_decoders_gen.go @@ -0,0 +1,369 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func decodeAcquireTelegramAccountResponse(resp *http.Response) (res *AcquireTelegramAccountOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response AcquireTelegramAccountOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetHealthResponse(resp *http.Response) (res *Health, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Health + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeHeartbeatTelegramAccountResponse(resp *http.Response) (res *HeartbeatTelegramAccountOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + return &HeartbeatTelegramAccountOK{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeReceiveTelegramCodeResponse(resp *http.Response) (res *ReceiveTelegramCodeOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ReceiveTelegramCodeOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} diff --git a/tgacc/oas/oas_schemas_gen.go b/tgacc/oas/oas_schemas_gen.go new file mode 100644 index 000000000..d686e4fd0 --- /dev/null +++ b/tgacc/oas/oas_schemas_gen.go @@ -0,0 +1,440 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "fmt" + "time" + + "github.com/google/uuid" +) + +func (s *ErrorStatusCode) Error() string { + return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) +} + +type AcquireTelegramAccountOK struct { + AccountID TelegramAccountID `json:"account_id"` + // Access token. + Token uuid.UUID `json:"token"` +} + +// GetAccountID returns the value of AccountID. +func (s *AcquireTelegramAccountOK) GetAccountID() TelegramAccountID { + return s.AccountID +} + +// GetToken returns the value of Token. +func (s *AcquireTelegramAccountOK) GetToken() uuid.UUID { + return s.Token +} + +// SetAccountID sets the value of AccountID. +func (s *AcquireTelegramAccountOK) SetAccountID(val TelegramAccountID) { + s.AccountID = val +} + +// SetToken sets the value of Token. +func (s *AcquireTelegramAccountOK) SetToken(val uuid.UUID) { + s.Token = val +} + +type AcquireTelegramAccountReq struct { + // Repository owner. + RepoOwner string `json:"repo_owner"` + // Repository name. + RepoName string `json:"repo_name"` + // Job ID. + Job string `json:"job"` + RunID int64 `json:"run_id"` + RunAttempt int `json:"run_attempt"` +} + +// GetRepoOwner returns the value of RepoOwner. +func (s *AcquireTelegramAccountReq) GetRepoOwner() string { + return s.RepoOwner +} + +// GetRepoName returns the value of RepoName. +func (s *AcquireTelegramAccountReq) GetRepoName() string { + return s.RepoName +} + +// GetJob returns the value of Job. +func (s *AcquireTelegramAccountReq) GetJob() string { + return s.Job +} + +// GetRunID returns the value of RunID. +func (s *AcquireTelegramAccountReq) GetRunID() int64 { + return s.RunID +} + +// GetRunAttempt returns the value of RunAttempt. +func (s *AcquireTelegramAccountReq) GetRunAttempt() int { + return s.RunAttempt +} + +// SetRepoOwner sets the value of RepoOwner. +func (s *AcquireTelegramAccountReq) SetRepoOwner(val string) { + s.RepoOwner = val +} + +// SetRepoName sets the value of RepoName. +func (s *AcquireTelegramAccountReq) SetRepoName(val string) { + s.RepoName = val +} + +// SetJob sets the value of Job. +func (s *AcquireTelegramAccountReq) SetJob(val string) { + s.Job = val +} + +// SetRunID sets the value of RunID. +func (s *AcquireTelegramAccountReq) SetRunID(val int64) { + s.RunID = val +} + +// SetRunAttempt sets the value of RunAttempt. +func (s *AcquireTelegramAccountReq) SetRunAttempt(val int) { + s.RunAttempt = val +} + +// Error occurred while processing request. +// Ref: #/components/schemas/Error +type Error struct { + // Human-readable error message. + ErrorMessage string `json:"error_message"` + TraceID OptTraceID `json:"trace_id"` + SpanID OptSpanID `json:"span_id"` +} + +// GetErrorMessage returns the value of ErrorMessage. +func (s *Error) GetErrorMessage() string { + return s.ErrorMessage +} + +// GetTraceID returns the value of TraceID. +func (s *Error) GetTraceID() OptTraceID { + return s.TraceID +} + +// GetSpanID returns the value of SpanID. +func (s *Error) GetSpanID() OptSpanID { + return s.SpanID +} + +// SetErrorMessage sets the value of ErrorMessage. +func (s *Error) SetErrorMessage(val string) { + s.ErrorMessage = val +} + +// SetTraceID sets the value of TraceID. +func (s *Error) SetTraceID(val OptTraceID) { + s.TraceID = val +} + +// SetSpanID sets the value of SpanID. +func (s *Error) SetSpanID(val OptSpanID) { + s.SpanID = val +} + +// ErrorStatusCode wraps Error with StatusCode. +type ErrorStatusCode struct { + StatusCode int + Response Error +} + +// GetStatusCode returns the value of StatusCode. +func (s *ErrorStatusCode) GetStatusCode() int { + return s.StatusCode +} + +// GetResponse returns the value of Response. +func (s *ErrorStatusCode) GetResponse() Error { + return s.Response +} + +// SetStatusCode sets the value of StatusCode. +func (s *ErrorStatusCode) SetStatusCode(val int) { + s.StatusCode = val +} + +// SetResponse sets the value of Response. +func (s *ErrorStatusCode) SetResponse(val Error) { + s.Response = val +} + +// Ref: #/components/schemas/Health +type Health struct { + // Health status. + Status string `json:"status"` + // Service version. + Version string `json:"version"` + // Service commit. + Commit string `json:"commit"` + // Service build date. + BuildDate time.Time `json:"build_date"` +} + +// GetStatus returns the value of Status. +func (s *Health) GetStatus() string { + return s.Status +} + +// GetVersion returns the value of Version. +func (s *Health) GetVersion() string { + return s.Version +} + +// GetCommit returns the value of Commit. +func (s *Health) GetCommit() string { + return s.Commit +} + +// GetBuildDate returns the value of BuildDate. +func (s *Health) GetBuildDate() time.Time { + return s.BuildDate +} + +// SetStatus sets the value of Status. +func (s *Health) SetStatus(val string) { + s.Status = val +} + +// SetVersion sets the value of Version. +func (s *Health) SetVersion(val string) { + s.Version = val +} + +// SetCommit sets the value of Commit. +func (s *Health) SetCommit(val string) { + s.Commit = val +} + +// SetBuildDate sets the value of BuildDate. +func (s *Health) SetBuildDate(val time.Time) { + s.BuildDate = val +} + +// HeartbeatTelegramAccountOK is response for HeartbeatTelegramAccount operation. +type HeartbeatTelegramAccountOK struct{} + +// NewOptBool returns new OptBool with value set to v. +func NewOptBool(v bool) OptBool { + return OptBool{ + Value: v, + Set: true, + } +} + +// OptBool is optional bool. +type OptBool struct { + Value bool + Set bool +} + +// IsSet returns true if OptBool was set. +func (o OptBool) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptBool) Reset() { + var v bool + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptBool) SetTo(v bool) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptBool) Get() (v bool, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptBool) Or(d bool) bool { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptSpanID returns new OptSpanID with value set to v. +func NewOptSpanID(v SpanID) OptSpanID { + return OptSpanID{ + Value: v, + Set: true, + } +} + +// OptSpanID is optional SpanID. +type OptSpanID struct { + Value SpanID + Set bool +} + +// IsSet returns true if OptSpanID was set. +func (o OptSpanID) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptSpanID) Reset() { + var v SpanID + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptSpanID) SetTo(v SpanID) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptSpanID) Get() (v SpanID, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptSpanID) Or(d SpanID) SpanID { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptString returns new OptString with value set to v. +func NewOptString(v string) OptString { + return OptString{ + Value: v, + Set: true, + } +} + +// OptString is optional string. +type OptString struct { + Value string + Set bool +} + +// IsSet returns true if OptString was set. +func (o OptString) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptString) Reset() { + var v string + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptString) SetTo(v string) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptString) Get() (v string, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptString) Or(d string) string { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptTraceID returns new OptTraceID with value set to v. +func NewOptTraceID(v TraceID) OptTraceID { + return OptTraceID{ + Value: v, + Set: true, + } +} + +// OptTraceID is optional TraceID. +type OptTraceID struct { + Value TraceID + Set bool +} + +// IsSet returns true if OptTraceID was set. +func (o OptTraceID) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptTraceID) Reset() { + var v TraceID + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptTraceID) SetTo(v TraceID) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptTraceID) Get() (v TraceID, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptTraceID) Or(d TraceID) TraceID { + if v, ok := o.Get(); ok { + return v + } + return d +} + +type ReceiveTelegramCodeOK struct { + // Code. + Code OptString `json:"code"` +} + +// GetCode returns the value of Code. +func (s *ReceiveTelegramCodeOK) GetCode() OptString { + return s.Code +} + +// SetCode sets the value of Code. +func (s *ReceiveTelegramCodeOK) SetCode(val OptString) { + s.Code = val +} + +type SpanID string + +type TelegramAccountID string + +type TokenAuth struct { + APIKey string +} + +// GetAPIKey returns the value of APIKey. +func (s *TokenAuth) GetAPIKey() string { + return s.APIKey +} + +// SetAPIKey sets the value of APIKey. +func (s *TokenAuth) SetAPIKey(val string) { + s.APIKey = val +} + +type TraceID string diff --git a/tgacc/oas/oas_security_gen.go b/tgacc/oas/oas_security_gen.go new file mode 100644 index 000000000..c7f571d7d --- /dev/null +++ b/tgacc/oas/oas_security_gen.go @@ -0,0 +1,25 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "context" + "net/http" + + "github.com/go-faster/errors" +) + +// SecuritySource is provider of security values (tokens, passwords, etc.). +type SecuritySource interface { + // TokenAuth provides tokenAuth security value. + TokenAuth(ctx context.Context, operationName OperationName) (TokenAuth, error) +} + +func (s *Client) securityTokenAuth(ctx context.Context, operationName OperationName, req *http.Request) error { + t, err := s.sec.TokenAuth(ctx, operationName) + if err != nil { + return errors.Wrap(err, "security source \"TokenAuth\"") + } + req.Header.Set("Token", t.APIKey) + return nil +} diff --git a/tgacc/oas/oas_validators_gen.go b/tgacc/oas/oas_validators_gen.go new file mode 100644 index 000000000..d544b2fda --- /dev/null +++ b/tgacc/oas/oas_validators_gen.go @@ -0,0 +1,189 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "github.com/go-faster/errors" + + "github.com/ogen-go/ogen/validate" +) + +func (s *AcquireTelegramAccountOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.AccountID.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "account_id", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *Error) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.TraceID.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "trace_id", + Error: err, + }) + } + if err := func() error { + if value, ok := s.SpanID.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "span_id", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ErrorStatusCode) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ReceiveTelegramCodeOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.Code.Get(); ok { + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 0, + MaxLengthSet: false, + Email: false, + Hostname: false, + Regex: regexMap["^[0-9]{3,6}$"], + }).Validate(string(value)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "code", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s SpanID) Validate() error { + alias := (string)(s) + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 0, + MaxLengthSet: false, + Email: false, + Hostname: false, + Regex: regexMap["[[:xdigit:]]{16}"], + }).Validate(string(alias)); err != nil { + return errors.Wrap(err, "string") + } + return nil +} + +func (s TelegramAccountID) Validate() error { + alias := (string)(s) + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 0, + MaxLengthSet: false, + Email: false, + Hostname: false, + Regex: regexMap["^[0-9]{7,15}$"], + }).Validate(string(alias)); err != nil { + return errors.Wrap(err, "string") + } + return nil +} + +func (s TraceID) Validate() error { + alias := (string)(s) + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 0, + MaxLengthSet: false, + Email: false, + Hostname: false, + Regex: regexMap["[[:xdigit:]]{32}"], + }).Validate(string(alias)); err != nil { + return errors.Wrap(err, "string") + } + return nil +} diff --git a/tgacc/tgacc.go b/tgacc/tgacc.go new file mode 100644 index 000000000..191ceed50 --- /dev/null +++ b/tgacc/tgacc.go @@ -0,0 +1,161 @@ +package tgacc + +import ( + "context" + "os" + "strconv" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/go-faster/errors" + "github.com/google/uuid" + + "github.com/gotd/td/telegram/auth" + "github.com/gotd/td/tg" + "github.com/gotd/td/tgacc/oas" +) + +// TestAccountManager is external test account manager. +type TestAccountManager struct { + client *oas.Client +} + +func (t *TestAccountManager) Acquire(ctx context.Context) (*TestAccount, error) { + jobID := os.Getenv("GITHUB_JOB_ID") + if jobID == "" { + return nil, errors.New("GITHUB_JOB_ID is empty") + } + runID, _ := strconv.ParseInt(os.Getenv("GITHUB_RUN_ID"), 10, 64) + if runID == 0 { + return nil, errors.New("GITHUB_RUN_ID is empty") + } + attempt, _ := strconv.Atoi(os.Getenv("GITHUB_RUN_ATTEMPT")) + res, err := t.client.AcquireTelegramAccount(ctx, &oas.AcquireTelegramAccountReq{ + RepoOwner: "gotd", + RepoName: "td", + Job: jobID, + RunID: runID, + RunAttempt: attempt, + }) + if err != nil { + return nil, errors.Wrap(err, "acquire account") + } + + phone := string(res.AccountID) + + ta := &TestAccount{ + Phone: phone, + UserAuthenticator: &codeAuth{ + phone: phone, + client: t.client, + token: res.Token, + }, + + token: res.Token, + client: t.client, + heartbeat: make(chan struct{}), + } + go ta.heartBeats() + return ta, nil +} + +type ghSecuritySource struct{} + +func (s ghSecuritySource) TokenAuth(ctx context.Context, operationName oas.OperationName) (oas.TokenAuth, error) { + return oas.TokenAuth{ + APIKey: os.Getenv("GITHUB_TOKEN"), + }, nil +} + +type TestAccount struct { + Phone string + UserAuthenticator auth.UserAuthenticator + + token uuid.UUID + client *oas.Client + heartbeat chan struct{} +} + +func (t TestAccount) heartBeats() { + ticker := time.NewTicker(time.Second * 10) + defer ticker.Stop() + + for { + select { + case <-t.heartbeat: + return + case <-ticker.C: + t.sendHeartBeat() + } + } +} + +func (t TestAccount) sendHeartBeat() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + _ = t.client.HeartbeatTelegramAccount(ctx, oas.HeartbeatTelegramAccountParams{ + Token: t.token, + }) +} + +// Close releases telegram account. +func (t TestAccount) Close() error { + close(t.heartbeat) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + return t.client.HeartbeatTelegramAccount(ctx, oas.HeartbeatTelegramAccountParams{ + Token: t.token, + Forget: oas.NewOptBool(true), + }) +} + +// codeAuth implements auth.UserAuthenticator prompting the external account +// manager. +type codeAuth struct { + phone string + token uuid.UUID + client *oas.Client +} + +func (codeAuth) SignUp(ctx context.Context) (auth.UserInfo, error) { + return auth.UserInfo{}, errors.New("not implemented") +} + +func (codeAuth) AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error { + return &auth.SignUpRequired{TermsOfService: tos} +} + +func (a codeAuth) Phone(_ context.Context) (string, error) { + return a.phone, nil +} + +func (codeAuth) Password(_ context.Context) (string, error) { + return "", errors.New("password not supported") +} + +func (a codeAuth) Code(ctx context.Context, sentCode *tg.AuthSentCode) (string, error) { + bo := backoff.NewExponentialBackOff() + bo.MaxElapsedTime = time.Minute + bo.MaxInterval = time.Second + + return backoff.RetryWithData(func() (string, error) { + res, err := a.client.ReceiveTelegramCode(ctx, oas.ReceiveTelegramCodeParams{ + Token: a.token, + }) + if err != nil { + return "", err + } + if res.Code.Value == "" { + return "", errors.New("no code") + } + return res.Code.Value, err + }, bo) +} + +func NewTestAccountManager() (*TestAccountManager, error) { + client, err := oas.NewClient("https://bot.gotd.dev", ghSecuritySource{}) + if err != nil { + return nil, errors.Wrap(err, "create client") + } + return &TestAccountManager{client: client}, nil +} diff --git a/tgacc/tgacc.openapi.yaml b/tgacc/tgacc.openapi.yaml new file mode 100644 index 000000000..b14079329 --- /dev/null +++ b/tgacc/tgacc.openapi.yaml @@ -0,0 +1,213 @@ +openapi: 3.0.0 +info: + title: gotd bot api + description: gotd bot api + version: 1.0.0 +servers: + - url: 'http://localhost:8080' +paths: + /api/health: + get: + operationId: "getHealth" + description: "get health" + responses: + 200: + description: Health + content: + "application/json": + schema: + $ref: "#/components/schemas/Health" + default: + $ref: "#/components/responses/Error" + /api/telegram/account/heartbeat/{token}: + get: + operationId: "heartbeatTelegramAccount" + description: "heartbeat telegram account" + parameters: + - name: token + in: path + required: true + schema: + type: string + format: uuid + - name: forget + in: query + required: false + schema: + type: boolean + responses: + 200: + description: "Telegram account heartbeat" + default: + $ref: "#/components/responses/Error" + /api/telegram/code/receive/{token}: + get: + operationId: "receiveTelegramCode" + description: "receive telegram code" + parameters: + - name: token + in: path + required: true + schema: + type: string + format: uuid + responses: + 200: + description: "Telegram code received" + content: + "application/json": + schema: + type: object + properties: + code: + type: string + description: "Code" + example: "12345" + pattern: "^[0-9]{3,6}$" + default: + $ref: "#/components/responses/Error" + /api/telegram/account/acquire: + post: + security: + - tokenAuth: [] + operationId: "acquireTelegramAccount" + description: "acquire telegram account" + requestBody: + required: true + description: Info about current github workflow job + content: + application/json: + schema: + type: object + required: + - repo_owner + - repo_name + - commit_sha + - job + - run_id + - run_attempt + properties: + repo_owner: + type: string + description: "Repository owner" + example: "owner" + repo_name: + type: string + description: "Repository name" + example: "repo" + job: + type: string + description: "Job ID" + run_id: + type: integer + format: int64 + run_attempt: + type: integer + responses: + 200: + description: "Telegram account acquired" + content: + "application/json": + schema: + type: object + required: + - account_id + - token + properties: + account_id: + $ref: "#/components/schemas/TelegramAccountID" + token: + type: string + description: "Access token" + format: uuid + default: + $ref: "#/components/responses/Error" +components: + parameters: + TelegramAccountID: + name: id + in: path + required: true + schema: + $ref: "#/components/schemas/TelegramAccountID" + securitySchemes: + tokenAuth: + type: apiKey + in: header + name: Token + schemas: + TelegramAccountID: + type: string + pattern: "^[0-9]{7,15}$" + example: 71234567890 + # Error-related schemas. + TraceID: + type: string + description: W3C trace-id + pattern: "[[:xdigit:]]{32}" + example: 0af7651916cd43dd8448eb211c80319c + externalDocs: + url: "https://www.w3.org/TR/trace-context/#trace-id" + description: "W3C Trace Context specification" + SpanID: + type: string + description: W3C parent-id (span) + pattern: "[[:xdigit:]]{16}" + example: b7ad6b7169203331 + externalDocs: + url: "https://www.w3.org/TR/trace-context/#parent-id" + description: "W3C Trace Context specification" + Error: + title: Structured error + description: Error occurred while processing request + externalDocs: + url: "https://pfm.pages.gitlab.corp.mail.ru/docs/dev/spec/v2/errors" + description: "Structured error specification" + type: object + required: + - error_message + - body + properties: + error_message: + type: string + description: "Human-readable error message" + example: "Something went wrong" + trace_id: + $ref: "#/components/schemas/TraceID" + span_id: + $ref: "#/components/schemas/SpanID" + + # Health-related schemas. + Health: + type: object + required: + - status + - version + - commit + - build_date + properties: + status: + type: string + description: "Health status" + example: "ok" + version: + type: string + description: "Service version" + example: "1.0.0" + commit: + type: string + description: "Service commit" + example: "c1b2d3f4" + build_date: + type: string + description: "Service build date" + example: "2022-01-01T00:00:00Z" + format: date-time + responses: + Error: + description: Structured error response. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + diff --git a/tgacc/tgacc_test.go b/tgacc/tgacc_test.go new file mode 100644 index 000000000..b737154ce --- /dev/null +++ b/tgacc/tgacc_test.go @@ -0,0 +1,25 @@ +package tgacc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/gotd/td/testutil" +) + +func TestExternalE2E(t *testing.T) { + testutil.SkipExternal(t) + + manager, err := NewTestAccountManager() + require.NoError(t, err) + + ctx := context.Background() + client, err := manager.Acquire(ctx) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, client.Close()) + }) +}