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:
dependabot[bot]
2025-01-02 14:02:42 -06:00
committed by Umputun
parent 81f70aa287
commit 2e00002413
241 changed files with 19923 additions and 933 deletions
+8 -7
View File
@@ -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
)
+46 -17
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -3,6 +3,7 @@
[![build workflow](https://github.com/redis/go-redis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/go-redis/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/redis/go-redis/v9)](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
[![Documentation](https://img.shields.io/badge/redis-documentation-informational)](https://redis.uptrace.dev/)
[![codecov](https://codecov.io/github/redis/go-redis/graph/badge.svg?token=tsrCZKuSSw)](https://codecov.io/github/redis/go-redis)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
}
}
File diff suppressed because it is too large Load Diff
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -2,5 +2,5 @@ package redis
// Version is the current release version.
func Version() string {
return "9.4.0"
return "9.7.0"
}
+1
View File
@@ -18,6 +18,7 @@ const (
MBTInput MessageBlockType = "input"
MBTHeader MessageBlockType = "header"
MBTRichText MessageBlockType = "rich_text"
MBTCall MessageBlockType = "call"
MBTVideo MessageBlockType = "video"
)
+23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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