Bump the go-modules-updates group in /backend with 5 updates
Bumps the go-modules-updates group in /backend with 5 updates: | Package | From | To | | --- | --- | --- | | [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) | `1.10.0` | `1.10.1` | | [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.1.0` | `5.2.0` | | [github.com/go-pkgz/rest](https://github.com/go-pkgz/rest) | `1.19.0` | `1.20.2` | | [golang.org/x/image](https://github.com/golang/image) | `0.22.0` | `0.23.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.31.0` | `0.33.0` | Updates `github.com/PuerkitoBio/goquery` from 1.10.0 to 1.10.1 - [Release notes](https://github.com/PuerkitoBio/goquery/releases) - [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.10.0...v1.10.1) Updates `github.com/go-chi/chi/v5` from 5.1.0 to 5.2.0 - [Release notes](https://github.com/go-chi/chi/releases) - [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-chi/chi/compare/v5.1.0...v5.2.0) Updates `github.com/go-pkgz/rest` from 1.19.0 to 1.20.2 - [Release notes](https://github.com/go-pkgz/rest/releases) - [Commits](https://github.com/go-pkgz/rest/compare/v1.19.0...v1.20.2) Updates `golang.org/x/image` from 0.22.0 to 0.23.0 - [Commits](https://github.com/golang/image/compare/v0.22.0...v0.23.0) Updates `golang.org/x/net` from 0.31.0 to 0.33.0 - [Commits](https://github.com/golang/net/compare/v0.31.0...v0.33.0) --- updated-dependencies: - dependency-name: github.com/PuerkitoBio/goquery dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: github.com/go-chi/chi/v5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: github.com/go-pkgz/rest dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/image dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
@@ -12,19 +12,19 @@ require (
|
||||
|
||||
require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.1 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/didip/tollbooth/v7 v7.0.2 // indirect
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.1.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.0 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
|
||||
github.com/go-pkgz/rest v1.19.0 // indirect
|
||||
github.com/go-pkgz/rest v1.20.2 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -33,8 +33,9 @@ require (
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
golang.org/x/image v0.22.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
@@ -10,8 +10,8 @@ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -22,11 +22,11 @@ github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi6
|
||||
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
@@ -38,8 +38,9 @@ github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiw
|
||||
github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM=
|
||||
github.com/go-pkgz/lgr v0.11.1/go.mod h1:tgDF4RXQnBfIgJqjgkv0yOeTQ3F1yewWIZkpUhHnAkU=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.19.0 h1:FNMi5QX5dDIkuC+/e0r+CWsTuOTwUiWMRSA16Ou+9+A=
|
||||
github.com/go-pkgz/rest v1.19.0/go.mod h1:Po+W6zQzpMPP6XDGLdAN2aW7UKk1IyrLSb48Lp1N3oQ=
|
||||
github.com/go-pkgz/rest v1.20.2 h1:6wYWo85H7xFU09FadVKKc5LKIfIpCStBXJj9F/P4COc=
|
||||
github.com/go-pkgz/rest v1.20.2/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -79,20 +80,34 @@ go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
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=
|
||||
@@ -101,22 +116,36 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
||||
+15
-15
@@ -6,20 +6,20 @@ toolchain go1.23.3
|
||||
|
||||
require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0
|
||||
github.com/PuerkitoBio/goquery v1.10.0
|
||||
github.com/PuerkitoBio/goquery v1.10.1
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/didip/tollbooth/v7 v7.0.2
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241208183119-88b3a842be9f
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241221172452-ba01339372ba
|
||||
github.com/go-pkgz/jrpc v0.3.0
|
||||
github.com/go-pkgz/lcw/v2 v2.0.0
|
||||
github.com/go-pkgz/lgr v0.11.1
|
||||
github.com/go-pkgz/notify v1.2.0
|
||||
github.com/go-pkgz/repeater v1.2.0
|
||||
github.com/go-pkgz/rest v1.19.0
|
||||
github.com/go-pkgz/rest v1.20.2
|
||||
github.com/go-pkgz/syncs v1.3.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -35,20 +35,20 @@ require (
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/image v0.22.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/net v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dghubble/oauth1 v0.7.3 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 // indirect
|
||||
github.com/go-pkgz/email v0.5.0 // indirect
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
|
||||
@@ -57,18 +57,18 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.4.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
||||
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d // indirect
|
||||
github.com/slack-go/slack v0.14.0 // indirect
|
||||
github.com/slack-go/slack v0.15.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver v1.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.1 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
|
||||
+62
-37
@@ -1,10 +1,10 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
@@ -19,16 +19,16 @@ github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6
|
||||
github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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=
|
||||
@@ -41,8 +41,8 @@ github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi6
|
||||
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
@@ -51,8 +51,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
@@ -60,8 +60,8 @@ github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241208183119-88b3a842be9f h1:CDG4Xwk8oq5jFzBtm81vAoB38PgXeUndiSQN/G2vnPA=
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241208183119-88b3a842be9f/go.mod h1:aepr8uqw5PKidS+h2sJDvTWbqzyveHDyKNEAznDb3L8=
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241221172452-ba01339372ba h1:TqoOfThmmcoiSAs/J+y0/3hwS/YRhzR2XepO147jR+I=
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241221172452-ba01339372ba/go.mod h1:VkvKMNbRJz36sdTL1F75khRqpl4yux4u2CujFzCZOeY=
|
||||
github.com/go-pkgz/email v0.5.0 h1:fdtMDGJ8NwyBACLR0LYHaCIK/OeUwZHMhH7Q0+oty9U=
|
||||
github.com/go-pkgz/email v0.5.0/go.mod h1:BdxglsQnymzhfdbnncEE72a6DrucZHy6I+42LK2jLEc=
|
||||
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
@@ -78,8 +78,8 @@ github.com/go-pkgz/notify v1.2.0/go.mod h1:BTHj9ly7xZpaxg91lBdxCxSmkpVQ9R6q5QviX
|
||||
github.com/go-pkgz/repeater v1.2.0 h1:oJFvjyKdTDd5RCzpzxlzYIZFFj6Zfl17rE1aUfu6UjQ=
|
||||
github.com/go-pkgz/repeater v1.2.0/go.mod h1:vypP6xamA53MFmafnGUucqOmALKk36xgKu2hSG73LHM=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.19.0 h1:FNMi5QX5dDIkuC+/e0r+CWsTuOTwUiWMRSA16Ou+9+A=
|
||||
github.com/go-pkgz/rest v1.19.0/go.mod h1:Po+W6zQzpMPP6XDGLdAN2aW7UKk1IyrLSb48Lp1N3oQ=
|
||||
github.com/go-pkgz/rest v1.20.2 h1:6wYWo85H7xFU09FadVKKc5LKIfIpCStBXJj9F/P4COc=
|
||||
github.com/go-pkgz/rest v1.20.2/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks=
|
||||
github.com/go-pkgz/syncs v1.3.2 h1:gmioASlJNy3gNosPlgvWOM2QP0Hdjzn2u+/sUShgd8E=
|
||||
github.com/go-pkgz/syncs v1.3.2/go.mod h1:qjgzpp7OpuhDf7BWsW/FHCu9DLjE32NPy6/vXAXT/Cw=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
@@ -136,8 +136,8 @@ github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPG
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -164,8 +164,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/pkg/errors v0.9.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/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d h1:l3+2LWCbVxn5itfvXAfH9n4YL9jh8l1g5zcncbIc1cs=
|
||||
@@ -179,8 +179,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k=
|
||||
github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/slack-go/slack v0.15.0 h1:LE2lj2y9vqqiOf+qIIy0GvEoxgF1N5yLGZffmEZykt0=
|
||||
github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -198,12 +198,12 @@ github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ
|
||||
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
||||
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
|
||||
github.com/tidwall/buntdb v1.3.1 h1:HKoDF01/aBhl9RjYtbaLnvX9/OuenwvQiC3OP1CcL4o=
|
||||
github.com/tidwall/buntdb v1.3.1/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
|
||||
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
|
||||
github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
|
||||
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
||||
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
|
||||
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
|
||||
@@ -251,19 +251,25 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
|
||||
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
|
||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -276,17 +282,23 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -306,13 +318,21 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -321,6 +341,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -328,6 +351,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
||||
+21
@@ -1,5 +1,26 @@
|
||||
# Changes
|
||||
|
||||
## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.2...compute/metadata/v0.6.0) (2024-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add debug logging ([#11078](https://github.com/googleapis/google-cloud-go/issues/11078)) ([a816814](https://github.com/googleapis/google-cloud-go/commit/a81681463906e4473570a2f426eb0dc2de64e53f))
|
||||
|
||||
## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.1...compute/metadata/v0.5.2) (2024-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Close Response Body for failed request ([#10891](https://github.com/googleapis/google-cloud-go/issues/10891)) ([e91d45e](https://github.com/googleapis/google-cloud-go/commit/e91d45e4757a9e354114509ba9800085d9e0ff1f))
|
||||
|
||||
## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.0...compute/metadata/v0.5.1) (2024-09-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Check error chain for retryable error ([#10840](https://github.com/googleapis/google-cloud-go/issues/10840)) ([2bdedef](https://github.com/googleapis/google-cloud-go/commit/2bdedeff621b223d63cebc4355fcf83bc68412cd))
|
||||
|
||||
## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.4.0...compute/metadata/v0.5.0) (2024-07-10)
|
||||
|
||||
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Code below this point is copied from github.com/googleapis/gax-go/v2/internallog
|
||||
// to avoid the dependency. The compute/metadata module is used by too many
|
||||
// non-client library modules that can't justify the dependency.
|
||||
|
||||
// The handler returned if logging is not enabled.
|
||||
type noOpHandler struct{}
|
||||
|
||||
func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithGroup(_ string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
// httpRequest returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Request] and the associated body.
|
||||
func httpRequest(req *http.Request, body []byte) slog.LogValuer {
|
||||
return &request{
|
||||
req: req,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req *http.Request
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *request) LogValue() slog.Value {
|
||||
if r == nil || r.req == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method))
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String()))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.req.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
// httpResponse returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Response] and the associated body.
|
||||
func httpResponse(resp *http.Response, body []byte) slog.LogValuer {
|
||||
return &response{
|
||||
resp: resp,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp *http.Response
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *response) LogValue() slog.Value {
|
||||
if r == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode)))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.resp.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
func processPayload(payload []byte) (slog.Attr, bool) {
|
||||
peekChar := payload[0]
|
||||
if peekChar == '{' {
|
||||
// JSON object
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else if peekChar == '[' {
|
||||
// JSON array
|
||||
var m []any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else {
|
||||
// Everything else
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.Compact(buf, payload); err != nil {
|
||||
// Write raw payload incase of error
|
||||
buf.Write(payload)
|
||||
}
|
||||
return slog.String("payload", buf.String()), true
|
||||
}
|
||||
return slog.Attr{}, false
|
||||
}
|
||||
+47
-11
@@ -24,6 +24,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -60,7 +61,10 @@ var (
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{hc: newDefaultHTTPClient()}
|
||||
var defaultClient = &Client{
|
||||
hc: newDefaultHTTPClient(),
|
||||
logger: slog.New(noOpHandler{}),
|
||||
}
|
||||
|
||||
func newDefaultHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
@@ -408,17 +412,42 @@ func strsContains(ss []string, s string) bool {
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
hc *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// Options for configuring a [Client].
|
||||
type Options struct {
|
||||
// Client is the HTTP client used to make requests. Optional.
|
||||
Client *http.Client
|
||||
// Logger is used to log information about HTTP request and responses.
|
||||
// If not provided, nothing will be logged. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
if c == nil {
|
||||
return NewWithOptions(&Options{
|
||||
Client: c,
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithOptions returns a Client that is configured with the provided Options.
|
||||
func NewWithOptions(opts *Options) *Client {
|
||||
if opts == nil {
|
||||
return defaultClient
|
||||
}
|
||||
return &Client{hc: c}
|
||||
client := opts.Client
|
||||
if client == nil {
|
||||
client = newDefaultHTTPClient()
|
||||
}
|
||||
logger := opts.Logger
|
||||
if logger == nil {
|
||||
logger = slog.New(noOpHandler{})
|
||||
}
|
||||
return &Client{hc: client, logger: logger}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
@@ -448,14 +477,26 @@ func (c *Client) getETag(ctx context.Context, suffix string) (value, etag string
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
var res *http.Response
|
||||
var reqErr error
|
||||
var body []byte
|
||||
retryer := newRetryer()
|
||||
for {
|
||||
c.logger.DebugContext(ctx, "metadata request", "request", httpRequest(req, nil))
|
||||
res, reqErr = c.hc.Do(req)
|
||||
var code int
|
||||
if res != nil {
|
||||
code = res.StatusCode
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close()
|
||||
return "", "", err
|
||||
}
|
||||
c.logger.DebugContext(ctx, "metadata response", "response", httpResponse(res, body))
|
||||
res.Body.Close()
|
||||
}
|
||||
if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
|
||||
if res != nil && res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
if err := sleep(ctx, delay); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -466,18 +507,13 @@ func (c *Client) getETag(ctx context.Context, suffix string) (value, etag string
|
||||
if reqErr != nil {
|
||||
return "", "", reqErr
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(body)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
return string(body), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
|
||||
+7
-2
@@ -17,10 +17,15 @@
|
||||
|
||||
package metadata
|
||||
|
||||
import "syscall"
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool { return err == syscall.ECONNRESET || err == syscall.ECONNREFUSED }
|
||||
syscallRetryable = func(err error) bool {
|
||||
return errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ Ongoing goquery development is tested on the latest 2 versions of Go.
|
||||
|
||||
**Note that goquery's API is now stable, and will not break.**
|
||||
|
||||
* **2024-12-26 (v1.10.1)** : Update `go.mod` dependencies.
|
||||
* **2024-09-06 (v1.10.0)** : Add `EachIter` which provides an iterator that can be used in `for..range` loops on the `*Selection` object. **goquery now requires Go version 1.23+** (thanks [@amikai](https://github.com/amikai)).
|
||||
* **2024-09-06 (v1.9.3)** : Update `go.mod` dependencies.
|
||||
* **2024-04-29 (v1.9.2)** : Update `go.mod` dependencies.
|
||||
|
||||
+2
@@ -70,3 +70,5 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
- [FreeCache](https://github.com/coocood/freecache)
|
||||
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
||||
- [Ristretto](https://github.com/dgraph-io/ristretto)
|
||||
- [Badger](https://github.com/dgraph-io/badger)
|
||||
|
||||
+22
-7
@@ -19,10 +19,13 @@ const (
|
||||
// Store the primes in an array as well.
|
||||
//
|
||||
// The consts are used when possible in Go code to avoid MOVs but we need a
|
||||
// contiguous array of the assembly code.
|
||||
// contiguous array for the assembly code.
|
||||
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
|
||||
|
||||
// Digest implements hash.Hash64.
|
||||
//
|
||||
// Note that a zero-valued Digest is not ready to receive writes.
|
||||
// Call Reset or create a Digest using New before calling other methods.
|
||||
type Digest struct {
|
||||
v1 uint64
|
||||
v2 uint64
|
||||
@@ -33,19 +36,31 @@ type Digest struct {
|
||||
n int // how much of mem is used
|
||||
}
|
||||
|
||||
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||
// New creates a new Digest with a zero seed.
|
||||
func New() *Digest {
|
||||
return NewWithSeed(0)
|
||||
}
|
||||
|
||||
// NewWithSeed creates a new Digest with the given seed.
|
||||
func NewWithSeed(seed uint64) *Digest {
|
||||
var d Digest
|
||||
d.Reset()
|
||||
d.ResetWithSeed(seed)
|
||||
return &d
|
||||
}
|
||||
|
||||
// Reset clears the Digest's state so that it can be reused.
|
||||
// It uses a seed value of zero.
|
||||
func (d *Digest) Reset() {
|
||||
d.v1 = primes[0] + prime2
|
||||
d.v2 = prime2
|
||||
d.v3 = 0
|
||||
d.v4 = -primes[0]
|
||||
d.ResetWithSeed(0)
|
||||
}
|
||||
|
||||
// ResetWithSeed clears the Digest's state so that it can be reused.
|
||||
// It uses the given seed to initialize the state.
|
||||
func (d *Digest) ResetWithSeed(seed uint64) {
|
||||
d.v1 = seed + prime1 + prime2
|
||||
d.v2 = seed + prime2
|
||||
d.v3 = seed
|
||||
d.v4 = seed - prime1
|
||||
d.total = 0
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
|
||||
//
|
||||
//go:noescape
|
||||
func Sum64(b []byte) uint64
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
|
||||
func Sum64(b []byte) uint64 {
|
||||
// A simpler version would be
|
||||
// d := New()
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
|
||||
func Sum64String(s string) uint64 {
|
||||
return Sum64([]byte(s))
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import (
|
||||
//
|
||||
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
func Sum64String(s string) uint64 {
|
||||
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||
|
||||
+8
-1
@@ -4,10 +4,17 @@ Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time g
|
||||
## Basis of the engine
|
||||
The engine is ported from the .NET framework's System.Text.RegularExpressions.Regex engine. That engine was open sourced in 2015 under the MIT license. There are some fundamental differences between .NET strings and Go strings that required a bit of borrowing from the Go framework regex engine as well. I cleaned up a couple of the dirtier bits during the port (regexcharclass.cs was terrible), but the parse tree, code emmitted, and therefore patterns matched should be identical.
|
||||
|
||||
## New Code Generation
|
||||
For extra performance use `regexp2` with [`regexp2cg`](https://github.com/dlclark/regexp2cg). It is a code generation utility for `regexp2` and you can likely improve your regexp runtime performance by 3-10x in hot code paths. As always you should benchmark your specifics to confirm the results. Give it a try!
|
||||
|
||||
## Installing
|
||||
This is a go-gettable library, so install is easy:
|
||||
|
||||
go get github.com/dlclark/regexp2/...
|
||||
go get github.com/dlclark/regexp2
|
||||
|
||||
To use the new Code Generation (while it's in beta) you'll need to use the `code_gen` branch:
|
||||
|
||||
go get github.com/dlclark/regexp2@code_gen
|
||||
|
||||
## Usage
|
||||
Usage is similar to the Go `regexp` package. Just like in `regexp`, you start by converting a regex into a state machine via the `Compile` or `MustCompile` methods. They ultimately do the same thing, but `MustCompile` will panic if the regex is invalid. You can then use the provided `Regexp` struct to find matches repeatedly. A `Regexp` struct is safe to use across goroutines.
|
||||
|
||||
+8
-6
@@ -6,8 +6,9 @@ import (
|
||||
)
|
||||
|
||||
// Match is a single regex result match that contains groups and repeated captures
|
||||
// -Groups
|
||||
// -Capture
|
||||
//
|
||||
// -Groups
|
||||
// -Capture
|
||||
type Match struct {
|
||||
Group //embeded group 0
|
||||
|
||||
@@ -43,10 +44,10 @@ type Group struct {
|
||||
type Capture struct {
|
||||
// the original string
|
||||
text []rune
|
||||
// the position in the original string where the first character of
|
||||
// captured substring was found.
|
||||
// Index is the position in the underlying rune slice where the first character of
|
||||
// captured substring was found. Even if you pass in a string this will be in Runes.
|
||||
Index int
|
||||
// the length of the captured substring.
|
||||
// Length is the number of runes in the captured substring.
|
||||
Length int
|
||||
}
|
||||
|
||||
@@ -187,7 +188,8 @@ func (m *Match) addMatch(c, start, l int) {
|
||||
}
|
||||
|
||||
// Nonpublic builder: Add a capture to balance the specified group. This is used by the
|
||||
// balanced match construct. (?<foo-foo2>...)
|
||||
//
|
||||
// balanced match construct. (?<foo-foo2>...)
|
||||
//
|
||||
// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c).
|
||||
// However, since we have backtracking, we need to keep track of everything.
|
||||
|
||||
+4
@@ -1604,6 +1604,10 @@ func (re *Regexp) getRunner() *runner {
|
||||
// run using re. (The cache empties when re gets garbage collected.)
|
||||
func (re *Regexp) putRunner(r *runner) {
|
||||
re.muRun.Lock()
|
||||
r.runtext = nil
|
||||
if r.runmatch != nil {
|
||||
r.runmatch.text = nil
|
||||
}
|
||||
re.runner = append(re.runner, r)
|
||||
re.muRun.Unlock()
|
||||
}
|
||||
|
||||
+3
-3
@@ -553,10 +553,10 @@ func (p *parser) scanRegex() (*regexNode, error) {
|
||||
}
|
||||
|
||||
case '.':
|
||||
if p.useOptionE() {
|
||||
p.addUnitSet(ECMAAnyClass())
|
||||
} else if p.useOptionS() {
|
||||
if p.useOptionS() {
|
||||
p.addUnitSet(AnyClass())
|
||||
} else if p.useOptionE() {
|
||||
p.addUnitSet(ECMAAnyClass())
|
||||
} else {
|
||||
p.addUnitNotone('\n')
|
||||
}
|
||||
|
||||
+3
-2
@@ -65,7 +65,7 @@ func main() {
|
||||
|
||||
**REST Preview:**
|
||||
|
||||
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs
|
||||
Here is a little preview of what routing looks like with chi. Also take a look at the generated routing docs
|
||||
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
|
||||
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
|
||||
|
||||
@@ -469,7 +469,8 @@ how setting context on a request in Go works.
|
||||
|
||||
* Carl Jackson for https://github.com/zenazn/goji
|
||||
* Parts of chi's thinking comes from goji, and chi's middleware package
|
||||
sources from goji.
|
||||
sources from [goji](https://github.com/zenazn/goji/tree/master/web/middleware).
|
||||
* Please see goji's [LICENSE](https://github.com/zenazn/goji/blob/master/LICENSE) (MIT)
|
||||
* Armon Dadgar for https://github.com/armon/go-radix
|
||||
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
|
||||
|
||||
|
||||
+5
-1
@@ -51,7 +51,7 @@
|
||||
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
|
||||
// "/user/{name}/info" matches "/user/jsmith/info"
|
||||
// "/page/*" matches "/page/intro/latest"
|
||||
// "/page/{other}/index" also matches "/page/intro/latest"
|
||||
// "/page/{other}/latest" also matches "/page/intro/latest"
|
||||
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
|
||||
package chi
|
||||
|
||||
@@ -127,6 +127,10 @@ type Routes interface {
|
||||
// the method/path - similar to routing a http request, but without
|
||||
// executing the handler thereafter.
|
||||
Match(rctx *Context, method, path string) bool
|
||||
|
||||
// Find searches the routing tree for the pattern that matches
|
||||
// the method/path.
|
||||
Find(rctx *Context, method, path string) string
|
||||
}
|
||||
|
||||
// Middlewares type is a slice of standard middleware handlers with methods
|
||||
|
||||
+4
-1
@@ -109,7 +109,7 @@ func (x *Context) URLParam(key string) string {
|
||||
// RoutePattern builds the routing pattern string for the particular
|
||||
// request, at the particular point during routing. This means, the value
|
||||
// will change throughout the execution of a request in a router. That is
|
||||
// why its advised to only use this value after calling the next handler.
|
||||
// why it's advised to only use this value after calling the next handler.
|
||||
//
|
||||
// For example,
|
||||
//
|
||||
@@ -121,6 +121,9 @@ func (x *Context) URLParam(key string) string {
|
||||
// })
|
||||
// }
|
||||
func (x *Context) RoutePattern() string {
|
||||
if x == nil {
|
||||
return ""
|
||||
}
|
||||
routePattern := strings.Join(x.RoutePatterns, "")
|
||||
routePattern = replaceWildcards(routePattern)
|
||||
if routePattern != "/" {
|
||||
|
||||
+9
-13
@@ -6,36 +6,32 @@ import (
|
||||
)
|
||||
|
||||
// SetHeader is a convenience handler to set a response header key/value
|
||||
func SetHeader(key, value string) func(next http.Handler) http.Handler {
|
||||
func SetHeader(key, value string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(key, value)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AllowContentType enforces a whitelist of request Content-Types otherwise responds
|
||||
// with a 415 Unsupported Media Type status.
|
||||
func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler {
|
||||
func AllowContentType(contentTypes ...string) func(http.Handler) http.Handler {
|
||||
allowedContentTypes := make(map[string]struct{}, len(contentTypes))
|
||||
for _, ctype := range contentTypes {
|
||||
allowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ContentLength == 0 {
|
||||
// skip check for empty content body
|
||||
// Skip check for empty content body
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type")))
|
||||
if i := strings.Index(s, ";"); i > -1 {
|
||||
s = s[0:i]
|
||||
}
|
||||
s := strings.ToLower(strings.TrimSpace(strings.Split(r.Header.Get("Content-Type"), ";")[0]))
|
||||
|
||||
if _, ok := allowedContentTypes[s]; ok {
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -43,7 +39,7 @@ func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handl
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -60,3 +60,11 @@ func RedirectSlashes(next http.Handler) http.Handler {
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// StripPrefix is a middleware that will strip the provided prefix from the
|
||||
// request path before handing the request over to the next handler.
|
||||
func StripPrefix(prefix string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.StripPrefix(prefix, next)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -22,11 +22,12 @@ type ThrottleOpts struct {
|
||||
Limit int
|
||||
BacklogLimit int
|
||||
BacklogTimeout time.Duration
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// Throttle is a middleware that limits number of currently processed requests
|
||||
// at a time across all users. Note: Throttle is not a rate-limiter per user,
|
||||
// instead it just puts a ceiling on the number of currently in-flight requests
|
||||
// instead it just puts a ceiling on the number of current in-flight requests
|
||||
// being processed from the point from where the Throttle middleware is mounted.
|
||||
func Throttle(limit int) func(http.Handler) http.Handler {
|
||||
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout})
|
||||
@@ -49,10 +50,16 @@ func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
panic("chi/middleware: Throttle expects backlogLimit to be positive")
|
||||
}
|
||||
|
||||
statusCode := opts.StatusCode
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
t := throttler{
|
||||
tokens: make(chan token, opts.Limit),
|
||||
backlogTokens: make(chan token, opts.Limit+opts.BacklogLimit),
|
||||
backlogTimeout: opts.BacklogTimeout,
|
||||
statusCode: statusCode,
|
||||
retryAfterFn: opts.RetryAfterFn,
|
||||
}
|
||||
|
||||
@@ -72,7 +79,7 @@ func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
|
||||
case <-ctx.Done():
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusTooManyRequests)
|
||||
http.Error(w, errContextCanceled, t.statusCode)
|
||||
return
|
||||
|
||||
case btok := <-t.backlogTokens:
|
||||
@@ -85,12 +92,12 @@ func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errTimedOut, http.StatusTooManyRequests)
|
||||
http.Error(w, errTimedOut, t.statusCode)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusTooManyRequests)
|
||||
http.Error(w, errContextCanceled, t.statusCode)
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
@@ -103,7 +110,7 @@ func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
|
||||
default:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errCapacityExceeded, http.StatusTooManyRequests)
|
||||
http.Error(w, errCapacityExceeded, t.statusCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -119,8 +126,9 @@ type token struct{}
|
||||
type throttler struct {
|
||||
tokens chan token
|
||||
backlogTokens chan token
|
||||
retryAfterFn func(ctxDone bool) time.Duration
|
||||
backlogTimeout time.Duration
|
||||
statusCode int
|
||||
retryAfterFn func(ctxDone bool) time.Duration
|
||||
}
|
||||
|
||||
// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized.
|
||||
|
||||
+5
-1
@@ -81,7 +81,11 @@ type basicWriter struct {
|
||||
}
|
||||
|
||||
func (b *basicWriter) WriteHeader(code int) {
|
||||
if !b.wroteHeader {
|
||||
if code >= 100 && code <= 199 && code != http.StatusSwitchingProtocols {
|
||||
if !b.discard {
|
||||
b.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
} else if !b.wroteHeader {
|
||||
b.code = code
|
||||
b.wroteHeader = true
|
||||
if !b.discard {
|
||||
|
||||
+26
-5
@@ -363,19 +363,40 @@ func (mx *Mux) Middlewares() Middlewares {
|
||||
// Note: the *Context state is updated during execution, so manage
|
||||
// the state carefully or make a NewRouteContext().
|
||||
func (mx *Mux) Match(rctx *Context, method, path string) bool {
|
||||
return mx.Find(rctx, method, path) != ""
|
||||
}
|
||||
|
||||
// Find searches the routing tree for the pattern that matches
|
||||
// the method/path.
|
||||
//
|
||||
// Note: the *Context state is updated during execution, so manage
|
||||
// the state carefully or make a NewRouteContext().
|
||||
func (mx *Mux) Find(rctx *Context, method, path string) string {
|
||||
m, ok := methodMap[method]
|
||||
if !ok {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
node, _, h := mx.tree.FindRoute(rctx, m, path)
|
||||
node, _, _ := mx.tree.FindRoute(rctx, m, path)
|
||||
pattern := rctx.routePattern
|
||||
|
||||
if node != nil {
|
||||
if node.subroutes == nil {
|
||||
e := node.endpoints[m]
|
||||
return e.pattern
|
||||
}
|
||||
|
||||
if node != nil && node.subroutes != nil {
|
||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||
return node.subroutes.Match(rctx, method, rctx.RoutePath)
|
||||
subPattern := node.subroutes.Find(rctx, method, rctx.RoutePath)
|
||||
if subPattern == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
pattern = strings.TrimSuffix(pattern, "/*")
|
||||
pattern += subPattern
|
||||
}
|
||||
|
||||
return h != nil
|
||||
return pattern
|
||||
}
|
||||
|
||||
// NotFoundHandler returns the default Mux 404 responder whenever a route
|
||||
|
||||
+22
-25
@@ -1,14 +1,11 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
tests: false
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 100
|
||||
enable:
|
||||
- shadow
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
@@ -23,25 +20,27 @@ linters-settings:
|
||||
- experimental
|
||||
disabled-checks:
|
||||
- wrapperFunc
|
||||
- hugeParam
|
||||
- rangeValCopy
|
||||
- singleCaseSwitch
|
||||
- ifElseChain
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- revive
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- staticcheck
|
||||
- unused
|
||||
- gosec
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
- copyloopvar
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gosimple
|
||||
@@ -49,21 +48,19 @@ linters:
|
||||
fast: false
|
||||
disable-all: true
|
||||
|
||||
run:
|
||||
output:
|
||||
format: tab
|
||||
skip-dirs:
|
||||
- vendor
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- vendor
|
||||
exclude-rules:
|
||||
- text: 'Deferring unsafe method "Close" on type "io.ReadCloser"'
|
||||
- text: "at least one file in a package should have a package comment"
|
||||
linters:
|
||||
- gosec
|
||||
- text: "should have a package comment, unless it's in another file for this package"
|
||||
- stylecheck
|
||||
- text: "should have a package comment"
|
||||
linters:
|
||||
- golint
|
||||
- revive
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gosec
|
||||
- dupl
|
||||
exclude-use-default: false
|
||||
|
||||
|
||||
+55
-2
@@ -130,8 +130,7 @@ and added to the request's context.
|
||||
|
||||
### Deprecation middleware
|
||||
|
||||
Adds the HTTP Deprecation response header, see [draft-dalal-deprecation-header-00](https://tools.ietf.org/id/draft-dalal-deprecation-header-00.html
|
||||
)
|
||||
Adds the HTTP Deprecation response header, see [draft-ietf-httpapi-deprecation-header-02](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-deprecation-header-02)
|
||||
|
||||
### BasicAuth middleware
|
||||
|
||||
@@ -183,6 +182,58 @@ example with chi router:
|
||||
router.Use(rest.Reject(http.StatusBadRequest, "X-Request-Id header is required", rejectFn))
|
||||
```
|
||||
|
||||
### BasicAuth middleware family
|
||||
|
||||
The package provides several BasicAuth middleware implementations for different authentication needs:
|
||||
|
||||
#### BasicAuth
|
||||
The base middleware that requires basic auth and matches user & passwd with a client-provided checker function.
|
||||
```go
|
||||
checkFn := func(user, passwd string) bool {
|
||||
return user == "admin" && passwd == "secret"
|
||||
}
|
||||
router.Use(rest.BasicAuth(checkFn))
|
||||
```
|
||||
|
||||
#### BasicAuthWithUserPasswd
|
||||
A simpler version comparing user & password with provided values directly.
|
||||
```go
|
||||
router.Use(rest.BasicAuthWithUserPasswd("admin", "secret"))
|
||||
```
|
||||
|
||||
#### BasicAuthWithBcryptHash
|
||||
Matches username and bcrypt-hashed password. Useful when storing hashed passwords.
|
||||
```go
|
||||
hash, err := rest.GenerateBcryptHash("secret")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
router.Use(rest.BasicAuthWithBcryptHash("admin", hash))
|
||||
```
|
||||
|
||||
#### BasicAuthWithArgon2Hash
|
||||
Similar to bcrypt version but uses Argon2id hash with a separate salt. Both hash and salt are base64 encoded.
|
||||
```go
|
||||
hash, salt, err := rest.GenerateArgon2Hash("secret")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
router.Use(rest.BasicAuthWithArgon2Hash("admin", hash, salt))
|
||||
```
|
||||
|
||||
#### BasicAuthWithPrompt
|
||||
Similar to BasicAuthWithUserPasswd but adds browser's authentication prompt by setting the WWW-Authenticate header.
|
||||
```go
|
||||
router.Use(rest.BasicAuthWithPrompt("admin", "secret"))
|
||||
```
|
||||
|
||||
All BasicAuth middlewares:
|
||||
- Return `StatusUnauthorized` (401) if no auth header provided
|
||||
- Return `StatusForbidden` (403) if credentials check failed
|
||||
- Add IsAuthorized flag to the request context, retrievable with `rest.IsAuthorized(r.Context())`
|
||||
- Use constant-time comparison to prevent timing attacks
|
||||
- Support secure password hashing with bcrypt and Argon2id
|
||||
|
||||
### Benchmarks middleware
|
||||
|
||||
Benchmarks middleware allows measuring the time of request handling, number of requests per second and report aggregated metrics.
|
||||
@@ -225,6 +276,8 @@ example with chi router:
|
||||
- `rest.FileServer` - creates a file server for static assets with directory listing disabled
|
||||
- `realip.Get` - returns client's IP address
|
||||
- `rest.ParseFromTo` - parses "from" and "to" request's query params with various formats
|
||||
- `rest.DecodeJSON` - decodes request body to the provided struct
|
||||
- `rest.EncodeJSON` - encodes response body from the provided struct, sets `Content-Type` to `application/json` and sends the status code
|
||||
|
||||
## Profiler
|
||||
|
||||
|
||||
+90
@@ -2,8 +2,13 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const baContextKey = "authorizedWithBasicAuth"
|
||||
@@ -39,6 +44,42 @@ func BasicAuthWithUserPasswd(user, passwd string) func(http.Handler) http.Handle
|
||||
return BasicAuth(checkFn)
|
||||
}
|
||||
|
||||
// BasicAuthWithBcryptHash middleware requires basic auth and matches user & bcrypt hashed password
|
||||
func BasicAuthWithBcryptHash(user, hashedPassword string) func(http.Handler) http.Handler {
|
||||
checkFn := func(reqUser, reqPasswd string) bool {
|
||||
if reqUser != user {
|
||||
return false
|
||||
}
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(reqPasswd))
|
||||
return err == nil
|
||||
}
|
||||
return BasicAuth(checkFn)
|
||||
}
|
||||
|
||||
// BasicAuthWithArgon2Hash middleware requires basic auth and matches user & argon2 hashed password
|
||||
// both hashedPassword and salt must be base64 encoded strings
|
||||
// Uses Argon2id with parameters: t=1, m=64*1024 KB, p=4 threads
|
||||
func BasicAuthWithArgon2Hash(user, hashedPassword, salt string) func(http.Handler) http.Handler {
|
||||
checkFn := func(reqUser, reqPasswd string) bool {
|
||||
if reqUser != user {
|
||||
return false
|
||||
}
|
||||
|
||||
saltBytes, err := base64.StdEncoding.DecodeString(salt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
storedHashBytes, err := base64.StdEncoding.DecodeString(hashedPassword)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hash := argon2.IDKey([]byte(reqPasswd), saltBytes, 1, 64*1024, 4, 32)
|
||||
return subtle.ConstantTimeCompare(hash, storedHashBytes) == 1
|
||||
}
|
||||
return BasicAuth(checkFn)
|
||||
}
|
||||
|
||||
// IsAuthorized returns true is user authorized.
|
||||
// it can be used in handlers to check if BasicAuth middleware was applied
|
||||
func IsAuthorized(ctx context.Context) bool {
|
||||
@@ -71,3 +112,52 @@ func BasicAuthWithPrompt(user, passwd string) func(http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// BasicAuthWithBcryptHashAndPrompt middleware requires basic auth and matches user & bcrypt hashed password
|
||||
// If the user is not authorized, it will prompt for basic auth
|
||||
func BasicAuthWithBcryptHashAndPrompt(user, hashedPassword string) func(http.Handler) http.Handler {
|
||||
checkFn := func(reqUser, reqPasswd string) bool {
|
||||
if reqUser != user {
|
||||
return false
|
||||
}
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(reqPasswd))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
// extract basic auth from request
|
||||
u, p, ok := r.BasicAuth()
|
||||
if ok && checkFn(u, p) {
|
||||
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey(baContextKey), true)))
|
||||
return
|
||||
}
|
||||
// not authorized, prompt for basic auth
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateBcryptHash generates a bcrypt hash from a password
|
||||
func GenerateBcryptHash(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// GenerateArgon2Hash generates an argon2 hash and salt from a password
|
||||
func GenerateArgon2Hash(password string) (hash, salt string, err error) {
|
||||
saltBytes := make([]byte, 16)
|
||||
if _, err := rand.Read(saltBytes); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// using recommended parameters: time=1, memory=64*1024, threads=4, keyLen=32
|
||||
hashBytes := argon2.IDKey([]byte(password), saltBytes, 1, 64*1024, 4, 32)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hashBytes), base64.StdEncoding.EncodeToString(saltBytes), nil
|
||||
}
|
||||
|
||||
+17
-5
@@ -55,10 +55,10 @@ func NewBenchmarks() *Benchmarks {
|
||||
// Default is 15 minutes. The increase of this range will change memory utilization as each second of the range
|
||||
// kept as benchData aggregate. The default means 15*60 = 900 seconds of data aggregate.
|
||||
// Larger range allows for longer time periods to be benchmarked.
|
||||
func (b *Benchmarks) WithTimeRange(max time.Duration) *Benchmarks {
|
||||
func (b *Benchmarks) WithTimeRange(maximum time.Duration) *Benchmarks {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.maxTimeRange = max
|
||||
b.maxTimeRange = maximum
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -124,13 +124,18 @@ func (b *Benchmarks) Stats(interval time.Duration) BenchmarkStats {
|
||||
respTime time.Duration
|
||||
)
|
||||
|
||||
now := b.nowFn().Truncate(time.Second)
|
||||
cutoff := now.Add(-interval)
|
||||
stInterval, fnInterval := time.Time{}, time.Time{}
|
||||
var minRespTime, maxRespTime time.Duration
|
||||
for e := b.data.Back(); e != nil; e = e.Prev() { // reverse order
|
||||
count := 0
|
||||
|
||||
for e := b.data.Back(); e != nil && count < int(interval.Seconds()); e = e.Prev() { // reverse order
|
||||
bd := e.Value.(benchData)
|
||||
if bd.ts.Before(b.nowFn().Add(-interval)) {
|
||||
if bd.ts.Before(cutoff) {
|
||||
break
|
||||
}
|
||||
|
||||
if minRespTime == 0 || bd.minRespTime < minRespTime {
|
||||
minRespTime = bd.minRespTime
|
||||
}
|
||||
@@ -143,15 +148,22 @@ func (b *Benchmarks) Stats(interval time.Duration) BenchmarkStats {
|
||||
fnInterval = bd.ts.Add(time.Second)
|
||||
}
|
||||
stInterval = bd.ts
|
||||
count++
|
||||
}
|
||||
|
||||
if requests == 0 {
|
||||
return BenchmarkStats{}
|
||||
}
|
||||
|
||||
// ensure we calculate rate based on actual interval
|
||||
actualInterval := fnInterval.Sub(stInterval)
|
||||
if actualInterval < time.Second {
|
||||
actualInterval = time.Second
|
||||
}
|
||||
|
||||
return BenchmarkStats{
|
||||
Requests: requests,
|
||||
RequestsSec: float64(requests) / (fnInterval.Sub(stInterval).Seconds()),
|
||||
RequestsSec: float64(requests) / actualInterval.Seconds(),
|
||||
AverageRespTime: respTime.Microseconds() / int64(requests),
|
||||
MinRespTime: minRespTime.Microseconds(),
|
||||
MaxRespTime: maxRespTime.Microseconds(),
|
||||
|
||||
+3
-3
@@ -13,10 +13,10 @@ func WithBody(l *Middleware) {
|
||||
}
|
||||
|
||||
// MaxBodySize sets size of the logged part of the request body.
|
||||
func MaxBodySize(max int) Option {
|
||||
func MaxBodySize(maximum int) Option {
|
||||
return func(l *Middleware) {
|
||||
if max >= 0 {
|
||||
l.maxBodySize = max
|
||||
if maximum >= 0 {
|
||||
l.maxBodySize = maximum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -13,16 +13,13 @@ import (
|
||||
|
||||
// Wrap converts a list of middlewares to nested calls (in reverse order)
|
||||
func Wrap(handler http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
|
||||
if len(mws) == 0 {
|
||||
return handler
|
||||
}
|
||||
res := handler
|
||||
for i := len(mws) - 1; i >= 0; i-- {
|
||||
res = mws[i](res)
|
||||
handler = mws[i](handler)
|
||||
}
|
||||
return res
|
||||
return handler
|
||||
}
|
||||
|
||||
|
||||
// AppInfo adds custom app-info to the response header
|
||||
func AppInfo(app, author, version string) func(http.Handler) http.Handler {
|
||||
f := func(h http.Handler) http.Handler {
|
||||
|
||||
+10
-3
@@ -14,15 +14,22 @@ type ipRange struct {
|
||||
end net.IP
|
||||
}
|
||||
|
||||
// privateRanges contains the list of private and special-use IP ranges.
|
||||
// reference: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
||||
var privateRanges = []ipRange{
|
||||
// IPv4 Private Ranges
|
||||
{start: net.ParseIP("10.0.0.0"), end: net.ParseIP("10.255.255.255")},
|
||||
{start: net.ParseIP("100.64.0.0"), end: net.ParseIP("100.127.255.255")},
|
||||
{start: net.ParseIP("172.16.0.0"), end: net.ParseIP("172.31.255.255")},
|
||||
{start: net.ParseIP("192.0.0.0"), end: net.ParseIP("192.0.0.255")},
|
||||
{start: net.ParseIP("192.168.0.0"), end: net.ParseIP("192.168.255.255")},
|
||||
// IPv4 Link-Local
|
||||
{start: net.ParseIP("169.254.0.0"), end: net.ParseIP("169.254.255.255")},
|
||||
// IPv4 Shared Address Space (RFC 6598)
|
||||
{start: net.ParseIP("100.64.0.0"), end: net.ParseIP("100.127.255.255")},
|
||||
// IPv4 Benchmarking (RFC 2544)
|
||||
{start: net.ParseIP("198.18.0.0"), end: net.ParseIP("198.19.255.255")},
|
||||
{start: net.ParseIP("::1"), end: net.ParseIP("::1")},
|
||||
// IPv6 Unique Local Addresses (ULA)
|
||||
{start: net.ParseIP("fc00::"), end: net.ParseIP("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
|
||||
// IPv6 Link-local Addresses
|
||||
{start: net.ParseIP("fe80::"), end: net.ParseIP("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# This is an example goreleaser.yaml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- ./gen.sh
|
||||
@@ -99,7 +99,7 @@ archives:
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
version_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
|
||||
+25
-4
@@ -16,6 +16,27 @@ This package provides various compression algorithms.
|
||||
|
||||
# changelog
|
||||
|
||||
* Sep 23rd, 2024 - [1.17.10](https://github.com/klauspost/compress/releases/tag/v1.17.10)
|
||||
* gzhttp: Add TransportAlwaysDecompress option. https://github.com/klauspost/compress/pull/978
|
||||
* gzhttp: Add supported decompress request body by @mirecl in https://github.com/klauspost/compress/pull/1002
|
||||
* s2: Add EncodeBuffer buffer recycling callback https://github.com/klauspost/compress/pull/982
|
||||
* zstd: Improve memory usage on small streaming encodes https://github.com/klauspost/compress/pull/1007
|
||||
* flate: read data written with partial flush by @vajexal in https://github.com/klauspost/compress/pull/996
|
||||
|
||||
* Jun 12th, 2024 - [1.17.9](https://github.com/klauspost/compress/releases/tag/v1.17.9)
|
||||
* s2: Reduce ReadFrom temporary allocations https://github.com/klauspost/compress/pull/949
|
||||
* flate, zstd: Shave some bytes off amd64 matchLen by @greatroar in https://github.com/klauspost/compress/pull/963
|
||||
* Upgrade zip/zlib to 1.22.4 upstream https://github.com/klauspost/compress/pull/970 https://github.com/klauspost/compress/pull/971
|
||||
* zstd: BuildDict fails with RLE table https://github.com/klauspost/compress/pull/951
|
||||
|
||||
* Apr 9th, 2024 - [1.17.8](https://github.com/klauspost/compress/releases/tag/v1.17.8)
|
||||
* zstd: Reject blocks where reserved values are not 0 https://github.com/klauspost/compress/pull/885
|
||||
* zstd: Add RLE detection+encoding https://github.com/klauspost/compress/pull/938
|
||||
|
||||
* Feb 21st, 2024 - [1.17.7](https://github.com/klauspost/compress/releases/tag/v1.17.7)
|
||||
* s2: Add AsyncFlush method: Complete the block without flushing by @Jille in https://github.com/klauspost/compress/pull/927
|
||||
* s2: Fix literal+repeat exceeds dst crash https://github.com/klauspost/compress/pull/930
|
||||
|
||||
* Feb 5th, 2024 - [1.17.6](https://github.com/klauspost/compress/releases/tag/v1.17.6)
|
||||
* zstd: Fix incorrect repeat coding in best mode https://github.com/klauspost/compress/pull/923
|
||||
* s2: Fix DecodeConcurrent deadlock on errors https://github.com/klauspost/compress/pull/925
|
||||
@@ -81,7 +102,7 @@ https://github.com/klauspost/compress/pull/919 https://github.com/klauspost/comp
|
||||
* zstd: Various minor improvements by @greatroar in https://github.com/klauspost/compress/pull/788 https://github.com/klauspost/compress/pull/794 https://github.com/klauspost/compress/pull/795
|
||||
* s2: Fix huge block overflow https://github.com/klauspost/compress/pull/779
|
||||
* s2: Allow CustomEncoder fallback https://github.com/klauspost/compress/pull/780
|
||||
* gzhttp: Suppport ResponseWriter Unwrap() in gzhttp handler by @jgimenez in https://github.com/klauspost/compress/pull/799
|
||||
* gzhttp: Support ResponseWriter Unwrap() in gzhttp handler by @jgimenez in https://github.com/klauspost/compress/pull/799
|
||||
|
||||
* Mar 13, 2023 - [v1.16.1](https://github.com/klauspost/compress/releases/tag/v1.16.1)
|
||||
* zstd: Speed up + improve best encoder by @greatroar in https://github.com/klauspost/compress/pull/776
|
||||
@@ -136,7 +157,7 @@ https://github.com/klauspost/compress/pull/919 https://github.com/klauspost/comp
|
||||
* zstd: Add [WithDecodeAllCapLimit](https://pkg.go.dev/github.com/klauspost/compress@v1.15.10/zstd#WithDecodeAllCapLimit) https://github.com/klauspost/compress/pull/649
|
||||
* Add Go 1.19 - deprecate Go 1.16 https://github.com/klauspost/compress/pull/651
|
||||
* flate: Improve level 5+6 compression https://github.com/klauspost/compress/pull/656
|
||||
* zstd: Improve "better" compresssion https://github.com/klauspost/compress/pull/657
|
||||
* zstd: Improve "better" compression https://github.com/klauspost/compress/pull/657
|
||||
* s2: Improve "best" compression https://github.com/klauspost/compress/pull/658
|
||||
* s2: Improve "better" compression. https://github.com/klauspost/compress/pull/635
|
||||
* s2: Slightly faster non-assembly decompression https://github.com/klauspost/compress/pull/646
|
||||
@@ -339,7 +360,7 @@ While the release has been extensively tested, it is recommended to testing when
|
||||
* s2: Fix binaries.
|
||||
|
||||
* Feb 25, 2021 (v1.11.8)
|
||||
* s2: Fixed occational out-of-bounds write on amd64. Upgrade recommended.
|
||||
* s2: Fixed occasional out-of-bounds write on amd64. Upgrade recommended.
|
||||
* s2: Add AMD64 assembly for better mode. 25-50% faster. [#315](https://github.com/klauspost/compress/pull/315)
|
||||
* s2: Less upfront decoder allocation. [#322](https://github.com/klauspost/compress/pull/322)
|
||||
* zstd: Faster "compression" of incompressible data. [#314](https://github.com/klauspost/compress/pull/314)
|
||||
@@ -518,7 +539,7 @@ While the release has been extensively tested, it is recommended to testing when
|
||||
* Feb 19, 2016: Faster bit writer, level -2 is 15% faster, level 1 is 4% faster.
|
||||
* Feb 19, 2016: Handle small payloads faster in level 1-3.
|
||||
* Feb 19, 2016: Added faster level 2 + 3 compression modes.
|
||||
* Feb 19, 2016: [Rebalanced compression levels](https://blog.klauspost.com/rebalancing-deflate-compression-levels/), so there is a more even progresssion in terms of compression. New default level is 5.
|
||||
* Feb 19, 2016: [Rebalanced compression levels](https://blog.klauspost.com/rebalancing-deflate-compression-levels/), so there is a more even progression in terms of compression. New default level is 5.
|
||||
* Feb 14, 2016: Snappy: Merge upstream changes.
|
||||
* Feb 14, 2016: Snappy: Fix aggressive skipping.
|
||||
* Feb 14, 2016: Snappy: Update benchmark.
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ const (
|
||||
// It is possible, but by no way guaranteed that corrupt data will
|
||||
// return an error.
|
||||
// It is up to the caller to verify integrity of the returned data.
|
||||
// Use a predefined Scrach to set maximum acceptable output size.
|
||||
// Use a predefined Scratch to set maximum acceptable output size.
|
||||
func Decompress(b []byte, s *Scratch) ([]byte, error) {
|
||||
s, err := s.prepare(b)
|
||||
if err != nil {
|
||||
|
||||
+2
-2
@@ -1136,7 +1136,7 @@ func (s *Scratch) matches(ct cTable, w io.Writer) {
|
||||
errs++
|
||||
}
|
||||
if errs > 0 {
|
||||
fmt.Fprintf(w, "%d errros in base, stopping\n", errs)
|
||||
fmt.Fprintf(w, "%d errors in base, stopping\n", errs)
|
||||
continue
|
||||
}
|
||||
// Ensure that all combinations are covered.
|
||||
@@ -1152,7 +1152,7 @@ func (s *Scratch) matches(ct cTable, w io.Writer) {
|
||||
errs++
|
||||
}
|
||||
if errs > 20 {
|
||||
fmt.Fprintf(w, "%d errros, stopping\n", errs)
|
||||
fmt.Fprintf(w, "%d errors, stopping\n", errs)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -598,7 +598,9 @@ func (b *blockDec) prepareSequences(in []byte, hist *history) (err error) {
|
||||
printf("RLE set to 0x%x, code: %v", symb, v)
|
||||
}
|
||||
case compModeFSE:
|
||||
println("Reading table for", tableIndex(i))
|
||||
if debugDecoder {
|
||||
println("Reading table for", tableIndex(i))
|
||||
}
|
||||
if seq.fse == nil || seq.fse.preDefined {
|
||||
seq.fse = fseDecoderPool.Get().(*fseDecoder)
|
||||
}
|
||||
|
||||
+16
-16
@@ -179,9 +179,9 @@ encodeLoop:
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
length := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -210,12 +210,12 @@ encodeLoop:
|
||||
|
||||
// Index match start+1 (long) -> s - 1
|
||||
index0 := s + repOff
|
||||
s += lenght + repOff
|
||||
s += length + repOff
|
||||
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
@@ -241,9 +241,9 @@ encodeLoop:
|
||||
if false && repIndex >= 0 && load6432(src, repIndex) == load6432(src, s+repOff) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 8 + e.matchlen(s+8+repOff2, repIndex+8, src)
|
||||
length := 8 + e.matchlen(s+8+repOff2, repIndex+8, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -270,11 +270,11 @@ encodeLoop:
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
s += lenght + repOff2
|
||||
s += length + repOff2
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
@@ -708,9 +708,9 @@ encodeLoop:
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
length := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -738,12 +738,12 @@ encodeLoop:
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
// Index match start+1 (long) -> s - 1
|
||||
s += lenght + repOff
|
||||
s += length + repOff
|
||||
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
@@ -772,9 +772,9 @@ encodeLoop:
|
||||
if false && repIndex >= 0 && load6432(src, repIndex) == load6432(src, s+repOff) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 8 + e.matchlen(s+8+repOff2, repIndex+8, src)
|
||||
length := 8 + e.matchlen(s+8+repOff2, repIndex+8, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -801,11 +801,11 @@ encodeLoop:
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
s += lenght + repOff2
|
||||
s += length + repOff2
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
|
||||
+8
-8
@@ -138,9 +138,9 @@ encodeLoop:
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
length := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -166,11 +166,11 @@ encodeLoop:
|
||||
println("repeat sequence", seq, "next s:", s)
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
s += lenght + repOff
|
||||
s += length + repOff
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
@@ -798,9 +798,9 @@ encodeLoop:
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
// Consider history as well.
|
||||
var seq seq
|
||||
lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
length := 4 + e.matchlen(s+4+repOff, repIndex+4, src)
|
||||
|
||||
seq.matchLen = uint32(lenght - zstdMinMatch)
|
||||
seq.matchLen = uint32(length - zstdMinMatch)
|
||||
|
||||
// We might be able to match backwards.
|
||||
// Extend as long as we can.
|
||||
@@ -826,11 +826,11 @@ encodeLoop:
|
||||
println("repeat sequence", seq, "next s:", s)
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
s += lenght + repOff
|
||||
s += length + repOff
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, lenght)
|
||||
println("repeat ended", s, length)
|
||||
|
||||
}
|
||||
break encodeLoop
|
||||
|
||||
+34
-11
@@ -6,6 +6,7 @@ package zstd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
@@ -149,6 +150,9 @@ func (e *Encoder) ResetContentSize(w io.Writer, size int64) {
|
||||
// and write CRC if requested.
|
||||
func (e *Encoder) Write(p []byte) (n int, err error) {
|
||||
s := &e.state
|
||||
if s.eofWritten {
|
||||
return 0, ErrEncoderClosed
|
||||
}
|
||||
for len(p) > 0 {
|
||||
if len(p)+len(s.filling) < e.o.blockSize {
|
||||
if e.o.crc {
|
||||
@@ -202,7 +206,7 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
return nil
|
||||
}
|
||||
if final && len(s.filling) > 0 {
|
||||
s.current = e.EncodeAll(s.filling, s.current[:0])
|
||||
s.current = e.encodeAll(s.encoder, s.filling, s.current[:0])
|
||||
var n2 int
|
||||
n2, s.err = s.w.Write(s.current)
|
||||
if s.err != nil {
|
||||
@@ -288,6 +292,9 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
s.filling, s.current, s.previous = s.previous[:0], s.filling, s.current
|
||||
s.nInput += int64(len(s.current))
|
||||
s.wg.Add(1)
|
||||
if final {
|
||||
s.eofWritten = true
|
||||
}
|
||||
go func(src []byte) {
|
||||
if debugEncoder {
|
||||
println("Adding block,", len(src), "bytes, final:", final)
|
||||
@@ -303,9 +310,6 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
blk := enc.Block()
|
||||
enc.Encode(blk, src)
|
||||
blk.last = final
|
||||
if final {
|
||||
s.eofWritten = true
|
||||
}
|
||||
// Wait for pending writes.
|
||||
s.wWg.Wait()
|
||||
if s.writeErr != nil {
|
||||
@@ -401,12 +405,20 @@ func (e *Encoder) Flush() error {
|
||||
if len(s.filling) > 0 {
|
||||
err := e.nextBlock(false)
|
||||
if err != nil {
|
||||
// Ignore Flush after Close.
|
||||
if errors.Is(s.err, ErrEncoderClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.wg.Wait()
|
||||
s.wWg.Wait()
|
||||
if s.err != nil {
|
||||
// Ignore Flush after Close.
|
||||
if errors.Is(s.err, ErrEncoderClosed) {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
return s.writeErr
|
||||
@@ -422,6 +434,9 @@ func (e *Encoder) Close() error {
|
||||
}
|
||||
err := e.nextBlock(true)
|
||||
if err != nil {
|
||||
if errors.Is(s.err, ErrEncoderClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if s.frameContentSize > 0 {
|
||||
@@ -459,6 +474,11 @@ func (e *Encoder) Close() error {
|
||||
}
|
||||
_, s.err = s.w.Write(frame)
|
||||
}
|
||||
if s.err == nil {
|
||||
s.err = ErrEncoderClosed
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.err
|
||||
}
|
||||
|
||||
@@ -469,6 +489,15 @@ func (e *Encoder) Close() error {
|
||||
// Data compressed with EncodeAll can be decoded with the Decoder,
|
||||
// using either a stream or DecodeAll.
|
||||
func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
e.init.Do(e.initialize)
|
||||
enc := <-e.encoders
|
||||
defer func() {
|
||||
e.encoders <- enc
|
||||
}()
|
||||
return e.encodeAll(enc, src, dst)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeAll(enc encoder, src, dst []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
if e.o.fullZero {
|
||||
// Add frame header.
|
||||
@@ -491,13 +520,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
}
|
||||
return dst
|
||||
}
|
||||
e.init.Do(e.initialize)
|
||||
enc := <-e.encoders
|
||||
defer func() {
|
||||
// Release encoder reference to last block.
|
||||
// If a non-single block is needed the encoder will reset again.
|
||||
e.encoders <- enc
|
||||
}()
|
||||
|
||||
// Use single segments when above minimum window and below window size.
|
||||
single := len(src) <= e.o.windowSize && len(src) > MinWindowSize
|
||||
if e.o.single != nil {
|
||||
|
||||
+3
-1
@@ -146,7 +146,9 @@ func (d *frameDec) reset(br byteBuffer) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
printf("raw: %x, mantissa: %d, exponent: %d\n", wd, wd&7, wd>>3)
|
||||
if debugDecoder {
|
||||
printf("raw: %x, mantissa: %d, exponent: %d\n", wd, wd&7, wd>>3)
|
||||
}
|
||||
windowLog := 10 + (wd >> 3)
|
||||
windowBase := uint64(1) << windowLog
|
||||
windowAdd := (windowBase / 8) * uint64(wd&0x7)
|
||||
|
||||
+2
-2
@@ -146,7 +146,7 @@ func (s *sequenceDecs) decodeSyncSimple(hist []byte) (bool, error) {
|
||||
return true, fmt.Errorf("output bigger than max block size (%d)", maxBlockSize)
|
||||
|
||||
default:
|
||||
return true, fmt.Errorf("sequenceDecs_decode returned erronous code %d", errCode)
|
||||
return true, fmt.Errorf("sequenceDecs_decode returned erroneous code %d", errCode)
|
||||
}
|
||||
|
||||
s.seqSize += ctx.litRemain
|
||||
@@ -292,7 +292,7 @@ func (s *sequenceDecs) decode(seqs []seqVals) error {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return fmt.Errorf("sequenceDecs_decode_amd64 returned erronous code %d", errCode)
|
||||
return fmt.Errorf("sequenceDecs_decode_amd64 returned erroneous code %d", errCode)
|
||||
}
|
||||
|
||||
if ctx.litRemain < 0 {
|
||||
|
||||
+4
-4
@@ -1814,7 +1814,7 @@ TEXT ·sequenceDecs_decodeSync_amd64(SB), $64-32
|
||||
MOVQ 40(SP), AX
|
||||
ADDQ AX, 48(SP)
|
||||
|
||||
// Calculate poiter to s.out[cap(s.out)] (a past-end pointer)
|
||||
// Calculate pointer to s.out[cap(s.out)] (a past-end pointer)
|
||||
ADDQ R10, 32(SP)
|
||||
|
||||
// outBase += outPosition
|
||||
@@ -2376,7 +2376,7 @@ TEXT ·sequenceDecs_decodeSync_bmi2(SB), $64-32
|
||||
MOVQ 40(SP), CX
|
||||
ADDQ CX, 48(SP)
|
||||
|
||||
// Calculate poiter to s.out[cap(s.out)] (a past-end pointer)
|
||||
// Calculate pointer to s.out[cap(s.out)] (a past-end pointer)
|
||||
ADDQ R9, 32(SP)
|
||||
|
||||
// outBase += outPosition
|
||||
@@ -2896,7 +2896,7 @@ TEXT ·sequenceDecs_decodeSync_safe_amd64(SB), $64-32
|
||||
MOVQ 40(SP), AX
|
||||
ADDQ AX, 48(SP)
|
||||
|
||||
// Calculate poiter to s.out[cap(s.out)] (a past-end pointer)
|
||||
// Calculate pointer to s.out[cap(s.out)] (a past-end pointer)
|
||||
ADDQ R10, 32(SP)
|
||||
|
||||
// outBase += outPosition
|
||||
@@ -3560,7 +3560,7 @@ TEXT ·sequenceDecs_decodeSync_safe_bmi2(SB), $64-32
|
||||
MOVQ 40(SP), CX
|
||||
ADDQ CX, 48(SP)
|
||||
|
||||
// Calculate poiter to s.out[cap(s.out)] (a past-end pointer)
|
||||
// Calculate pointer to s.out[cap(s.out)] (a past-end pointer)
|
||||
ADDQ R9, 32(SP)
|
||||
|
||||
// outBase += outPosition
|
||||
|
||||
+4
@@ -88,6 +88,10 @@ var (
|
||||
// Close has been called.
|
||||
ErrDecoderClosed = errors.New("decoder used after Close")
|
||||
|
||||
// ErrEncoderClosed will be returned if the Encoder was used after
|
||||
// Close has been called.
|
||||
ErrEncoderClosed = errors.New("encoder used after Close")
|
||||
|
||||
// ErrDecoderNilInput is returned when a nil Reader was provided
|
||||
// and an operation other than Reset/DecodeAll/Close was attempted.
|
||||
ErrDecoderNilInput = errors.New("nil input provided as reader")
|
||||
|
||||
+9
@@ -1,3 +1,12 @@
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
* `go-redis` won't skip span creation if the parent spans is not recording. ([#2980](https://github.com/redis/go-redis/issues/2980))
|
||||
Users can use the OpenTelemetry sampler to control the sampling behavior.
|
||||
For instance, you can use the `ParentBased(NeverSample())` sampler from `go.opentelemetry.io/otel/sdk/trace` to keep
|
||||
a similar behavior (drop orphan spans) of `go-redis` as before.
|
||||
|
||||
## [9.0.5](https://github.com/redis/go-redis/compare/v9.0.4...v9.0.5) (2023-05-29)
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -1,7 +1,12 @@
|
||||
GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||
|
||||
test: testdeps
|
||||
$(eval GO_VERSION := $(shell go version | cut -d " " -f 3 | cut -d. -f2))
|
||||
set -e; for dir in $(GO_MOD_DIRS); do \
|
||||
if echo "$${dir}" | grep -q "./example" && [ "$(GO_VERSION)" = "19" ]; then \
|
||||
echo "Skipping go test in $${dir} due to Go version 1.19 and dir contains ./example"; \
|
||||
continue; \
|
||||
fi; \
|
||||
echo "go test in $${dir}"; \
|
||||
(cd "$${dir}" && \
|
||||
go mod tidy -compat=1.18 && \
|
||||
@@ -9,6 +14,7 @@ test: testdeps
|
||||
go test ./... -short -race && \
|
||||
go test ./... -run=NONE -bench=. -benchmem && \
|
||||
env GOOS=linux GOARCH=386 go test && \
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./... && \
|
||||
go vet); \
|
||||
done
|
||||
cd internal/customvet && go build .
|
||||
@@ -26,7 +32,7 @@ build:
|
||||
|
||||
testdata/redis:
|
||||
mkdir -p $@
|
||||
wget -qO- https://download.redis.io/releases/redis-7.2.1.tar.gz | tar xvz --strip-components=1 -C $@
|
||||
wget -qO- https://download.redis.io/releases/redis-7.4-rc2.tar.gz | tar xvz --strip-components=1 -C $@
|
||||
|
||||
testdata/redis/src/redis-server: testdata/redis
|
||||
cd $< && make all
|
||||
|
||||
+30
-5
@@ -3,6 +3,7 @@
|
||||
[](https://github.com/redis/go-redis/actions)
|
||||
[](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
|
||||
[](https://redis.uptrace.dev/)
|
||||
[](https://codecov.io/github/redis/go-redis)
|
||||
[](https://discord.gg/rWtp5Aj)
|
||||
|
||||
> go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
||||
@@ -51,8 +52,8 @@ key value NoSQL database that uses RocksDB as storage engine and is compatible w
|
||||
|
||||
## Features
|
||||
|
||||
- Redis 3 commands except QUIT, MONITOR, and SYNC.
|
||||
- Automatic connection pooling with
|
||||
- Redis commands except QUIT and SYNC.
|
||||
- Automatic connection pooling.
|
||||
- [Pub/Sub](https://redis.uptrace.dev/guide/go-redis-pubsub.html).
|
||||
- [Pipelines and transactions](https://redis.uptrace.dev/guide/go-redis-pipelines.html).
|
||||
- [Scripting](https://redis.uptrace.dev/guide/lua-scripting.html).
|
||||
@@ -143,9 +144,6 @@ to this specification.
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@@ -161,6 +159,33 @@ func ExampleClient() *redis.Client {
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
go-redis supports extending the client identification phase to allow projects to send their own custom client identification.
|
||||
|
||||
#### Default Client Identification
|
||||
|
||||
By default, go-redis automatically sends the client library name and version during the connection process. This feature is available in redis-server as of version 7.2. As a result, the command is "fire and forget", meaning it should fail silently, in the case that the redis server does not support this feature.
|
||||
|
||||
#### Disabling Identity Verification
|
||||
|
||||
When connection identity verification is not required or needs to be explicitly disabled, a `DisableIndentity` configuration option exists. In V10 of this library, `DisableIndentity` will become `DisableIdentity` in order to fix the associated typo.
|
||||
|
||||
To disable verification, set the `DisableIndentity` option to `true` in the Redis client options:
|
||||
|
||||
```go
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
DisableIndentity: true, // Disable set-info on connect
|
||||
})
|
||||
```
|
||||
|
||||
#### Unstable RESP3 Structures for RediSearch Commands
|
||||
When integrating Redis with application functionalities using RESP3, it's important to note that some response structures aren't final yet. This is especially true for more complex structures like search and query results. We recommend using RESP2 when using the search and query capabilities, but we plan to stabilize the RESP3-based API-s in the coming versions. You can find more guidance in the upcoming release notes.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
|
||||
|
||||
+18
-6
@@ -2,6 +2,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type BitMapCmdable interface {
|
||||
@@ -15,6 +16,7 @@ type BitMapCmdable interface {
|
||||
BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
|
||||
BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
|
||||
BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
|
||||
BitFieldRO(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd {
|
||||
@@ -37,16 +39,26 @@ func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int
|
||||
|
||||
type BitCount struct {
|
||||
Start, End int64
|
||||
Unit string // BYTE(default) | BIT
|
||||
}
|
||||
|
||||
const BitCountIndexByte string = "BYTE"
|
||||
const BitCountIndexBit string = "BIT"
|
||||
|
||||
func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd {
|
||||
args := []interface{}{"bitcount", key}
|
||||
args := make([]any, 2, 5)
|
||||
args[0] = "bitcount"
|
||||
args[1] = key
|
||||
if bitCount != nil {
|
||||
args = append(
|
||||
args,
|
||||
bitCount.Start,
|
||||
bitCount.End,
|
||||
)
|
||||
args = append(args, bitCount.Start, bitCount.End)
|
||||
if bitCount.Unit != "" {
|
||||
if bitCount.Unit != BitCountIndexByte && bitCount.Unit != BitCountIndexBit {
|
||||
cmd := NewIntCmd(ctx)
|
||||
cmd.SetErr(errors.New("redis: invalid bitcount index"))
|
||||
return cmd
|
||||
}
|
||||
args = append(args, bitCount.Unit)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
|
||||
+91
-7
@@ -40,7 +40,7 @@ type Cmder interface {
|
||||
|
||||
readTimeout() *time.Duration
|
||||
readReply(rd *proto.Reader) error
|
||||
|
||||
readRawReply(rd *proto.Reader) error
|
||||
SetErr(error)
|
||||
Err() error
|
||||
}
|
||||
@@ -122,11 +122,11 @@ func cmdString(cmd Cmder, val interface{}) string {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type baseCmd struct {
|
||||
ctx context.Context
|
||||
args []interface{}
|
||||
err error
|
||||
keyPos int8
|
||||
|
||||
ctx context.Context
|
||||
args []interface{}
|
||||
err error
|
||||
keyPos int8
|
||||
rawVal interface{}
|
||||
_readTimeout *time.Duration
|
||||
}
|
||||
|
||||
@@ -197,6 +197,11 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) {
|
||||
cmd._readTimeout = &d
|
||||
}
|
||||
|
||||
func (cmd *baseCmd) readRawReply(rd *proto.Reader) (err error) {
|
||||
cmd.rawVal, err = rd.ReadReply()
|
||||
return err
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type Cmd struct {
|
||||
@@ -573,6 +578,10 @@ func (cmd *StatusCmd) Result() (string, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *StatusCmd) Bytes() ([]byte, error) {
|
||||
return util.StringToBytes(cmd.val), cmd.err
|
||||
}
|
||||
|
||||
func (cmd *StatusCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
@@ -3783,6 +3792,65 @@ func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// MapStringInterfaceCmd represents a command that returns a map of strings to interface{}.
|
||||
type MapMapStringInterfaceCmd struct {
|
||||
baseCmd
|
||||
val map[string]interface{}
|
||||
}
|
||||
|
||||
func NewMapMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapMapStringInterfaceCmd {
|
||||
return &MapMapStringInterfaceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) SetVal(val map[string]interface{}) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) Result() (map[string]interface{}, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := make(map[string]interface{}, n/2)
|
||||
for i := 0; i < n; i += 2 {
|
||||
_, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
cmd.err = err
|
||||
}
|
||||
key, err := rd.ReadString()
|
||||
if err != nil {
|
||||
cmd.err = err
|
||||
}
|
||||
value, err := rd.ReadString()
|
||||
if err != nil {
|
||||
cmd.err = err
|
||||
}
|
||||
data[key] = value
|
||||
}
|
||||
|
||||
cmd.val = data
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
type MapStringInterfaceSliceCmd struct {
|
||||
@@ -4997,6 +5065,7 @@ type ClientInfo struct {
|
||||
PSub int // number of pattern matching subscriptions
|
||||
SSub int // redis version 7.0.3, number of shard channel subscriptions
|
||||
Multi int // number of commands in a MULTI/EXEC context
|
||||
Watch int // redis version 7.4 RC1, number of keys this client is currently watching.
|
||||
QueryBuf int // qbuf, query buffer length (0 means no query pending)
|
||||
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
|
||||
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
|
||||
@@ -5149,6 +5218,8 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
|
||||
info.SSub, err = strconv.Atoi(val)
|
||||
case "multi":
|
||||
info.Multi, err = strconv.Atoi(val)
|
||||
case "watch":
|
||||
info.Watch, err = strconv.Atoi(val)
|
||||
case "qbuf":
|
||||
info.QueryBuf, err = strconv.Atoi(val)
|
||||
case "qbuf-free":
|
||||
@@ -5310,6 +5381,16 @@ type LibraryInfo struct {
|
||||
LibVer *string
|
||||
}
|
||||
|
||||
// WithLibraryName returns a valid LibraryInfo with library name only.
|
||||
func WithLibraryName(libName string) LibraryInfo {
|
||||
return LibraryInfo{LibName: &libName}
|
||||
}
|
||||
|
||||
// WithLibraryVersion returns a valid LibraryInfo with library version only.
|
||||
func WithLibraryVersion(libVer string) LibraryInfo {
|
||||
return LibraryInfo{LibVer: &libVer}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type InfoCmd struct {
|
||||
@@ -5444,9 +5525,12 @@ func (cmd *MonitorCmd) readMonitor(rd *proto.Reader, cancel context.CancelFunc)
|
||||
for {
|
||||
cmd.mu.Lock()
|
||||
st := cmd.status
|
||||
pk, _ := rd.Peek(1)
|
||||
cmd.mu.Unlock()
|
||||
if pk, _ := rd.Peek(1); len(pk) != 0 && st == monitorStatusStart {
|
||||
if len(pk) != 0 && st == monitorStatusStart {
|
||||
cmd.mu.Lock()
|
||||
line, err := rd.ReadString()
|
||||
cmd.mu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-1
@@ -220,6 +220,7 @@ type Cmdable interface {
|
||||
ProbabilisticCmdable
|
||||
PubSubCmdable
|
||||
ScriptingFunctionsCmdable
|
||||
SearchCmdable
|
||||
SetCmdable
|
||||
SortedSetCmdable
|
||||
StringCmdable
|
||||
@@ -309,7 +310,7 @@ func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *S
|
||||
|
||||
var cmd *StatusCmd
|
||||
if info.LibName != nil {
|
||||
libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, runtime.Version())
|
||||
libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, internal.ReplaceSpaces(runtime.Version()))
|
||||
cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName)
|
||||
} else {
|
||||
cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer)
|
||||
|
||||
+3
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
@@ -129,7 +130,9 @@ func isMovedError(err error) (moved bool, ask bool, addr string) {
|
||||
if ind == -1 {
|
||||
return false, false, ""
|
||||
}
|
||||
|
||||
addr = s[ind+1:]
|
||||
addr = internal.GetAddr(addr)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -19,6 +19,7 @@ type GenericCmdable interface {
|
||||
Keys(ctx context.Context, pattern string) *StringSliceCmd
|
||||
Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd
|
||||
Move(ctx context.Context, key string, db int) *BoolCmd
|
||||
ObjectFreq(ctx context.Context, key string) *IntCmd
|
||||
ObjectRefCount(ctx context.Context, key string) *IntCmd
|
||||
ObjectEncoding(ctx context.Context, key string) *StringCmd
|
||||
ObjectIdleTime(ctx context.Context, key string) *DurationCmd
|
||||
@@ -159,6 +160,12 @@ func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectFreq(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "object", "freq", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectRefCount(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "object", "refcount", key)
|
||||
_ = c(ctx, cmd)
|
||||
|
||||
+277
-1
@@ -1,6 +1,9 @@
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HashCmdable interface {
|
||||
HDel(ctx context.Context, key string, fields ...string) *IntCmd
|
||||
@@ -16,9 +19,23 @@ type HashCmdable interface {
|
||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
HVals(ctx context.Context, key string) *StringSliceCmd
|
||||
HRandField(ctx context.Context, key string, count int) *StringSliceCmd
|
||||
HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd
|
||||
HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd
|
||||
HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd
|
||||
HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd
|
||||
HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd
|
||||
HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPersist(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HPExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HPTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
}
|
||||
|
||||
func (c cmdable) HDel(ctx context.Context, key string, fields ...string) *IntCmd {
|
||||
@@ -172,3 +189,262 @@ func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match str
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"hscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
args = append(args, "novalues")
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type HExpireArgs struct {
|
||||
NX bool
|
||||
XX bool
|
||||
GT bool
|
||||
LT bool
|
||||
}
|
||||
|
||||
// HExpire - Sets the expiration time for specified fields in a hash in seconds.
|
||||
// The command constructs an argument list starting with "HEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
|
||||
// For more information - https://redis.io/commands/hexpire/
|
||||
func (c cmdable) HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRE", key, formatSec(ctx, expiration), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpire - Sets the expiration time for specified fields in a hash in seconds.
|
||||
// It requires a key, an expiration duration, a struct with boolean flags for conditional expiration settings (NX, XX, GT, LT), and a list of fields.
|
||||
// The command constructs an argument list starting with "HEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
|
||||
// For more information - https://redis.io/commands/hexpire/
|
||||
func (c cmdable) HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRE", key, formatSec(ctx, expiration)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpire - Sets the expiration time for specified fields in a hash in milliseconds.
|
||||
// Similar to HExpire, it accepts a key, an expiration duration in milliseconds, a struct with expiration condition flags, and a list of fields.
|
||||
// The command modifies the standard time.Duration to milliseconds for the Redis command.
|
||||
// For more information - https://redis.io/commands/hpexpire/
|
||||
func (c cmdable) HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRE", key, formatMs(ctx, expiration), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRE", key, formatMs(ctx, expiration)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpireAt - Sets the expiration time for specified fields in a hash to a UNIX timestamp in seconds.
|
||||
// Takes a key, a UNIX timestamp, a struct of conditional flags, and a list of fields.
|
||||
// The command sets absolute expiration times based on the UNIX timestamp provided.
|
||||
// For more information - https://redis.io/commands/hexpireat/
|
||||
func (c cmdable) HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd {
|
||||
|
||||
args := []interface{}{"HEXPIREAT", key, tm.Unix(), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIREAT", key, tm.Unix()}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpireAt - Sets the expiration time for specified fields in a hash to a UNIX timestamp in milliseconds.
|
||||
// Similar to HExpireAt but for timestamps in milliseconds. It accepts the same parameters and adjusts the UNIX time to milliseconds.
|
||||
// For more information - https://redis.io/commands/hpexpireat/
|
||||
func (c cmdable) HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIREAT", key, tm.UnixNano() / int64(time.Millisecond), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIREAT", key, tm.UnixNano() / int64(time.Millisecond)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPersist - Removes the expiration time from specified fields in a hash.
|
||||
// Accepts a key and the fields themselves.
|
||||
// This command ensures that each field specified will have its expiration removed if present.
|
||||
// For more information - https://redis.io/commands/hpersist/
|
||||
func (c cmdable) HPersist(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPERSIST", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpireTime - Retrieves the expiration time for specified fields in a hash as a UNIX timestamp in seconds.
|
||||
// Requires a key and the fields themselves to fetch their expiration timestamps.
|
||||
// This command returns the expiration times for each field or error/status codes for each field as specified.
|
||||
// For more information - https://redis.io/commands/hexpiretime/
|
||||
func (c cmdable) HExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRETIME", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpireTime - Retrieves the expiration time for specified fields in a hash as a UNIX timestamp in milliseconds.
|
||||
// Similar to HExpireTime, adjusted for timestamps in milliseconds. It requires the same parameters.
|
||||
// Provides the expiration timestamp for each field in milliseconds.
|
||||
// For more information - https://redis.io/commands/hexpiretime/
|
||||
func (c cmdable) HPExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRETIME", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HTTL - Retrieves the remaining time to live for specified fields in a hash in seconds.
|
||||
// Requires a key and the fields themselves. It returns the TTL for each specified field.
|
||||
// This command fetches the TTL in seconds for each field or returns error/status codes as appropriate.
|
||||
// For more information - https://redis.io/commands/httl/
|
||||
func (c cmdable) HTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HTTL", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPTTL - Retrieves the remaining time to live for specified fields in a hash in milliseconds.
|
||||
// Similar to HTTL, but returns the TTL in milliseconds. It requires a key and the specified fields.
|
||||
// This command provides the TTL in milliseconds for each field or returns error/status codes as needed.
|
||||
// For more information - https://redis.io/commands/hpttl/
|
||||
func (c cmdable) HPTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPTTL", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
+8
@@ -168,9 +168,12 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
p.connsMu.Lock()
|
||||
if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns {
|
||||
p.connsMu.Unlock()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
p.connsMu.Unlock()
|
||||
|
||||
cn, err := p.dialConn(ctx, pooled)
|
||||
if err != nil {
|
||||
@@ -180,6 +183,11 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
|
||||
if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns {
|
||||
_ = cn.Close()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
p.conns = append(p.conns, cn)
|
||||
if pooled {
|
||||
// If pool is full remove the cn on next Put.
|
||||
|
||||
+82
@@ -2,6 +2,9 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
@@ -44,3 +47,82 @@ func isLower(s string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ReplaceSpaces(s string) string {
|
||||
// Pre-allocate a builder with the same length as s to minimize allocations.
|
||||
// This is a basic optimization; adjust the initial size based on your use case.
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(s))
|
||||
|
||||
for _, char := range s {
|
||||
if char == ' ' {
|
||||
// Replace space with a hyphen.
|
||||
builder.WriteRune('-')
|
||||
} else {
|
||||
// Copy the character as-is.
|
||||
builder.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func GetAddr(addr string) string {
|
||||
ind := strings.LastIndexByte(addr, ':')
|
||||
if ind == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.IndexByte(addr, '.') != -1 {
|
||||
return addr
|
||||
}
|
||||
|
||||
if addr[0] == '[' {
|
||||
return addr
|
||||
}
|
||||
return net.JoinHostPort(addr[:ind], addr[ind+1:])
|
||||
}
|
||||
|
||||
func ToInteger(val interface{}) int {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case string:
|
||||
i, _ := strconv.Atoi(v)
|
||||
return i
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func ToFloat(val interface{}) float64 {
|
||||
switch v := val.(type) {
|
||||
case float64:
|
||||
return v
|
||||
case string:
|
||||
f, _ := strconv.ParseFloat(v, 64)
|
||||
return f
|
||||
default:
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func ToString(val interface{}) string {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ToStringSlice(val interface{}) []string {
|
||||
if arr, ok := val.([]interface{}); ok {
|
||||
result := make([]string, len(arr))
|
||||
for i, v := range arr {
|
||||
result[i] = ToString(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-4
@@ -60,7 +60,7 @@ type JSONArrTrimArgs struct {
|
||||
type JSONCmd struct {
|
||||
baseCmd
|
||||
val string
|
||||
expanded []interface{}
|
||||
expanded interface{}
|
||||
}
|
||||
|
||||
var _ Cmder = (*JSONCmd)(nil)
|
||||
@@ -100,11 +100,11 @@ func (cmd *JSONCmd) Result() (string, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd JSONCmd) Expanded() (interface{}, error) {
|
||||
func (cmd *JSONCmd) Expanded() (interface{}, error) {
|
||||
if len(cmd.val) != 0 && cmd.expanded == nil {
|
||||
err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
|
||||
}
|
||||
|
||||
// JSONNumIncrBy increments the number value stored at the specified path by the provided number.
|
||||
// For more information, see https://redis.io/commands/json.numincreby
|
||||
// For more information, see https://redis.io/docs/latest/commands/json.numincrby/
|
||||
func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd {
|
||||
args := []interface{}{"JSON.NUMINCRBY", key, path, value}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
|
||||
+12
-3
@@ -61,6 +61,12 @@ type Options struct {
|
||||
// before reconnecting. It should return the current username and password.
|
||||
CredentialsProvider func() (username string, password string)
|
||||
|
||||
// CredentialsProviderContext is an enhanced parameter of CredentialsProvider,
|
||||
// done to maintain API compatibility. In the future,
|
||||
// there might be a merge between CredentialsProviderContext and CredentialsProvider.
|
||||
// There will be a conflict between them; if CredentialsProviderContext exists, we will ignore CredentialsProvider.
|
||||
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
||||
|
||||
// Database to be selected after connecting to the server.
|
||||
DB int
|
||||
|
||||
@@ -147,6 +153,9 @@ type Options struct {
|
||||
|
||||
// Add suffix to client name. Default is empty.
|
||||
IdentitySuffix string
|
||||
|
||||
// Enable Unstable mode for Redis Search module with RESP3.
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *Options) init() {
|
||||
@@ -235,7 +244,7 @@ func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, er
|
||||
}
|
||||
}
|
||||
|
||||
// ParseURL parses an URL into Options that can be used to connect to Redis.
|
||||
// ParseURL parses a URL into Options that can be used to connect to Redis.
|
||||
// Scheme is required.
|
||||
// There are two connection types: by tcp socket and by unix socket.
|
||||
// Tcp connection:
|
||||
@@ -250,12 +259,12 @@ func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, er
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// additionally a plain integer as value (i.e. without unit) is interpreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// URL attributes (scheme, host, userinfo, resp.), query parameters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
//
|
||||
|
||||
+54
-22
@@ -62,9 +62,11 @@ type ClusterOptions struct {
|
||||
|
||||
OnConnect func(ctx context.Context, cn *Conn) error
|
||||
|
||||
Protocol int
|
||||
Username string
|
||||
Password string
|
||||
Protocol int
|
||||
Username string
|
||||
Password string
|
||||
CredentialsProvider func() (username string, password string)
|
||||
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
||||
|
||||
MaxRetries int
|
||||
MinRetryBackoff time.Duration
|
||||
@@ -156,12 +158,12 @@ func (opt *ClusterOptions) init() {
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// additionally a plain integer as value (i.e. without unit) is interpreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// URL attributes (scheme, host, userinfo, resp.), query parameters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
//
|
||||
@@ -271,9 +273,11 @@ func (opt *ClusterOptions) clientOptions() *Options {
|
||||
Dialer: opt.Dialer,
|
||||
OnConnect: opt.OnConnect,
|
||||
|
||||
Protocol: opt.Protocol,
|
||||
Username: opt.Username,
|
||||
Password: opt.Password,
|
||||
Protocol: opt.Protocol,
|
||||
Username: opt.Username,
|
||||
Password: opt.Password,
|
||||
CredentialsProvider: opt.CredentialsProvider,
|
||||
CredentialsProviderContext: opt.CredentialsProviderContext,
|
||||
|
||||
MaxRetries: opt.MaxRetries,
|
||||
MinRetryBackoff: opt.MinRetryBackoff,
|
||||
@@ -337,6 +341,8 @@ func (n *clusterNode) Close() error {
|
||||
return n.Client.Close()
|
||||
}
|
||||
|
||||
const maximumNodeLatency = 1 * time.Minute
|
||||
|
||||
func (n *clusterNode) updateLatency() {
|
||||
const numProbe = 10
|
||||
var dur uint64
|
||||
@@ -357,7 +363,7 @@ func (n *clusterNode) updateLatency() {
|
||||
if successes == 0 {
|
||||
// If none of the pings worked, set latency to some arbitrarily high value so this node gets
|
||||
// least priority.
|
||||
latency = float64((1 * time.Minute) / time.Microsecond)
|
||||
latency = float64((maximumNodeLatency) / time.Microsecond)
|
||||
} else {
|
||||
latency = float64(dur) / float64(successes)
|
||||
}
|
||||
@@ -731,20 +737,40 @@ func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) {
|
||||
return c.nodes.Random()
|
||||
}
|
||||
|
||||
var node *clusterNode
|
||||
var allNodesFailing = true
|
||||
var (
|
||||
closestNonFailingNode *clusterNode
|
||||
closestNode *clusterNode
|
||||
minLatency time.Duration
|
||||
)
|
||||
|
||||
// setting the max possible duration as zerovalue for minlatency
|
||||
minLatency = time.Duration(math.MaxInt64)
|
||||
|
||||
for _, n := range nodes {
|
||||
if n.Failing() {
|
||||
continue
|
||||
if closestNode == nil || n.Latency() < minLatency {
|
||||
closestNode = n
|
||||
minLatency = n.Latency()
|
||||
if !n.Failing() {
|
||||
closestNonFailingNode = n
|
||||
allNodesFailing = false
|
||||
}
|
||||
}
|
||||
if node == nil || n.Latency() < node.Latency() {
|
||||
node = n
|
||||
}
|
||||
}
|
||||
if node != nil {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// If all nodes are failing - return random node
|
||||
// pick the healthly node with the lowest latency
|
||||
if !allNodesFailing && closestNonFailingNode != nil {
|
||||
return closestNonFailingNode, nil
|
||||
}
|
||||
|
||||
// if all nodes are failing, we will pick the temporarily failing node with lowest latency
|
||||
if minLatency < maximumNodeLatency && closestNode != nil {
|
||||
internal.Logger.Printf(context.TODO(), "redis: all nodes are marked as failed, picking the temporarily failing node with lowest latency")
|
||||
return closestNode, nil
|
||||
}
|
||||
|
||||
// If all nodes are having the maximum latency(all pings are failing) - return a random node across the cluster
|
||||
internal.Logger.Printf(context.TODO(), "redis: pings to all nodes are failing, picking a random node across the cluster")
|
||||
return c.nodes.Random()
|
||||
}
|
||||
|
||||
@@ -912,10 +938,13 @@ func (c *ClusterClient) Process(ctx context.Context, cmd Cmder) error {
|
||||
func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||
slot := c.cmdSlot(ctx, cmd)
|
||||
var node *clusterNode
|
||||
var moved bool
|
||||
var ask bool
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
||||
if attempt > 0 {
|
||||
// MOVED and ASK responses are not transient errors that require retry delay; they
|
||||
// should be attempted immediately.
|
||||
if attempt > 0 && !moved && !ask {
|
||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -959,7 +988,6 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||
continue
|
||||
}
|
||||
|
||||
var moved bool
|
||||
var addr string
|
||||
moved, ask, addr = isMovedError(lastErr)
|
||||
if moved || ask {
|
||||
@@ -1293,6 +1321,7 @@ func (c *ClusterClient) processPipelineNode(
|
||||
_ = node.Client.withProcessPipelineHook(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
|
||||
cn, err := node.Client.getConn(ctx)
|
||||
if err != nil {
|
||||
node.MarkAsFailing()
|
||||
_ = c.mapCmdsByNode(ctx, failedCmds, cmds)
|
||||
setCmdsErr(cmds, err)
|
||||
return err
|
||||
@@ -1314,6 +1343,9 @@ func (c *ClusterClient) processPipelineNodeConn(
|
||||
if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmds(wr, cmds)
|
||||
}); err != nil {
|
||||
if isBadConn(err, false, node.Client.getAddr()) {
|
||||
node.MarkAsFailing()
|
||||
}
|
||||
if shouldRetry(err, true) {
|
||||
_ = c.mapCmdsByNode(ctx, failedCmds, cmds)
|
||||
}
|
||||
@@ -1345,7 +1377,7 @@ func (c *ClusterClient) pipelineReadCmds(
|
||||
continue
|
||||
}
|
||||
|
||||
if c.opt.ReadOnly {
|
||||
if c.opt.ReadOnly && isBadConn(err, false, node.Client.getAddr()) {
|
||||
node.MarkAsFailing()
|
||||
}
|
||||
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "redis",
|
||||
"version": "9.4.0",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:redis/go-redis.git",
|
||||
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",
|
||||
"license": "BSD-2-clause"
|
||||
}
|
||||
+2
-2
@@ -84,7 +84,7 @@ func (c *PubSub) conn(ctx context.Context, newChannels []string) (*pool.Conn, er
|
||||
}
|
||||
|
||||
func (c *PubSub) writeCmd(ctx context.Context, cn *pool.Conn, cmd Cmder) error {
|
||||
return cn.WithWriter(context.Background(), c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmd(wr, cmd)
|
||||
})
|
||||
}
|
||||
@@ -491,7 +491,7 @@ func (c *PubSub) getContext() context.Context {
|
||||
// Receive* APIs can not be used after channel is created.
|
||||
//
|
||||
// go-redis periodically sends ping messages to test connection health
|
||||
// and re-subscribes if ping can not not received for 1 minute.
|
||||
// and re-subscribes if ping can not received for 1 minute.
|
||||
func (c *PubSub) Channel(opts ...ChannelOption) <-chan *Message {
|
||||
c.chOnce.Do(func() {
|
||||
c.msgCh = newChannel(c, opts...)
|
||||
|
||||
+40
-16
@@ -283,8 +283,13 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
}
|
||||
cn.Inited = true
|
||||
|
||||
var err error
|
||||
username, password := c.opt.Username, c.opt.Password
|
||||
if c.opt.CredentialsProvider != nil {
|
||||
if c.opt.CredentialsProviderContext != nil {
|
||||
if username, password, err = c.opt.CredentialsProviderContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if c.opt.CredentialsProvider != nil {
|
||||
username, password = c.opt.CredentialsProvider()
|
||||
}
|
||||
|
||||
@@ -300,7 +305,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
|
||||
// for redis-server versions that do not support the HELLO command,
|
||||
// RESP2 will continue to be used.
|
||||
if err := conn.Hello(ctx, protocol, username, password, "").Err(); err == nil {
|
||||
if err = conn.Hello(ctx, protocol, username, password, "").Err(); err == nil {
|
||||
auth = true
|
||||
} else if !isRedisError(err) {
|
||||
// When the server responds with the RESP protocol and the result is not a normal
|
||||
@@ -312,18 +317,8 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
// difficult to rely on error strings to determine all results.
|
||||
return err
|
||||
}
|
||||
if !c.opt.DisableIndentity {
|
||||
libName := ""
|
||||
libVer := Version()
|
||||
if c.opt.IdentitySuffix != "" {
|
||||
libName = c.opt.IdentitySuffix
|
||||
}
|
||||
libInfo := LibraryInfo{LibName: &libName}
|
||||
conn.ClientSetInfo(ctx, libInfo)
|
||||
libInfo = LibraryInfo{LibVer: &libVer}
|
||||
conn.ClientSetInfo(ctx, libInfo)
|
||||
}
|
||||
_, err := conn.Pipelined(ctx, func(pipe Pipeliner) error {
|
||||
|
||||
_, err = conn.Pipelined(ctx, func(pipe Pipeliner) error {
|
||||
if !auth && password != "" {
|
||||
if username != "" {
|
||||
pipe.AuthACL(ctx, username, password)
|
||||
@@ -350,6 +345,18 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.opt.DisableIndentity {
|
||||
libName := ""
|
||||
libVer := Version()
|
||||
if c.opt.IdentitySuffix != "" {
|
||||
libName = c.opt.IdentitySuffix
|
||||
}
|
||||
p := conn.Pipeline()
|
||||
p.ClientSetInfo(ctx, WithLibraryName(libName))
|
||||
p.ClientSetInfo(ctx, WithLibraryVersion(libVer))
|
||||
_, _ = p.Exec(ctx)
|
||||
}
|
||||
|
||||
if c.opt.OnConnect != nil {
|
||||
return c.opt.OnConnect(ctx, conn)
|
||||
}
|
||||
@@ -405,6 +412,19 @@ func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (c *baseClient) assertUnstableCommand(cmd Cmder) bool {
|
||||
switch cmd.(type) {
|
||||
case *AggregateCmd, *FTInfoCmd, *FTSpellCheckCmd, *FTSearchCmd, *FTSynDumpCmd:
|
||||
if c.opt.UnstableResp3 {
|
||||
return true
|
||||
} else {
|
||||
panic("RESP3 responses for this command are disabled because they may still change. Please set the flag UnstableResp3 . See the [README](https://github.com/redis/go-redis/blob/master/README.md) and the release notes for guidance.")
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool, error) {
|
||||
if attempt > 0 {
|
||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||
@@ -420,8 +440,12 @@ func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool
|
||||
atomic.StoreUint32(&retryTimeout, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cn.WithReader(c.context(ctx), c.cmdTimeout(cmd), cmd.readReply); err != nil {
|
||||
readReplyFunc := cmd.readReply
|
||||
// Apply unstable RESP3 search module.
|
||||
if c.opt.Protocol != 2 && c.assertUnstableCommand(cmd) {
|
||||
readReplyFunc = cmd.readRawReply
|
||||
}
|
||||
if err := cn.WithReader(c.context(ctx), c.cmdTimeout(cmd), readReplyFunc); err != nil {
|
||||
if cmd.readTimeout() == nil {
|
||||
atomic.StoreUint32(&retryTimeout, 1)
|
||||
} else {
|
||||
|
||||
+2
@@ -100,6 +100,7 @@ type RingOptions struct {
|
||||
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *RingOptions) init() {
|
||||
@@ -168,6 +169,7 @@ func (opt *RingOptions) clientOptions() *Options {
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2240
File diff suppressed because it is too large
Load Diff
+9
@@ -82,6 +82,7 @@ type FailoverOptions struct {
|
||||
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *FailoverOptions) clientOptions() *Options {
|
||||
@@ -119,6 +120,7 @@ func (opt *FailoverOptions) clientOptions() *Options {
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +155,10 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +196,9 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -137,10 +137,11 @@ type XReadArgs struct {
|
||||
Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2
|
||||
Count int64
|
||||
Block time.Duration
|
||||
ID string
|
||||
}
|
||||
|
||||
func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd {
|
||||
args := make([]interface{}, 0, 6+len(a.Streams))
|
||||
args := make([]interface{}, 0, 2*len(a.Streams)+6)
|
||||
args = append(args, "xread")
|
||||
|
||||
keyPos := int8(1)
|
||||
@@ -159,6 +160,11 @@ func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd {
|
||||
for _, s := range a.Streams {
|
||||
args = append(args, s)
|
||||
}
|
||||
if a.ID != "" {
|
||||
for range a.Streams {
|
||||
args = append(args, a.ID)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := NewXStreamSliceCmd(ctx, args...)
|
||||
if a.Block >= 0 {
|
||||
@@ -178,36 +184,42 @@ func (c cmdable) XReadStreams(ctx context.Context, streams ...string) *XStreamSl
|
||||
|
||||
func (c cmdable) XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start, "mkstream")
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "setid", stream, group, start)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDestroy(ctx context.Context, stream, group string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "destroy", stream, group)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "createconsumer", stream, group, consumer)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "delconsumer", stream, group, consumer)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
+42
-14
@@ -40,25 +40,32 @@ type TimeseriesCmdable interface {
|
||||
}
|
||||
|
||||
type TSOptions struct {
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Encoding string
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Encoding string
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
type TSIncrDecrOptions struct {
|
||||
Timestamp int64
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Uncompressed bool
|
||||
Labels map[string]string
|
||||
Timestamp int64
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Uncompressed bool
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
|
||||
type TSAlterOptions struct {
|
||||
Retention int
|
||||
ChunkSize int
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
Retention int
|
||||
ChunkSize int
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
|
||||
type TSCreateRuleOptions struct {
|
||||
@@ -223,6 +230,9 @@ func (c cmdable) TSAddWithArgs(ctx context.Context, key string, timestamp interf
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
@@ -264,6 +274,9 @@ func (c cmdable) TSCreateWithArgs(ctx context.Context, key string, options *TSOp
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
@@ -292,6 +305,9 @@ func (c cmdable) TSAlter(ctx context.Context, key string, options *TSAlterOption
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
@@ -351,12 +367,18 @@ func (c cmdable) TSIncrByWithArgs(ctx context.Context, key string, timestamp flo
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
@@ -391,12 +413,18 @@ func (c cmdable) TSDecrByWithArgs(ctx context.Context, key string, timestamp flo
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
|
||||
+3
@@ -68,6 +68,7 @@ type UniversalOptions struct {
|
||||
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
// Cluster returns cluster options created from the universal options.
|
||||
@@ -160,6 +161,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
||||
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +205,7 @@ func (o *UniversalOptions) Simple() *Options {
|
||||
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@ package redis
|
||||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "9.4.0"
|
||||
return "9.7.0"
|
||||
}
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ const (
|
||||
MBTInput MessageBlockType = "input"
|
||||
MBTHeader MessageBlockType = "header"
|
||||
MBTRichText MessageBlockType = "rich_text"
|
||||
MBTCall MessageBlockType = "call"
|
||||
MBTVideo MessageBlockType = "video"
|
||||
)
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package slack
|
||||
|
||||
// CallBlock defines data that is used to display a call in slack.
|
||||
//
|
||||
// More Information: https://api.slack.com/apis/calls#post_to_channel
|
||||
type CallBlock struct {
|
||||
Type MessageBlockType `json:"type"`
|
||||
BlockID string `json:"block_id,omitempty"`
|
||||
CallID string `json:"call_id"`
|
||||
}
|
||||
|
||||
// BlockType returns the type of the block
|
||||
func (s CallBlock) BlockType() MessageBlockType {
|
||||
return s.Type
|
||||
}
|
||||
|
||||
// NewFileBlock returns a new instance of a file block
|
||||
func NewCallBlock(callID string) *CallBlock {
|
||||
return &CallBlock{
|
||||
Type: MBTCall,
|
||||
CallID: callID,
|
||||
}
|
||||
}
|
||||
+2
@@ -69,6 +69,8 @@ func (b *Blocks) UnmarshalJSON(data []byte) error {
|
||||
block = &RichTextBlock{}
|
||||
case "section":
|
||||
block = &SectionBlock{}
|
||||
case "call":
|
||||
block = &CallBlock{}
|
||||
case "video":
|
||||
block = &VideoBlock{}
|
||||
default:
|
||||
|
||||
+133
-1
@@ -258,6 +258,36 @@ func NewOptionsSelectBlockElement(optType string, placeholder *TextBlockObject,
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialOption sets the initial option for the select element
|
||||
func (s *SelectBlockElement) WithInitialOption(option *OptionBlockObject) *SelectBlockElement {
|
||||
s.InitialOption = option
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialUser sets the initial user for the select element
|
||||
func (s *SelectBlockElement) WithInitialUser(user string) *SelectBlockElement {
|
||||
s.InitialUser = user
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialConversation sets the initial conversation for the select element
|
||||
func (s *SelectBlockElement) WithInitialConversation(conversation string) *SelectBlockElement {
|
||||
s.InitialConversation = conversation
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialChannel sets the initial channel for the select element
|
||||
func (s *SelectBlockElement) WithInitialChannel(channel string) *SelectBlockElement {
|
||||
s.InitialChannel = channel
|
||||
return s
|
||||
}
|
||||
|
||||
// WithConfirm adds a confirmation dialogue to the select element
|
||||
func (s *SelectBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *SelectBlockElement {
|
||||
s.Confirm = confirm
|
||||
return s
|
||||
}
|
||||
|
||||
// NewOptionsGroupSelectBlockElement returns a new instance of SelectBlockElement for use with
|
||||
// the Options object only.
|
||||
func NewOptionsGroupSelectBlockElement(
|
||||
@@ -309,6 +339,48 @@ func NewOptionsMultiSelectBlockElement(optType string, placeholder *TextBlockObj
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialOptions sets the initial options for the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithInitialOptions(options ...*OptionBlockObject) *MultiSelectBlockElement {
|
||||
s.InitialOptions = options
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialUsers sets the initial users for the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithInitialUsers(users ...string) *MultiSelectBlockElement {
|
||||
s.InitialUsers = users
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialConversations sets the initial conversations for the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithInitialConversations(conversations ...string) *MultiSelectBlockElement {
|
||||
s.InitialConversations = conversations
|
||||
return s
|
||||
}
|
||||
|
||||
// WithInitialChannels sets the initial channels for the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithInitialChannels(channels ...string) *MultiSelectBlockElement {
|
||||
s.InitialChannels = channels
|
||||
return s
|
||||
}
|
||||
|
||||
// WithConfirm adds a confirmation dialogue to the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *MultiSelectBlockElement {
|
||||
s.Confirm = confirm
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxSelectedItems sets the maximum number of items that can be selected
|
||||
func (s *MultiSelectBlockElement) WithMaxSelectedItems(maxSelectedItems int) *MultiSelectBlockElement {
|
||||
s.MaxSelectedItems = &maxSelectedItems
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinQueryLength sets the minimum query length for the multi-select element
|
||||
func (s *MultiSelectBlockElement) WithMinQueryLength(minQueryLength int) *MultiSelectBlockElement {
|
||||
s.MinQueryLength = &minQueryLength
|
||||
return s
|
||||
}
|
||||
|
||||
// NewOptionsGroupMultiSelectBlockElement returns a new instance of MultiSelectBlockElement for use with
|
||||
// the Options object only.
|
||||
func NewOptionsGroupMultiSelectBlockElement(
|
||||
@@ -352,6 +424,12 @@ func NewOverflowBlockElement(actionID string, options ...*OptionBlockObject) *Ov
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfirm adds a confirmation dialogue to the overflow element
|
||||
func (s *OverflowBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *OverflowBlockElement {
|
||||
s.Confirm = confirm
|
||||
return s
|
||||
}
|
||||
|
||||
// DatePickerBlockElement defines an element which lets users easily select a
|
||||
// date from a calendar style UI. Date picker elements can be used inside of
|
||||
// section and actions blocks.
|
||||
@@ -520,6 +598,36 @@ func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialValue sets the initial value for the plain-text input element
|
||||
func (s *PlainTextInputBlockElement) WithInitialValue(initialValue string) *PlainTextInputBlockElement {
|
||||
s.InitialValue = initialValue
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinLength sets the minimum length for the plain-text input element
|
||||
func (s *PlainTextInputBlockElement) WithMinLength(minLength int) *PlainTextInputBlockElement {
|
||||
s.MinLength = minLength
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxLength sets the maximum length for the plain-text input element
|
||||
func (s *PlainTextInputBlockElement) WithMaxLength(maxLength int) *PlainTextInputBlockElement {
|
||||
s.MaxLength = maxLength
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMultiline sets the multiline property for the plain-text input element
|
||||
func (s *PlainTextInputBlockElement) WithMultiline(multiline bool) *PlainTextInputBlockElement {
|
||||
s.Multiline = multiline
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDispatchActionConfig sets the dispatch action config for the plain-text input element
|
||||
func (s *PlainTextInputBlockElement) WithDispatchActionConfig(config *DispatchActionConfig) *PlainTextInputBlockElement {
|
||||
s.DispatchActionConfig = config
|
||||
return s
|
||||
}
|
||||
|
||||
// RichTextInputBlockElement creates a field where allows users to enter formatted text
|
||||
// in a WYSIWYG composer, offering the same messaging writing experience as in Slack
|
||||
// More Information: https://api.slack.com/reference/block-kit/block-elements#rich_text_input
|
||||
@@ -527,7 +635,7 @@ type RichTextInputBlockElement struct {
|
||||
Type MessageElementType `json:"type"`
|
||||
ActionID string `json:"action_id,omitempty"`
|
||||
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
|
||||
InitialValue string `json:"initial_value,omitempty"`
|
||||
InitialValue *RichTextBlock `json:"initial_value,omitempty"`
|
||||
DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
|
||||
FocusOnLoad bool `json:"focus_on_load,omitempty"`
|
||||
}
|
||||
@@ -629,6 +737,30 @@ func NewNumberInputBlockElement(placeholder *TextBlockObject, actionID string, i
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialValue sets the initial value for the number input element
|
||||
func (s *NumberInputBlockElement) WithInitialValue(initialValue string) *NumberInputBlockElement {
|
||||
s.InitialValue = initialValue
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinValue sets the minimum value for the number input element
|
||||
func (s *NumberInputBlockElement) WithMinValue(minValue string) *NumberInputBlockElement {
|
||||
s.MinValue = minValue
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxValue sets the maximum value for the number input element
|
||||
func (s *NumberInputBlockElement) WithMaxValue(maxValue string) *NumberInputBlockElement {
|
||||
s.MaxValue = maxValue
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDispatchActionConfig sets the dispatch action config for the number input element
|
||||
func (s *NumberInputBlockElement) WithDispatchActionConfig(config *DispatchActionConfig) *NumberInputBlockElement {
|
||||
s.DispatchActionConfig = config
|
||||
return s
|
||||
}
|
||||
|
||||
// FileInputBlockElement creates a field where a user can upload a file.
|
||||
//
|
||||
// File input elements are currently only available in modals.
|
||||
|
||||
+12
@@ -28,3 +28,15 @@ func NewInputBlock(blockID string, label, hint *TextBlockObject, element BlockEl
|
||||
Hint: hint,
|
||||
}
|
||||
}
|
||||
|
||||
// WithOptional sets the optional flag on the input block
|
||||
func (s *InputBlock) WithOptional(optional bool) *InputBlock {
|
||||
s.Optional = optional
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDispatchAction sets the dispatch action flag on the input block
|
||||
func (s *InputBlock) WithDispatchAction(dispatchAction bool) *InputBlock {
|
||||
s.DispatchAction = dispatchAction
|
||||
return s
|
||||
}
|
||||
|
||||
+8
-1
@@ -341,6 +341,7 @@ type RichTextSectionEmojiElement struct {
|
||||
Type RichTextSectionElementType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
SkinTone int `json:"skin_tone"`
|
||||
Unicode string `json:"unicode,omitempty"`
|
||||
Style *RichTextSectionTextStyle `json:"style,omitempty"`
|
||||
}
|
||||
|
||||
@@ -414,16 +415,22 @@ func NewRichTextSectionUserGroupElement(usergroupID string) *RichTextSectionUser
|
||||
type RichTextSectionDateElement struct {
|
||||
Type RichTextSectionElementType `json:"type"`
|
||||
Timestamp JSONTime `json:"timestamp"`
|
||||
Format string `json:"format"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Fallback *string `json:"fallback,omitempty"`
|
||||
}
|
||||
|
||||
func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType {
|
||||
return r.Type
|
||||
}
|
||||
|
||||
func NewRichTextSectionDateElement(timestamp int64) *RichTextSectionDateElement {
|
||||
func NewRichTextSectionDateElement(timestamp int64, format string, url *string, fallback *string) *RichTextSectionDateElement {
|
||||
return &RichTextSectionDateElement{
|
||||
Type: RTSEDate,
|
||||
Timestamp: JSONTime(timestamp),
|
||||
Format: format,
|
||||
URL: url,
|
||||
Fallback: fallback,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Call struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
DateStart JSONTime `json:"date_start"`
|
||||
DateEnd JSONTime `json:"date_end"`
|
||||
ExternalUniqueID string `json:"external_unique_id"`
|
||||
JoinURL string `json:"join_url"`
|
||||
DesktopAppJoinURL string `json:"desktop_app_join_url"`
|
||||
ExternalDisplayID string `json:"external_display_id"`
|
||||
Participants []CallParticipant `json:"users"`
|
||||
Channels []string `json:"channels"`
|
||||
}
|
||||
|
||||
// CallParticipant is a thin user representation which has a SlackID, ExternalID, or both.
|
||||
//
|
||||
// See: https://api.slack.com/apis/calls#users
|
||||
type CallParticipant struct {
|
||||
SlackID string `json:"slack_id,omitempty"`
|
||||
ExternalID string `json:"external_id,omitempty"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
}
|
||||
|
||||
// Valid checks if the CallUser has a is valid with a SlackID or ExternalID or both.
|
||||
func (u CallParticipant) Valid() bool {
|
||||
return u.SlackID != "" || u.ExternalID != ""
|
||||
}
|
||||
|
||||
type AddCallParameters struct {
|
||||
JoinURL string // Required
|
||||
ExternalUniqueID string // Required
|
||||
CreatedBy string // Required if using a bot token
|
||||
Title string
|
||||
DesktopAppJoinURL string
|
||||
ExternalDisplayID string
|
||||
DateStart JSONTime
|
||||
Participants []CallParticipant
|
||||
}
|
||||
|
||||
type UpdateCallParameters struct {
|
||||
Title string
|
||||
DesktopAppJoinURL string
|
||||
JoinURL string
|
||||
}
|
||||
|
||||
type EndCallParameters struct {
|
||||
// Duration is the duration of the call in seconds. Omitted if 0.
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
type callResponse struct {
|
||||
Call Call `json:"call"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// AddCall adds a new Call to the Slack API.
|
||||
func (api *Client) AddCall(params AddCallParameters) (Call, error) {
|
||||
return api.AddCallContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// AddCallContext adds a new Call to the Slack API.
|
||||
func (api *Client) AddCallContext(ctx context.Context, params AddCallParameters) (Call, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"join_url": {params.JoinURL},
|
||||
"external_unique_id": {params.ExternalUniqueID},
|
||||
}
|
||||
if params.CreatedBy != "" {
|
||||
values.Set("created_by", params.CreatedBy)
|
||||
}
|
||||
if params.DateStart != 0 {
|
||||
values.Set("date_start", strconv.FormatInt(int64(params.DateStart), 10))
|
||||
}
|
||||
if params.DesktopAppJoinURL != "" {
|
||||
values.Set("desktop_app_join_url", params.DesktopAppJoinURL)
|
||||
}
|
||||
if params.ExternalDisplayID != "" {
|
||||
values.Set("external_display_id", params.ExternalDisplayID)
|
||||
}
|
||||
if params.Title != "" {
|
||||
values.Set("title", params.Title)
|
||||
}
|
||||
if len(params.Participants) > 0 {
|
||||
data, err := json.Marshal(params.Participants)
|
||||
if err != nil {
|
||||
return Call{}, err
|
||||
}
|
||||
values.Set("users", string(data))
|
||||
}
|
||||
|
||||
response := &callResponse{}
|
||||
if err := api.postMethod(ctx, "calls.add", values, response); err != nil {
|
||||
return Call{}, err
|
||||
}
|
||||
|
||||
return response.Call, response.Err()
|
||||
}
|
||||
|
||||
// GetCallInfo returns information about a Call.
|
||||
func (api *Client) GetCall(callID string) (Call, error) {
|
||||
return api.GetCallContext(context.Background(), callID)
|
||||
}
|
||||
|
||||
// GetCallInfoContext returns information about a Call.
|
||||
func (api *Client) GetCallContext(ctx context.Context, callID string) (Call, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"id": {callID},
|
||||
}
|
||||
|
||||
response := &callResponse{}
|
||||
if err := api.postMethod(ctx, "calls.info", values, response); err != nil {
|
||||
return Call{}, err
|
||||
}
|
||||
return response.Call, response.Err()
|
||||
}
|
||||
|
||||
func (api *Client) UpdateCall(callID string, params UpdateCallParameters) (Call, error) {
|
||||
return api.UpdateCallContext(context.Background(), callID, params)
|
||||
}
|
||||
|
||||
// UpdateCallContext updates a Call with the given parameters.
|
||||
func (api *Client) UpdateCallContext(ctx context.Context, callID string, params UpdateCallParameters) (Call, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"id": {callID},
|
||||
}
|
||||
|
||||
if params.DesktopAppJoinURL != "" {
|
||||
values.Set("desktop_app_join_url", params.DesktopAppJoinURL)
|
||||
}
|
||||
if params.JoinURL != "" {
|
||||
values.Set("join_url", params.JoinURL)
|
||||
}
|
||||
if params.Title != "" {
|
||||
values.Set("title", params.Title)
|
||||
}
|
||||
|
||||
response := &callResponse{}
|
||||
if err := api.postMethod(ctx, "calls.update", values, response); err != nil {
|
||||
return Call{}, err
|
||||
}
|
||||
return response.Call, response.Err()
|
||||
}
|
||||
|
||||
// EndCall ends a Call.
|
||||
func (api *Client) EndCall(callID string, params EndCallParameters) error {
|
||||
return api.EndCallContext(context.Background(), callID, params)
|
||||
}
|
||||
|
||||
// EndCallContext ends a Call.
|
||||
func (api *Client) EndCallContext(ctx context.Context, callID string, params EndCallParameters) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"id": {callID},
|
||||
}
|
||||
|
||||
if params.Duration != 0 {
|
||||
values.Set("duration", strconv.FormatInt(int64(params.Duration.Seconds()), 10))
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, "calls.end", values, response); err != nil {
|
||||
return err
|
||||
}
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// CallAddParticipants adds users to a Call.
|
||||
func (api *Client) CallAddParticipants(callID string, participants []CallParticipant) error {
|
||||
return api.CallAddParticipantsContext(context.Background(), callID, participants)
|
||||
}
|
||||
|
||||
// CallAddParticipantsContext adds users to a Call.
|
||||
func (api *Client) CallAddParticipantsContext(ctx context.Context, callID string, participants []CallParticipant) error {
|
||||
return api.setCallParticipants(ctx, "calls.participants.add", callID, participants)
|
||||
}
|
||||
|
||||
// CallRemoveParticipants removes users from a Call.
|
||||
func (api *Client) CallRemoveParticipants(callID string, participants []CallParticipant) error {
|
||||
return api.CallRemoveParticipantsContext(context.Background(), callID, participants)
|
||||
}
|
||||
|
||||
// CallRemoveParticipantsContext removes users from a Call.
|
||||
func (api *Client) CallRemoveParticipantsContext(ctx context.Context, callID string, participants []CallParticipant) error {
|
||||
return api.setCallParticipants(ctx, "calls.participants.remove", callID, participants)
|
||||
}
|
||||
|
||||
func (api *Client) setCallParticipants(ctx context.Context, method, callID string, participants []CallParticipant) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"id": {callID},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(participants)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Set("users", string(data))
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, method, values, response); err != nil {
|
||||
return err
|
||||
}
|
||||
return response.Err()
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type CanvasDetails struct {
|
||||
CanvasID string `json:"canvas_id"`
|
||||
}
|
||||
|
||||
type DocumentContent struct {
|
||||
Type string `json:"type"`
|
||||
Markdown string `json:"markdown,omitempty"`
|
||||
}
|
||||
|
||||
type CanvasChange struct {
|
||||
Operation string `json:"operation"`
|
||||
SectionID string `json:"section_id,omitempty"`
|
||||
DocumentContent DocumentContent `json:"document_content"`
|
||||
}
|
||||
|
||||
type EditCanvasParams struct {
|
||||
CanvasID string `json:"canvas_id"`
|
||||
Changes []CanvasChange `json:"changes"`
|
||||
}
|
||||
|
||||
type SetCanvasAccessParams struct {
|
||||
CanvasID string `json:"canvas_id"`
|
||||
AccessLevel string `json:"access_level"`
|
||||
ChannelIDs []string `json:"channel_ids,omitempty"`
|
||||
UserIDs []string `json:"user_ids,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteCanvasAccessParams struct {
|
||||
CanvasID string `json:"canvas_id"`
|
||||
ChannelIDs []string `json:"channel_ids,omitempty"`
|
||||
UserIDs []string `json:"user_ids,omitempty"`
|
||||
}
|
||||
|
||||
type LookupCanvasSectionsCriteria struct {
|
||||
SectionTypes []string `json:"section_types,omitempty"`
|
||||
ContainsText string `json:"contains_text,omitempty"`
|
||||
}
|
||||
|
||||
type LookupCanvasSectionsParams struct {
|
||||
CanvasID string `json:"canvas_id"`
|
||||
Criteria LookupCanvasSectionsCriteria `json:"criteria"`
|
||||
}
|
||||
|
||||
type CanvasSection struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type LookupCanvasSectionsResponse struct {
|
||||
SlackResponse
|
||||
Sections []CanvasSection `json:"sections"`
|
||||
}
|
||||
|
||||
// CreateCanvas creates a new canvas.
|
||||
// For more details, see CreateCanvasContext documentation.
|
||||
func (api *Client) CreateCanvas(title string, documentContent DocumentContent) (string, error) {
|
||||
return api.CreateCanvasContext(context.Background(), title, documentContent)
|
||||
}
|
||||
|
||||
// CreateCanvasContext creates a new canvas with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.create
|
||||
func (api *Client) CreateCanvasContext(ctx context.Context, title string, documentContent DocumentContent) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if title != "" {
|
||||
values.Add("title", title)
|
||||
}
|
||||
if documentContent.Type != "" {
|
||||
documentContentJSON, err := json.Marshal(documentContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
values.Add("document_content", string(documentContentJSON))
|
||||
}
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
CanvasID string `json:"canvas_id"`
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "canvases.create", values, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.CanvasID, response.Err()
|
||||
}
|
||||
|
||||
// DeleteCanvas deletes an existing canvas.
|
||||
// For more details, see DeleteCanvasContext documentation.
|
||||
func (api *Client) DeleteCanvas(canvasID string) error {
|
||||
return api.DeleteCanvasContext(context.Background(), canvasID)
|
||||
}
|
||||
|
||||
// DeleteCanvasContext deletes an existing canvas with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.delete
|
||||
func (api *Client) DeleteCanvasContext(ctx context.Context, canvasID string) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"canvas_id": {canvasID},
|
||||
}
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "canvases.delete", values, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// EditCanvas edits an existing canvas.
|
||||
// For more details, see EditCanvasContext documentation.
|
||||
func (api *Client) EditCanvas(params EditCanvasParams) error {
|
||||
return api.EditCanvasContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// EditCanvasContext edits an existing canvas with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.edit
|
||||
func (api *Client) EditCanvasContext(ctx context.Context, params EditCanvasParams) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"canvas_id": {params.CanvasID},
|
||||
}
|
||||
|
||||
changesJSON, err := json.Marshal(params.Changes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Add("changes", string(changesJSON))
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err = api.postMethod(ctx, "canvases.edit", values, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// SetCanvasAccess sets the access level to a canvas for specified entities.
|
||||
// For more details, see SetCanvasAccessContext documentation.
|
||||
func (api *Client) SetCanvasAccess(params SetCanvasAccessParams) error {
|
||||
return api.SetCanvasAccessContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// SetCanvasAccessContext sets the access level to a canvas for specified entities with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.access.set
|
||||
func (api *Client) SetCanvasAccessContext(ctx context.Context, params SetCanvasAccessParams) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"canvas_id": {params.CanvasID},
|
||||
"access_level": {params.AccessLevel},
|
||||
}
|
||||
if len(params.ChannelIDs) > 0 {
|
||||
channelIDsJSON, err := json.Marshal(params.ChannelIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Add("channel_ids", string(channelIDsJSON))
|
||||
}
|
||||
if len(params.UserIDs) > 0 {
|
||||
userIDsJSON, err := json.Marshal(params.UserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Add("user_ids", string(userIDsJSON))
|
||||
}
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "canvases.access.set", values, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// DeleteCanvasAccess removes access to a canvas for specified entities.
|
||||
// For more details, see DeleteCanvasAccessContext documentation.
|
||||
func (api *Client) DeleteCanvasAccess(params DeleteCanvasAccessParams) error {
|
||||
return api.DeleteCanvasAccessContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// DeleteCanvasAccessContext removes access to a canvas for specified entities with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.access.delete
|
||||
func (api *Client) DeleteCanvasAccessContext(ctx context.Context, params DeleteCanvasAccessParams) error {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"canvas_id": {params.CanvasID},
|
||||
}
|
||||
if len(params.ChannelIDs) > 0 {
|
||||
channelIDsJSON, err := json.Marshal(params.ChannelIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Add("channel_ids", string(channelIDsJSON))
|
||||
}
|
||||
if len(params.UserIDs) > 0 {
|
||||
userIDsJSON, err := json.Marshal(params.UserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Add("user_ids", string(userIDsJSON))
|
||||
}
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "canvases.access.delete", values, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// LookupCanvasSections finds sections matching the provided criteria.
|
||||
// For more details, see LookupCanvasSectionsContext documentation.
|
||||
func (api *Client) LookupCanvasSections(params LookupCanvasSectionsParams) ([]CanvasSection, error) {
|
||||
return api.LookupCanvasSectionsContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// LookupCanvasSectionsContext finds sections matching the provided criteria with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/canvases.sections.lookup
|
||||
func (api *Client) LookupCanvasSectionsContext(ctx context.Context, params LookupCanvasSectionsParams) ([]CanvasSection, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"canvas_id": {params.CanvasID},
|
||||
}
|
||||
|
||||
criteriaJSON, err := json.Marshal(params.Criteria)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values.Add("criteria", string(criteriaJSON))
|
||||
|
||||
response := LookupCanvasSectionsResponse{}
|
||||
|
||||
err = api.postMethod(ctx, "canvases.sections.lookup", values, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.Sections, response.Err()
|
||||
}
|
||||
+1
@@ -377,6 +377,7 @@ func (t responseURLSender) BuildRequestContext(ctx context.Context) (*http.Reque
|
||||
req, err := jsonReq(ctx, t.endpoint, Msg{
|
||||
Text: t.values.Get("text"),
|
||||
Timestamp: t.values.Get("ts"),
|
||||
ThreadTimestamp: t.values.Get("thread_ts"),
|
||||
Attachments: t.attachments,
|
||||
Blocks: t.blocks,
|
||||
Metadata: t.metadata,
|
||||
|
||||
+78
-15
@@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -329,42 +330,71 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
|
||||
}
|
||||
|
||||
// InviteSharedEmailsToConversation invites users to a shared channels by email.
|
||||
// For more details, see InviteSharedEmailsToConversationContext documentation.
|
||||
// For more details, see InviteSharedToConversationContext documentation.
|
||||
func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(context.Background(), channelID, emails, nil)
|
||||
return api.InviteSharedToConversationContext(context.Background(), InviteSharedToConversationParams{
|
||||
ChannelID: channelID,
|
||||
Emails: emails,
|
||||
})
|
||||
}
|
||||
|
||||
// InviteSharedEmailsToConversationContext invites users to a shared channels by email using context.
|
||||
// For more details, see inviteSharedToConversationHelper documentation.
|
||||
// For more details, see InviteSharedToConversationContext documentation.
|
||||
func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(ctx, channelID, emails, nil)
|
||||
return api.InviteSharedToConversationContext(ctx, InviteSharedToConversationParams{
|
||||
ChannelID: channelID,
|
||||
Emails: emails,
|
||||
})
|
||||
}
|
||||
|
||||
// InviteSharedUserIDsToConversation invites users to a shared channels by user id.
|
||||
// For more details, see InviteSharedUserIDsToConversationContext documentation.
|
||||
// For more details, see InviteSharedToConversationContext documentation.
|
||||
func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(context.Background(), channelID, nil, userIDs)
|
||||
return api.InviteSharedToConversationContext(context.Background(), InviteSharedToConversationParams{
|
||||
ChannelID: channelID,
|
||||
UserIDs: userIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context.
|
||||
// For more details, see inviteSharedToConversationHelper documentation.
|
||||
// For more details, see InviteSharedToConversationContext documentation.
|
||||
func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(ctx, channelID, nil, userIDs)
|
||||
return api.InviteSharedToConversationContext(ctx, InviteSharedToConversationParams{
|
||||
ChannelID: channelID,
|
||||
UserIDs: userIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// inviteSharedToConversationHelper invites emails or userIDs to a channel with a custom context.
|
||||
// InviteSharedToConversationParams defines the parameters for the InviteSharedToConversation and InviteSharedToConversationContext functions.
|
||||
type InviteSharedToConversationParams struct {
|
||||
ChannelID string
|
||||
Emails []string
|
||||
UserIDs []string
|
||||
ExternalLimited *bool
|
||||
}
|
||||
|
||||
// InviteSharedToConversation invites emails or userIDs to a channel.
|
||||
// For more details, see InviteSharedToConversationContext documentation.
|
||||
func (api *Client) InviteSharedToConversation(params InviteSharedToConversationParams) (string, bool, error) {
|
||||
return api.InviteSharedToConversationContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// InviteSharedToConversationContext invites emails or userIDs to a channel with a custom context.
|
||||
// This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation.
|
||||
// It accepts either emails or userIDs, but not both.
|
||||
// Slack API docs: https://api.slack.com/methods/conversations.inviteShared
|
||||
func (api *Client) inviteSharedToConversationHelper(ctx context.Context, channelID string, emails []string, userIDs []string) (string, bool, error) {
|
||||
func (api *Client) InviteSharedToConversationContext(ctx context.Context, params InviteSharedToConversationParams) (string, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel": {channelID},
|
||||
"channel": {params.ChannelID},
|
||||
}
|
||||
if len(emails) > 0 {
|
||||
values.Add("emails", strings.Join(emails, ","))
|
||||
} else if len(userIDs) > 0 {
|
||||
values.Add("user_ids", strings.Join(userIDs, ","))
|
||||
if len(params.Emails) > 0 {
|
||||
values.Add("emails", strings.Join(params.Emails, ","))
|
||||
} else if len(params.UserIDs) > 0 {
|
||||
values.Add("user_ids", strings.Join(params.UserIDs, ","))
|
||||
}
|
||||
if params.ExternalLimited != nil {
|
||||
values.Add("external_limited", strconv.FormatBool(*params.ExternalLimited))
|
||||
}
|
||||
response := struct {
|
||||
SlackResponse
|
||||
@@ -796,3 +826,36 @@ func (api *Client) MarkConversationContext(ctx context.Context, channel, ts stri
|
||||
}
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// CreateChannelCanvas creates a new canvas in a channel.
|
||||
// For more details, see CreateChannelCanvasContext documentation.
|
||||
func (api *Client) CreateChannelCanvas(channel string, documentContent DocumentContent) (string, error) {
|
||||
return api.CreateChannelCanvasContext(context.Background(), channel, documentContent)
|
||||
}
|
||||
|
||||
// CreateChannelCanvasContext creates a new canvas in a channel with a custom context.
|
||||
// Slack API docs: https://api.slack.com/methods/conversations.canvases.create
|
||||
func (api *Client) CreateChannelCanvasContext(ctx context.Context, channel string, documentContent DocumentContent) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel_id": {channel},
|
||||
}
|
||||
if documentContent.Type != "" {
|
||||
documentContentJSON, err := json.Marshal(documentContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
values.Add("document_content", string(documentContentJSON))
|
||||
}
|
||||
|
||||
response := struct {
|
||||
SlackResponse
|
||||
CanvasID string `json:"canvas_id"`
|
||||
}{}
|
||||
err := api.postMethod(ctx, "conversations.canvases.create", values, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.CanvasID, response.Err()
|
||||
}
|
||||
|
||||
+4
-2
@@ -355,14 +355,16 @@ func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParamet
|
||||
}
|
||||
|
||||
// UploadFile uploads a file.
|
||||
// DEPRECATED: Use UploadFileV2 instead. This will stop functioning on March 11, 2025.
|
||||
//
|
||||
// Deprecated: Use [Client.UploadFileV2] instead. This will stop functioning on March 11, 2025.
|
||||
// For more details, see: https://api.slack.com/methods/files.upload#markdown
|
||||
func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
|
||||
return api.UploadFileContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// UploadFileContext uploads a file and setting a custom context.
|
||||
// DEPRECATED: Use UploadFileV2Context instead. This will stop functioning on March 11, 2025.
|
||||
//
|
||||
// Deprecated: Use [Client.UploadFileV2Context] instead. This will stop functioning on March 11, 2025.
|
||||
// For more details, see: https://api.slack.com/methods/files.upload#markdown
|
||||
func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
|
||||
// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type (
|
||||
FunctionCompleteSuccessRequest struct {
|
||||
FunctionExecutionID string `json:"function_execution_id"`
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
}
|
||||
|
||||
FunctionCompleteErrorRequest struct {
|
||||
FunctionExecutionID string `json:"function_execution_id"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
)
|
||||
|
||||
type FunctionCompleteSuccessRequestOption func(opt *FunctionCompleteSuccessRequest) error
|
||||
|
||||
func FunctionCompleteSuccessRequestOptionOutput(outputs map[string]string) FunctionCompleteSuccessRequestOption {
|
||||
return func(opt *FunctionCompleteSuccessRequest) error {
|
||||
if len(outputs) > 0 {
|
||||
opt.Outputs = outputs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// FunctionCompleteSuccess indicates function is completed
|
||||
func (api *Client) FunctionCompleteSuccess(functionExecutionId string, options ...FunctionCompleteSuccessRequestOption) error {
|
||||
return api.FunctionCompleteSuccessContext(context.Background(), functionExecutionId, options...)
|
||||
}
|
||||
|
||||
// FunctionCompleteSuccess indicates function is completed
|
||||
func (api *Client) FunctionCompleteSuccessContext(ctx context.Context, functionExecutionId string, options ...FunctionCompleteSuccessRequestOption) error {
|
||||
// More information: https://api.slack.com/methods/functions.completeSuccess
|
||||
r := &FunctionCompleteSuccessRequest{
|
||||
FunctionExecutionID: functionExecutionId,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(r)
|
||||
}
|
||||
|
||||
endpoint := api.endpoint + "functions.completeSuccess"
|
||||
jsonData, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FunctionCompleteError indicates function is completed with error
|
||||
func (api *Client) FunctionCompleteError(functionExecutionID string, errorMessage string) error {
|
||||
return api.FunctionCompleteErrorContext(context.Background(), functionExecutionID, errorMessage)
|
||||
}
|
||||
|
||||
// FunctionCompleteErrorContext indicates function is completed with error
|
||||
func (api *Client) FunctionCompleteErrorContext(ctx context.Context, functionExecutionID string, errorMessage string) error {
|
||||
// More information: https://api.slack.com/methods/functions.completeError
|
||||
r := FunctionCompleteErrorRequest{
|
||||
FunctionExecutionID: functionExecutionID,
|
||||
}
|
||||
r.Error = errorMessage
|
||||
|
||||
endpoint := api.endpoint + "functions.completeError"
|
||||
jsonData, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -305,7 +305,7 @@ func (ejp *extJSONParser) readValue(t bsontype.Type) (*extJSONValue, error) {
|
||||
}
|
||||
|
||||
// remove hyphens
|
||||
uuidNoHyphens := strings.Replace(uuid, "-", "", -1)
|
||||
uuidNoHyphens := strings.ReplaceAll(uuid, "-", "")
|
||||
if len(uuidNoHyphens) != 32 {
|
||||
return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding length and hyphens")
|
||||
}
|
||||
|
||||
+6
-7
@@ -628,13 +628,14 @@ func (ejvw *extJSONValueWriter) WriteArrayEnd() error {
|
||||
|
||||
func formatDouble(f float64) string {
|
||||
var s string
|
||||
if math.IsInf(f, 1) {
|
||||
switch {
|
||||
case math.IsInf(f, 1):
|
||||
s = "Infinity"
|
||||
} else if math.IsInf(f, -1) {
|
||||
case math.IsInf(f, -1):
|
||||
s = "-Infinity"
|
||||
} else if math.IsNaN(f) {
|
||||
case math.IsNaN(f):
|
||||
s = "NaN"
|
||||
} else {
|
||||
default:
|
||||
// Print exactly one decimalType place for integers; otherwise, print as many are necessary to
|
||||
// perfectly represent it.
|
||||
s = strconv.FormatFloat(f, 'G', -1, 64)
|
||||
@@ -739,9 +740,7 @@ func (ss sortableString) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
func (ss sortableString) Swap(i, j int) {
|
||||
oldI := ss[i]
|
||||
ss[i] = ss[j]
|
||||
ss[j] = oldI
|
||||
ss[i], ss[j] = ss[j], ss[i]
|
||||
}
|
||||
|
||||
func sortStringAlphebeticAscending(s string) string {
|
||||
|
||||
+21
-16
@@ -82,12 +82,13 @@ func (js *jsonScanner) nextToken() (*jsonToken, error) {
|
||||
return js.scanString()
|
||||
default:
|
||||
// check if it's a number
|
||||
if c == '-' || isDigit(c) {
|
||||
switch {
|
||||
case c == '-' || isDigit(c):
|
||||
return js.scanNumber(c)
|
||||
} else if c == 't' || c == 'f' || c == 'n' {
|
||||
case c == 't' || c == 'f' || c == 'n':
|
||||
// maybe a literal
|
||||
return js.scanLiteral(c)
|
||||
} else {
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid JSON input. Position: %d. Character: %c", js.pos-1, c)
|
||||
}
|
||||
}
|
||||
@@ -174,7 +175,7 @@ func getu4(s []byte) rune {
|
||||
for _, c := range s[:4] {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
c = c - '0'
|
||||
c -= '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
c = c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
@@ -325,13 +326,14 @@ func (js *jsonScanner) scanLiteral(first byte) (*jsonToken, error) {
|
||||
|
||||
c5, err := js.readNextByte()
|
||||
|
||||
if bytes.Equal([]byte("true"), lit) && (isValueTerminator(c5) || errors.Is(err, io.EOF)) {
|
||||
switch {
|
||||
case bytes.Equal([]byte("true"), lit) && (isValueTerminator(c5) || errors.Is(err, io.EOF)):
|
||||
js.pos = int(math.Max(0, float64(js.pos-1)))
|
||||
return &jsonToken{t: jttBool, v: true, p: p}, nil
|
||||
} else if bytes.Equal([]byte("null"), lit) && (isValueTerminator(c5) || errors.Is(err, io.EOF)) {
|
||||
case bytes.Equal([]byte("null"), lit) && (isValueTerminator(c5) || errors.Is(err, io.EOF)):
|
||||
js.pos = int(math.Max(0, float64(js.pos-1)))
|
||||
return &jsonToken{t: jttNull, v: nil, p: p}, nil
|
||||
} else if bytes.Equal([]byte("fals"), lit) {
|
||||
case bytes.Equal([]byte("fals"), lit):
|
||||
if c5 == 'e' {
|
||||
c5, err = js.readNextByte()
|
||||
|
||||
@@ -430,12 +432,13 @@ func (js *jsonScanner) scanNumber(first byte) (*jsonToken, error) {
|
||||
case '}', ']', ',':
|
||||
s = nssDone
|
||||
default:
|
||||
if isWhiteSpace(c) || errors.Is(err, io.EOF) {
|
||||
switch {
|
||||
case isWhiteSpace(c) || errors.Is(err, io.EOF):
|
||||
s = nssDone
|
||||
} else if isDigit(c) {
|
||||
case isDigit(c):
|
||||
s = nssSawIntegerDigits
|
||||
b.WriteByte(c)
|
||||
} else {
|
||||
default:
|
||||
s = nssInvalid
|
||||
}
|
||||
}
|
||||
@@ -455,12 +458,13 @@ func (js *jsonScanner) scanNumber(first byte) (*jsonToken, error) {
|
||||
case '}', ']', ',':
|
||||
s = nssDone
|
||||
default:
|
||||
if isWhiteSpace(c) || errors.Is(err, io.EOF) {
|
||||
switch {
|
||||
case isWhiteSpace(c) || errors.Is(err, io.EOF):
|
||||
s = nssDone
|
||||
} else if isDigit(c) {
|
||||
case isDigit(c):
|
||||
s = nssSawFractionDigits
|
||||
b.WriteByte(c)
|
||||
} else {
|
||||
default:
|
||||
s = nssInvalid
|
||||
}
|
||||
}
|
||||
@@ -490,12 +494,13 @@ func (js *jsonScanner) scanNumber(first byte) (*jsonToken, error) {
|
||||
case '}', ']', ',':
|
||||
s = nssDone
|
||||
default:
|
||||
if isWhiteSpace(c) || errors.Is(err, io.EOF) {
|
||||
switch {
|
||||
case isWhiteSpace(c) || errors.Is(err, io.EOF):
|
||||
s = nssDone
|
||||
} else if isDigit(c) {
|
||||
case isDigit(c):
|
||||
s = nssSawExponentDigits
|
||||
b.WriteByte(c)
|
||||
} else {
|
||||
default:
|
||||
s = nssInvalid
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -598,7 +598,7 @@ func (vw *valueWriter) writeLength() error {
|
||||
return errMaxDocumentSizeExceeded{size: int64(len(vw.buf))}
|
||||
}
|
||||
frame := &vw.stack[vw.frame]
|
||||
length = length - int(frame.start)
|
||||
length -= int(frame.start)
|
||||
start := frame.start
|
||||
|
||||
_ = vw.buf[start+3] // BCE
|
||||
|
||||
+7
-4
@@ -107,10 +107,13 @@
|
||||
// The name may be empty in order to specify options without overriding the default field name. The following options can
|
||||
// be used to configure behavior:
|
||||
//
|
||||
// 1. omitempty: If the omitempty struct tag is specified on a field, the field will be omitted from the marshaling if
|
||||
// the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array,
|
||||
// slice, map, or string.
|
||||
// NOTE: It is recommended that this tag be used for all slice and map fields.
|
||||
// 1. omitempty: If the "omitempty" struct tag is specified on a field, the field will not be marshaled if it is set to
|
||||
// an "empty" value. Numbers, booleans, and strings are considered empty if their value is equal to the zero value for
|
||||
// the type (i.e. 0 for numbers, false for booleans, and "" for strings). Slices, maps, and arrays are considered
|
||||
// empty if they are of length zero. Interfaces and pointers are considered empty if their value is nil. By default,
|
||||
// structs are only considered empty if the struct type implements [bsoncodec.Zeroer] and the IsZero
|
||||
// method returns true. Struct types that do not implement [bsoncodec.Zeroer] are never considered empty and will be
|
||||
// marshaled as embedded documents. NOTE: It is recommended that this tag be used for all slice and map fields.
|
||||
//
|
||||
// 2. minsize: If the minsize struct tag is specified on a field of type int64, uint, uint32, or uint64 and the value of
|
||||
// the field can fit in a signed int32, the field will be serialized as a BSON int32 rather than a BSON int64. For
|
||||
|
||||
+2
-4
@@ -70,7 +70,6 @@ func (d Decimal128) String() string {
|
||||
// Bits: 1*sign 2*ignored 14*exponent 111*significand.
|
||||
// Implicit 0b100 prefix in significand.
|
||||
exp = int(d.h >> 47 & (1<<14 - 1))
|
||||
//high = 4<<47 | d.h&(1<<47-1)
|
||||
// Spec says all of these values are out of range.
|
||||
high, low = 0, 0
|
||||
} else {
|
||||
@@ -152,13 +151,12 @@ func (d Decimal128) BigInt() (*big.Int, int, error) {
|
||||
// Bits: 1*sign 2*ignored 14*exponent 111*significand.
|
||||
// Implicit 0b100 prefix in significand.
|
||||
exp = int(high >> 47 & (1<<14 - 1))
|
||||
//high = 4<<47 | d.h&(1<<47-1)
|
||||
// Spec says all of these values are out of range.
|
||||
high, low = 0, 0
|
||||
} else {
|
||||
// Bits: 1*sign 14*exponent 113*significand
|
||||
exp = int(high >> 49 & (1<<14 - 1))
|
||||
high = high & (1<<49 - 1)
|
||||
high &= (1<<49 - 1)
|
||||
}
|
||||
exp += MinDecimal128Exp
|
||||
|
||||
@@ -352,7 +350,7 @@ var (
|
||||
|
||||
// ParseDecimal128FromBigInt attempts to parse the given significand and exponent into a valid Decimal128 value.
|
||||
func ParseDecimal128FromBigInt(bi *big.Int, exp int) (Decimal128, bool) {
|
||||
//copy
|
||||
// copy
|
||||
bi = new(big.Int).Set(bi)
|
||||
|
||||
q := new(big.Int)
|
||||
|
||||
+4
-3
@@ -186,7 +186,7 @@ func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (opera
|
||||
Database(bw.collection.db.name).Collection(bw.collection.name).
|
||||
Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).
|
||||
ServerAPI(bw.collection.client.serverAPI).Timeout(bw.collection.client.timeout).
|
||||
Logger(bw.collection.client.logger)
|
||||
Logger(bw.collection.client.logger).Authenticator(bw.collection.client.authenticator)
|
||||
if bw.comment != nil {
|
||||
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
|
||||
if err != nil {
|
||||
@@ -256,7 +256,7 @@ func (bw *bulkWrite) runDelete(ctx context.Context, batch bulkWriteBatch) (opera
|
||||
Database(bw.collection.db.name).Collection(bw.collection.name).
|
||||
Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
|
||||
ServerAPI(bw.collection.client.serverAPI).Timeout(bw.collection.client.timeout).
|
||||
Logger(bw.collection.client.logger)
|
||||
Logger(bw.collection.client.logger).Authenticator(bw.collection.client.authenticator)
|
||||
if bw.comment != nil {
|
||||
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
|
||||
if err != nil {
|
||||
@@ -387,7 +387,8 @@ func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (opera
|
||||
Database(bw.collection.db.name).Collection(bw.collection.name).
|
||||
Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
|
||||
ArrayFilters(hasArrayFilters).ServerAPI(bw.collection.client.serverAPI).
|
||||
Timeout(bw.collection.client.timeout).Logger(bw.collection.client.logger)
|
||||
Timeout(bw.collection.client.timeout).Logger(bw.collection.client.logger).
|
||||
Authenticator(bw.collection.client.authenticator)
|
||||
if bw.comment != nil {
|
||||
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
|
||||
if err != nil {
|
||||
|
||||
+2
-1
@@ -137,7 +137,8 @@ func newChangeStream(ctx context.Context, config changeStreamConfig, pipeline in
|
||||
ReadPreference(config.readPreference).ReadConcern(config.readConcern).
|
||||
Deployment(cs.client.deployment).ClusterClock(cs.client.clock).
|
||||
CommandMonitor(cs.client.monitor).Session(cs.sess).ServerSelector(cs.selector).Retry(driver.RetryNone).
|
||||
ServerAPI(cs.client.serverAPI).Crypt(config.crypt).Timeout(cs.client.timeout)
|
||||
ServerAPI(cs.client.serverAPI).Crypt(config.crypt).Timeout(cs.client.timeout).
|
||||
Authenticator(cs.client.authenticator)
|
||||
|
||||
if cs.options.Collation != nil {
|
||||
cs.aggregate.Collation(bsoncore.Document(cs.options.Collation.ToDocument()))
|
||||
|
||||
+16
-2
@@ -26,6 +26,7 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo/writeconcern"
|
||||
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/auth"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt"
|
||||
mcopts "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt/options"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
|
||||
@@ -79,6 +80,7 @@ type Client struct {
|
||||
metadataClientFLE *Client
|
||||
internalClientFLE *Client
|
||||
encryptedFieldsMap map[string]interface{}
|
||||
authenticator driver.Authenticator
|
||||
}
|
||||
|
||||
// Connect creates a new Client and then initializes it using the Connect method. This is equivalent to calling
|
||||
@@ -209,10 +211,22 @@ func NewClient(opts ...*options.ClientOptions) (*Client, error) {
|
||||
clientOpt.SetMaxPoolSize(defaultMaxPoolSize)
|
||||
}
|
||||
|
||||
cfg, err := topology.NewConfig(clientOpt, client.clock)
|
||||
if clientOpt.Auth != nil {
|
||||
client.authenticator, err = auth.CreateAuthenticator(
|
||||
clientOpt.Auth.AuthMechanism,
|
||||
topology.ConvertCreds(clientOpt.Auth),
|
||||
clientOpt.HTTPClient,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating authenticator: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := topology.NewConfigWithAuthenticator(clientOpt, client.clock, client.authenticator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.serverAPI = topology.ServerAPIFromServerOptions(cfg.ServerOpts)
|
||||
|
||||
if client.deployment == nil {
|
||||
@@ -690,7 +704,7 @@ func (c *Client) ListDatabases(ctx context.Context, filter interface{}, opts ...
|
||||
op := operation.NewListDatabases(filterDoc).
|
||||
Session(sess).ReadPreference(c.readPreference).CommandMonitor(c.monitor).
|
||||
ServerSelector(selector).ClusterClock(c.clock).Database("admin").Deployment(c.deployment).Crypt(c.cryptFLE).
|
||||
ServerAPI(c.serverAPI).Timeout(c.timeout)
|
||||
ServerAPI(c.serverAPI).Timeout(c.timeout).Authenticator(c.authenticator)
|
||||
|
||||
if ldo.NameOnly != nil {
|
||||
op = op.NameOnly(*ldo.NameOnly)
|
||||
|
||||
+7
-3
@@ -204,7 +204,12 @@ func transformExplicitEncryptionOptions(opts ...*options.EncryptOptions) *mcopts
|
||||
if eo.RangeOptions.Precision != nil {
|
||||
transformedRange.Precision = eo.RangeOptions.Precision
|
||||
}
|
||||
transformedRange.Sparsity = eo.RangeOptions.Sparsity
|
||||
if eo.RangeOptions.Sparsity != nil {
|
||||
transformedRange.Sparsity = eo.RangeOptions.Sparsity
|
||||
}
|
||||
if eo.RangeOptions.TrimFactor != nil {
|
||||
transformedRange.TrimFactor = eo.RangeOptions.TrimFactor
|
||||
}
|
||||
transformed.SetRangeOptions(transformedRange)
|
||||
}
|
||||
return transformed
|
||||
@@ -230,8 +235,7 @@ func (ce *ClientEncryption) Encrypt(ctx context.Context, val bson.RawValue,
|
||||
// 2. An Aggregate Expression of this form:
|
||||
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
|
||||
// $gt may also be $gte. $lt may also be $lte.
|
||||
// Only supported for queryType "rangePreview"
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
// Only supported for queryType "range"
|
||||
func (ce *ClientEncryption) EncryptExpression(ctx context.Context, expr interface{}, result interface{}, opts ...*options.EncryptOptions) error {
|
||||
transformed := transformExplicitEncryptionOptions(opts...)
|
||||
|
||||
|
||||
+17
-12
@@ -291,7 +291,8 @@ func (coll *Collection) insert(ctx context.Context, documents []interface{},
|
||||
ServerSelector(selector).ClusterClock(coll.client.clock).
|
||||
Database(coll.db.name).Collection(coll.name).
|
||||
Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).Ordered(true).
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Logger(coll.client.logger)
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Logger(coll.client.logger).
|
||||
Authenticator(coll.client.authenticator)
|
||||
imo := options.MergeInsertManyOptions(opts...)
|
||||
if imo.BypassDocumentValidation != nil && *imo.BypassDocumentValidation {
|
||||
op = op.BypassDocumentValidation(*imo.BypassDocumentValidation)
|
||||
@@ -471,7 +472,8 @@ func (coll *Collection) delete(ctx context.Context, filter interface{}, deleteOn
|
||||
ServerSelector(selector).ClusterClock(coll.client.clock).
|
||||
Database(coll.db.name).Collection(coll.name).
|
||||
Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).Ordered(true).
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Logger(coll.client.logger)
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Logger(coll.client.logger).
|
||||
Authenticator(coll.client.authenticator)
|
||||
if do.Comment != nil {
|
||||
comment, err := marshalValue(do.Comment, coll.bsonOpts, coll.registry)
|
||||
if err != nil {
|
||||
@@ -588,7 +590,7 @@ func (coll *Collection) updateOrReplace(ctx context.Context, filter bsoncore.Doc
|
||||
Database(coll.db.name).Collection(coll.name).
|
||||
Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).Hint(uo.Hint != nil).
|
||||
ArrayFilters(uo.ArrayFilters != nil).Ordered(true).ServerAPI(coll.client.serverAPI).
|
||||
Timeout(coll.client.timeout).Logger(coll.client.logger)
|
||||
Timeout(coll.client.timeout).Logger(coll.client.logger).Authenticator(coll.client.authenticator)
|
||||
if uo.Let != nil {
|
||||
let, err := marshal(uo.Let, coll.bsonOpts, coll.registry)
|
||||
if err != nil {
|
||||
@@ -861,7 +863,8 @@ func aggregate(a aggregateParams) (cur *Cursor, err error) {
|
||||
ServerAPI(a.client.serverAPI).
|
||||
HasOutputStage(hasOutputStage).
|
||||
Timeout(a.client.timeout).
|
||||
MaxTime(ao.MaxTime)
|
||||
MaxTime(ao.MaxTime).
|
||||
Authenticator(a.client.authenticator)
|
||||
|
||||
// Omit "maxTimeMS" from operations that return a user-managed cursor to
|
||||
// prevent confusing "cursor not found" errors. To maintain existing
|
||||
@@ -992,7 +995,7 @@ func (coll *Collection) CountDocuments(ctx context.Context, filter interface{},
|
||||
op := operation.NewAggregate(pipelineArr).Session(sess).ReadConcern(rc).ReadPreference(coll.readPreference).
|
||||
CommandMonitor(coll.client.monitor).ServerSelector(selector).ClusterClock(coll.client.clock).Database(coll.db.name).
|
||||
Collection(coll.name).Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
|
||||
Timeout(coll.client.timeout).MaxTime(countOpts.MaxTime)
|
||||
Timeout(coll.client.timeout).MaxTime(countOpts.MaxTime).Authenticator(coll.client.authenticator)
|
||||
if countOpts.Collation != nil {
|
||||
op.Collation(bsoncore.Document(countOpts.Collation.ToDocument()))
|
||||
}
|
||||
@@ -1077,7 +1080,7 @@ func (coll *Collection) EstimatedDocumentCount(ctx context.Context,
|
||||
Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
|
||||
Deployment(coll.client.deployment).ReadConcern(rc).ReadPreference(coll.readPreference).
|
||||
ServerSelector(selector).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
|
||||
Timeout(coll.client.timeout).MaxTime(co.MaxTime)
|
||||
Timeout(coll.client.timeout).MaxTime(co.MaxTime).Authenticator(coll.client.authenticator)
|
||||
|
||||
if co.Comment != nil {
|
||||
comment, err := marshalValue(co.Comment, coll.bsonOpts, coll.registry)
|
||||
@@ -1144,7 +1147,7 @@ func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter i
|
||||
Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
|
||||
Deployment(coll.client.deployment).ReadConcern(rc).ReadPreference(coll.readPreference).
|
||||
ServerSelector(selector).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
|
||||
Timeout(coll.client.timeout).MaxTime(option.MaxTime)
|
||||
Timeout(coll.client.timeout).MaxTime(option.MaxTime).Authenticator(coll.client.authenticator)
|
||||
|
||||
if option.Collation != nil {
|
||||
op.Collation(bsoncore.Document(option.Collation.ToDocument()))
|
||||
@@ -1257,7 +1260,7 @@ func (coll *Collection) find(
|
||||
ClusterClock(coll.client.clock).Database(coll.db.name).Collection(coll.name).
|
||||
Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
|
||||
Timeout(coll.client.timeout).MaxTime(fo.MaxTime).Logger(coll.client.logger).
|
||||
OmitCSOTMaxTimeMS(omitCSOTMaxTimeMS)
|
||||
OmitCSOTMaxTimeMS(omitCSOTMaxTimeMS).Authenticator(coll.client.authenticator)
|
||||
|
||||
cursorOpts := coll.client.createBaseCursorOptions()
|
||||
|
||||
@@ -1521,7 +1524,7 @@ func (coll *Collection) FindOneAndDelete(ctx context.Context, filter interface{}
|
||||
}
|
||||
fod := options.MergeFindOneAndDeleteOptions(opts...)
|
||||
op := operation.NewFindAndModify(f).Remove(true).ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).
|
||||
MaxTime(fod.MaxTime)
|
||||
MaxTime(fod.MaxTime).Authenticator(coll.client.authenticator)
|
||||
if fod.Collation != nil {
|
||||
op = op.Collation(bsoncore.Document(fod.Collation.ToDocument()))
|
||||
}
|
||||
@@ -1601,7 +1604,8 @@ func (coll *Collection) FindOneAndReplace(ctx context.Context, filter interface{
|
||||
|
||||
fo := options.MergeFindOneAndReplaceOptions(opts...)
|
||||
op := operation.NewFindAndModify(f).Update(bsoncore.Value{Type: bsontype.EmbeddedDocument, Data: r}).
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).MaxTime(fo.MaxTime)
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).MaxTime(fo.MaxTime).Authenticator(coll.client.authenticator)
|
||||
|
||||
if fo.BypassDocumentValidation != nil && *fo.BypassDocumentValidation {
|
||||
op = op.BypassDocumentValidation(*fo.BypassDocumentValidation)
|
||||
}
|
||||
@@ -1688,7 +1692,7 @@ func (coll *Collection) FindOneAndUpdate(ctx context.Context, filter interface{}
|
||||
|
||||
fo := options.MergeFindOneAndUpdateOptions(opts...)
|
||||
op := operation.NewFindAndModify(f).ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).
|
||||
MaxTime(fo.MaxTime)
|
||||
MaxTime(fo.MaxTime).Authenticator(coll.client.authenticator)
|
||||
|
||||
u, err := marshalUpdateValue(update, coll.bsonOpts, coll.registry, true)
|
||||
if err != nil {
|
||||
@@ -1894,7 +1898,8 @@ func (coll *Collection) drop(ctx context.Context) error {
|
||||
ServerSelector(selector).ClusterClock(coll.client.clock).
|
||||
Database(coll.db.name).Collection(coll.name).
|
||||
Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout)
|
||||
ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).
|
||||
Authenticator(coll.client.authenticator)
|
||||
err = op.Execute(ctx)
|
||||
|
||||
// ignore namespace not found errors
|
||||
|
||||
+3
-2
@@ -286,8 +286,9 @@ func (c *Cursor) Close(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice.
|
||||
// The slice pointed to by results will be completely overwritten. This method will close the cursor after retrieving
|
||||
// all documents. If the cursor has been iterated, any previously iterated documents will not be included in results.
|
||||
// The slice pointed to by results will be completely overwritten. A nil slice pointer will not be modified if the cursor
|
||||
// has been closed, exhausted, or is empty. This method will close the cursor after retrieving all documents. If the
|
||||
// cursor has been iterated, any previously iterated documents will not be included in results.
|
||||
//
|
||||
// This method requires driver version >= 1.1.0.
|
||||
func (c *Cursor) All(ctx context.Context, results interface{}) error {
|
||||
|
||||
+6
-5
@@ -189,7 +189,7 @@ func (db *Database) processRunCommand(ctx context.Context, cmd interface{},
|
||||
ServerSelector(readSelect).ClusterClock(db.client.clock).
|
||||
Database(db.name).Deployment(db.client.deployment).
|
||||
Crypt(db.client.cryptFLE).ReadPreference(ro.ReadPreference).ServerAPI(db.client.serverAPI).
|
||||
Timeout(db.client.timeout).Logger(db.client.logger), sess, nil
|
||||
Timeout(db.client.timeout).Logger(db.client.logger).Authenticator(db.client.authenticator), sess, nil
|
||||
}
|
||||
|
||||
// RunCommand executes the given command against the database.
|
||||
@@ -308,7 +308,7 @@ func (db *Database) Drop(ctx context.Context) error {
|
||||
Session(sess).WriteConcern(wc).CommandMonitor(db.client.monitor).
|
||||
ServerSelector(selector).ClusterClock(db.client.clock).
|
||||
Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
|
||||
ServerAPI(db.client.serverAPI)
|
||||
ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
|
||||
|
||||
err = op.Execute(ctx)
|
||||
|
||||
@@ -402,7 +402,7 @@ func (db *Database) ListCollections(ctx context.Context, filter interface{}, opt
|
||||
Session(sess).ReadPreference(db.readPreference).CommandMonitor(db.client.monitor).
|
||||
ServerSelector(selector).ClusterClock(db.client.clock).
|
||||
Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
|
||||
ServerAPI(db.client.serverAPI).Timeout(db.client.timeout)
|
||||
ServerAPI(db.client.serverAPI).Timeout(db.client.timeout).Authenticator(db.client.authenticator)
|
||||
|
||||
cursorOpts := db.client.createBaseCursorOptions()
|
||||
|
||||
@@ -679,7 +679,7 @@ func (db *Database) createCollection(ctx context.Context, name string, opts ...*
|
||||
|
||||
func (db *Database) createCollectionOperation(name string, opts ...*options.CreateCollectionOptions) (*operation.Create, error) {
|
||||
cco := options.MergeCreateCollectionOptions(opts...)
|
||||
op := operation.NewCreate(name).ServerAPI(db.client.serverAPI)
|
||||
op := operation.NewCreate(name).ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
|
||||
|
||||
if cco.Capped != nil {
|
||||
op.Capped(*cco.Capped)
|
||||
@@ -805,7 +805,8 @@ func (db *Database) CreateView(ctx context.Context, viewName, viewOn string, pip
|
||||
op := operation.NewCreate(viewName).
|
||||
ViewOn(viewOn).
|
||||
Pipeline(pipelineArray).
|
||||
ServerAPI(db.client.serverAPI)
|
||||
ServerAPI(db.client.serverAPI).
|
||||
Authenticator(db.client.authenticator)
|
||||
cvo := options.MergeCreateViewOptions(opts...)
|
||||
if cvo.Collation != nil {
|
||||
op.Collation(bsoncore.Document(cvo.Collation.ToDocument()))
|
||||
|
||||
+10
-8
@@ -316,21 +316,23 @@ func NewServer(addr address.Address, response bson.Raw) Server {
|
||||
|
||||
desc.Kind = Standalone
|
||||
|
||||
if isReplicaSet {
|
||||
switch {
|
||||
case isReplicaSet:
|
||||
desc.Kind = RSGhost
|
||||
} else if desc.SetName != "" {
|
||||
if isWritablePrimary {
|
||||
case desc.SetName != "":
|
||||
switch {
|
||||
case isWritablePrimary:
|
||||
desc.Kind = RSPrimary
|
||||
} else if hidden {
|
||||
case hidden:
|
||||
desc.Kind = RSMember
|
||||
} else if secondary {
|
||||
case secondary:
|
||||
desc.Kind = RSSecondary
|
||||
} else if arbiterOnly {
|
||||
case arbiterOnly:
|
||||
desc.Kind = RSArbiter
|
||||
} else {
|
||||
default:
|
||||
desc.Kind = RSMember
|
||||
}
|
||||
} else if msg == "isdbgrid" {
|
||||
case msg == "isdbgrid":
|
||||
desc.Kind = Mongos
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -534,7 +534,7 @@ func numericalIndexDocsEqual(expected, actual bsoncore.Document) (bool, error) {
|
||||
actualInt, actualOK := actualVal.AsInt64OK()
|
||||
expectedInt, expectedOK := expectedVal.AsInt64OK()
|
||||
|
||||
//GridFS indexes always have numeric values
|
||||
// GridFS indexes always have numeric values
|
||||
if !actualOK || !expectedOK {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
+20
-6
@@ -94,7 +94,7 @@ func (iv IndexView) List(ctx context.Context, opts ...*options.ListIndexesOption
|
||||
ServerSelector(selector).ClusterClock(iv.coll.client.clock).
|
||||
Database(iv.coll.db.name).Collection(iv.coll.name).
|
||||
Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
|
||||
Timeout(iv.coll.client.timeout)
|
||||
Timeout(iv.coll.client.timeout).Authenticator(iv.coll.client.authenticator)
|
||||
|
||||
cursorOpts := iv.coll.client.createBaseCursorOptions()
|
||||
|
||||
@@ -262,7 +262,7 @@ func (iv IndexView) CreateMany(ctx context.Context, models []IndexModel, opts ..
|
||||
Session(sess).WriteConcern(wc).ClusterClock(iv.coll.client.clock).
|
||||
Database(iv.coll.db.name).Collection(iv.coll.name).CommandMonitor(iv.coll.client.monitor).
|
||||
Deployment(iv.coll.client.deployment).ServerSelector(selector).ServerAPI(iv.coll.client.serverAPI).
|
||||
Timeout(iv.coll.client.timeout).MaxTime(option.MaxTime)
|
||||
Timeout(iv.coll.client.timeout).MaxTime(option.MaxTime).Authenticator(iv.coll.client.authenticator)
|
||||
if option.CommitQuorum != nil {
|
||||
commitQuorum, err := marshalValue(option.CommitQuorum, iv.coll.bsonOpts, iv.coll.registry)
|
||||
if err != nil {
|
||||
@@ -367,7 +367,7 @@ func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsoncore.Docum
|
||||
return optsDoc, nil
|
||||
}
|
||||
|
||||
func (iv IndexView) drop(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
|
||||
func (iv IndexView) drop(ctx context.Context, index any, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
@@ -397,12 +397,12 @@ func (iv IndexView) drop(ctx context.Context, name string, opts ...*options.Drop
|
||||
|
||||
// TODO(GODRIVER-3038): This operation should pass CSE to the DropIndexes
|
||||
// Crypt setter to be applied to the operation.
|
||||
op := operation.NewDropIndexes(name).
|
||||
Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
|
||||
op := operation.NewDropIndexes(index).Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
|
||||
ServerSelector(selector).ClusterClock(iv.coll.client.clock).
|
||||
Database(iv.coll.db.name).Collection(iv.coll.name).
|
||||
Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
|
||||
Timeout(iv.coll.client.timeout).MaxTime(dio.MaxTime)
|
||||
Timeout(iv.coll.client.timeout).MaxTime(dio.MaxTime).
|
||||
Authenticator(iv.coll.client.authenticator)
|
||||
|
||||
err = op.Execute(ctx)
|
||||
if err != nil {
|
||||
@@ -435,6 +435,20 @@ func (iv IndexView) DropOne(ctx context.Context, name string, opts ...*options.D
|
||||
return iv.drop(ctx, name, opts...)
|
||||
}
|
||||
|
||||
// DropOneWithKey drops a collection index by key using the dropIndexes operation. If the operation succeeds, this returns
|
||||
// a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the number of
|
||||
// indexes that existed prior to the drop.
|
||||
//
|
||||
// This function is useful to drop an index using its key specification instead of its name.
|
||||
func (iv IndexView) DropOneWithKey(ctx context.Context, keySpecDocument interface{}, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
|
||||
doc, err := marshal(keySpecDocument, iv.coll.bsonOpts, iv.coll.registry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return iv.drop(ctx, doc, opts...)
|
||||
}
|
||||
|
||||
// DropAll executes a dropIndexes operation to drop all indexes on the collection. If the operation succeeds, this
|
||||
// returns a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the
|
||||
// number of indexes that existed prior to the drop.
|
||||
|
||||
+67
-4
@@ -30,6 +30,7 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo/writeconcern"
|
||||
"go.mongodb.org/mongo-driver/tag"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/auth"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage"
|
||||
)
|
||||
@@ -89,9 +90,9 @@ type ContextDialer interface {
|
||||
// The SERVICE_HOST and CANONICALIZE_HOST_NAME properties must not be used at the same time on Linux and Darwin
|
||||
// systems.
|
||||
//
|
||||
// AuthSource: the name of the database to use for authentication. This defaults to "$external" for MONGODB-X509,
|
||||
// GSSAPI, and PLAIN and "admin" for all other mechanisms. This can also be set through the "authSource" URI option
|
||||
// (e.g. "authSource=otherDb").
|
||||
// AuthSource: the name of the database to use for authentication. This defaults to "$external" for MONGODB-AWS,
|
||||
// MONGODB-OIDC, MONGODB-X509, GSSAPI, and PLAIN. It defaults to "admin" for all other auth mechanisms. This can
|
||||
// also be set through the "authSource" URI option (e.g. "authSource=otherDb").
|
||||
//
|
||||
// Username: the username for authentication. This can also be set through the URI as a username:password pair before
|
||||
// the first @ character. For example, a URI for user "user", password "pwd", and host "localhost:27017" would be
|
||||
@@ -111,6 +112,34 @@ type Credential struct {
|
||||
Username string
|
||||
Password string
|
||||
PasswordSet bool
|
||||
OIDCMachineCallback OIDCCallback
|
||||
OIDCHumanCallback OIDCCallback
|
||||
}
|
||||
|
||||
// OIDCCallback is the type for both Human and Machine Callback flows.
|
||||
// RefreshToken will always be nil in the OIDCArgs for the Machine flow.
|
||||
type OIDCCallback func(context.Context, *OIDCArgs) (*OIDCCredential, error)
|
||||
|
||||
// OIDCArgs contains the arguments for the OIDC callback.
|
||||
type OIDCArgs struct {
|
||||
Version int
|
||||
IDPInfo *IDPInfo
|
||||
RefreshToken *string
|
||||
}
|
||||
|
||||
// OIDCCredential contains the access token and refresh token.
|
||||
type OIDCCredential struct {
|
||||
AccessToken string
|
||||
ExpiresAt *time.Time
|
||||
RefreshToken *string
|
||||
}
|
||||
|
||||
// IDPInfo contains the information needed to perform OIDC authentication with
|
||||
// an Identity Provider.
|
||||
type IDPInfo struct {
|
||||
Issuer string
|
||||
ClientID string
|
||||
RequestScopes []string
|
||||
}
|
||||
|
||||
// BSONOptions are optional BSON marshaling and unmarshaling behaviors.
|
||||
@@ -332,6 +361,37 @@ func (c *ClientOptions) validate() error {
|
||||
return fmt.Errorf("invalid server monitoring mode: %q", *mode)
|
||||
}
|
||||
|
||||
// OIDC Validation
|
||||
if c.Auth != nil && c.Auth.AuthMechanism == auth.MongoDBOIDC {
|
||||
if c.Auth.Password != "" {
|
||||
return fmt.Errorf("password must not be set for the %s auth mechanism", auth.MongoDBOIDC)
|
||||
}
|
||||
if c.Auth.OIDCMachineCallback != nil && c.Auth.OIDCHumanCallback != nil {
|
||||
return fmt.Errorf("cannot set both OIDCMachineCallback and OIDCHumanCallback, only one may be specified")
|
||||
}
|
||||
if c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.AllowedHostsProp] != "" {
|
||||
return fmt.Errorf("Cannot specify ALLOWED_HOSTS without an OIDCHumanCallback")
|
||||
}
|
||||
if env, ok := c.Auth.AuthMechanismProperties[auth.EnvironmentProp]; ok {
|
||||
switch env {
|
||||
case auth.GCPEnvironmentValue, auth.AzureEnvironmentValue:
|
||||
if c.Auth.OIDCMachineCallback != nil {
|
||||
return fmt.Errorf("OIDCMachineCallback cannot be specified with the %s %q", env, auth.EnvironmentProp)
|
||||
}
|
||||
if c.Auth.OIDCHumanCallback != nil {
|
||||
return fmt.Errorf("OIDCHumanCallback cannot be specified with the %s %q", env, auth.EnvironmentProp)
|
||||
}
|
||||
if c.Auth.AuthMechanismProperties[auth.ResourceProp] == "" {
|
||||
return fmt.Errorf("%q must be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp)
|
||||
}
|
||||
default:
|
||||
if c.Auth.AuthMechanismProperties[auth.ResourceProp] != "" {
|
||||
return fmt.Errorf("%q must not be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1258,7 +1318,10 @@ func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
|
||||
}
|
||||
}
|
||||
var encoded bytes.Buffer
|
||||
pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: keyBytes})
|
||||
err = pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: keyBytes})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error encoding private key as PEM: %w", err)
|
||||
}
|
||||
keyBlock := encoded.Bytes()
|
||||
keyBlocks = append(keyBlocks, keyBlock)
|
||||
start = len(data) - len(remaining)
|
||||
|
||||
+15
-13
@@ -17,13 +17,13 @@ const (
|
||||
QueryTypeEquality string = "equality"
|
||||
)
|
||||
|
||||
// RangeOptions specifies index options for a Queryable Encryption field supporting "rangePreview" queries.
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
// RangeOptions specifies index options for a Queryable Encryption field supporting "range" queries.
|
||||
type RangeOptions struct {
|
||||
Min *bson.RawValue
|
||||
Max *bson.RawValue
|
||||
Sparsity int64
|
||||
Precision *int32
|
||||
Min *bson.RawValue
|
||||
Max *bson.RawValue
|
||||
Sparsity *int64
|
||||
TrimFactor *int32
|
||||
Precision *int32
|
||||
}
|
||||
|
||||
// EncryptOptions represents options to explicitly encrypt a value.
|
||||
@@ -58,6 +58,7 @@ func (e *EncryptOptions) SetKeyAltName(keyAltName string) *EncryptOptions {
|
||||
// - AEAD_AES_256_CBC_HMAC_SHA_512-Random
|
||||
// - Indexed
|
||||
// - Unindexed
|
||||
// - Range
|
||||
// This is required.
|
||||
// Indexed and Unindexed are used for Queryable Encryption.
|
||||
func (e *EncryptOptions) SetAlgorithm(algorithm string) *EncryptOptions {
|
||||
@@ -81,36 +82,37 @@ func (e *EncryptOptions) SetContentionFactor(contentionFactor int64) *EncryptOpt
|
||||
return e
|
||||
}
|
||||
|
||||
// SetRangeOptions specifies the options to use for explicit encryption with range. It is only valid to set if algorithm is "rangePreview".
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
// SetRangeOptions specifies the options to use for explicit encryption with range. It is only valid to set if algorithm is "Range".
|
||||
func (e *EncryptOptions) SetRangeOptions(ro RangeOptions) *EncryptOptions {
|
||||
e.RangeOptions = &ro
|
||||
return e
|
||||
}
|
||||
|
||||
// SetMin sets the range index minimum value.
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
func (ro *RangeOptions) SetMin(min bson.RawValue) *RangeOptions {
|
||||
ro.Min = &min
|
||||
return ro
|
||||
}
|
||||
|
||||
// SetMax sets the range index maximum value.
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
func (ro *RangeOptions) SetMax(max bson.RawValue) *RangeOptions {
|
||||
ro.Max = &max
|
||||
return ro
|
||||
}
|
||||
|
||||
// SetSparsity sets the range index sparsity.
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
func (ro *RangeOptions) SetSparsity(sparsity int64) *RangeOptions {
|
||||
ro.Sparsity = sparsity
|
||||
ro.Sparsity = &sparsity
|
||||
return ro
|
||||
}
|
||||
|
||||
// SetTrimFactor sets the range index trim factor.
|
||||
func (ro *RangeOptions) SetTrimFactor(trimFactor int32) *RangeOptions {
|
||||
ro.TrimFactor = &trimFactor
|
||||
return ro
|
||||
}
|
||||
|
||||
// SetPrecision sets the range index precision.
|
||||
// Beta: The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
|
||||
func (ro *RangeOptions) SetPrecision(precision int32) *RangeOptions {
|
||||
ro.Precision = &precision
|
||||
return ro
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user