mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
build(deps): bump the dependencies group across 2 directories with 12 updates
Bumps the dependencies group with 9 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/huandu/go-sqlbuilder](https://github.com/huandu/go-sqlbuilder) | `1.36.1` | `1.37.0` | | [github.com/lestrrat-go/jwx/v3](https://github.com/lestrrat-go/jwx) | `3.0.10` | `3.0.11` | | [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.23.0` | `1.23.2` | | [github.com/spf13/cobra](https://github.com/spf13/cobra) | `1.9.1` | `1.10.1` | | [github.com/spf13/viper](https://github.com/spf13/viper) | `1.20.1` | `1.21.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.43.0` | `0.44.0` | | [golang.org/x/time](https://github.com/golang/time) | `0.12.0` | `0.13.0` | | [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.75.0` | `1.75.1` | | google.golang.org/protobuf | `1.36.8` | `1.36.9` | Bumps the dependencies group with 2 updates in the /e2e directory: [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) and [modernc.org/sqlite](https://gitlab.com/cznic/sqlite). Updates `github.com/huandu/go-sqlbuilder` from 1.36.1 to 1.37.0 - [Release notes](https://github.com/huandu/go-sqlbuilder/releases) - [Commits](https://github.com/huandu/go-sqlbuilder/compare/v1.36.1...v1.37.0) Updates `github.com/lestrrat-go/jwx/v3` from 3.0.10 to 3.0.11 - [Release notes](https://github.com/lestrrat-go/jwx/releases) - [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes) - [Commits](https://github.com/lestrrat-go/jwx/compare/v3.0.10...v3.0.11) Updates `github.com/prometheus/client_golang` from 1.23.0 to 1.23.2 - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2) Updates `github.com/spf13/cobra` from 1.9.1 to 1.10.1 - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1) Updates `github.com/spf13/pflag` from 1.0.7 to 1.0.9 - [Release notes](https://github.com/spf13/pflag/releases) - [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.9) Updates `github.com/spf13/viper` from 1.20.1 to 1.21.0 - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.20.1...v1.21.0) Updates `golang.org/x/net` from 0.43.0 to 0.44.0 - [Commits](https://github.com/golang/net/compare/v0.43.0...v0.44.0) Updates `golang.org/x/time` from 0.12.0 to 0.13.0 - [Commits](https://github.com/golang/time/compare/v0.12.0...v0.13.0) Updates `google.golang.org/grpc` from 1.75.0 to 1.75.1 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.0...v1.75.1) Updates `google.golang.org/protobuf` from 1.36.8 to 1.36.9 Updates `github.com/testcontainers/testcontainers-go` from 0.38.0 to 0.39.0 - [Release notes](https://github.com/testcontainers/testcontainers-go/releases) - [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.38.0...v0.39.0) Updates `modernc.org/sqlite` from 1.38.2 to 1.39.0 - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.38.2...v1.39.0) --- updated-dependencies: - dependency-name: github.com/huandu/go-sqlbuilder dependency-version: 1.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: github.com/lestrrat-go/jwx/v3 dependency-version: 3.0.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github.com/spf13/cobra dependency-version: 1.10.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: github.com/spf13/pflag dependency-version: 1.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github.com/spf13/viper dependency-version: 1.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: golang.org/x/net dependency-version: 0.44.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: golang.org/x/time dependency-version: 0.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: google.golang.org/grpc dependency-version: 1.75.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github.com/testcontainers/testcontainers-go dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: modernc.org/sqlite dependency-version: 1.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Stephan Renatus
parent
5d21f9ac13
commit
d3d014d18d
+20
-17
@@ -12,12 +12,12 @@ require (
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/microsoft/go-mssqldb v1.9.3
|
||||
github.com/open-policy-agent/opa v1.8.0
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
modernc.org/sqlite v1.38.2
|
||||
github.com/testcontainers/testcontainers-go v0.39.0
|
||||
modernc.org/sqlite v1.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -58,13 +58,16 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/huandu/go-sqlbuilder v1.36.1 // indirect
|
||||
github.com/huandu/go-clone v1.7.3 // indirect
|
||||
github.com/huandu/go-sqlbuilder v1.37.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0 // indirect
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
@@ -89,15 +92,15 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
||||
@@ -121,17 +124,17 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
||||
+45
-36
@@ -1,5 +1,5 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
@@ -124,10 +124,13 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+u
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
||||
github.com/huandu/go-sqlbuilder v1.36.1 h1:4S17aR2BPW8L2PeotAD4iVhSMjwzk6CO8ABmp3tMEhY=
|
||||
github.com/huandu/go-sqlbuilder v1.36.1/go.mod h1:59Zjq93ndlKI6O5kHmkXQpDgriBAPMKuhByEOy6xYK8=
|
||||
github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ=
|
||||
github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
|
||||
github.com/huandu/go-sqlbuilder v1.37.0 h1:hXgk2rTnlgFgKsmFpizhe6g/oz1wxef4qk3ixFhK6a0=
|
||||
github.com/huandu/go-sqlbuilder v1.37.0/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@@ -142,12 +145,16 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
||||
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0 h1:nZUx/zFg5uc2rhlu1L1DidGr5Sj02JbXvGSpnY4LMrc=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0/go.mod h1:k2U1QIiyVqAKtkffbg+cUmsyiPGQsb9aAfNQiNFuQ9Q=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11 h1:yEeUGNUuNjcez/Voxvr7XPTYNraSQTENJgtVTfwvG/w=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11/go.mod h1:XSOAh2SiXm0QgRe3DulLZLyt+wUuEdFo81zuKTLcvgQ=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
||||
@@ -211,12 +218,12 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
@@ -232,13 +239,14 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -246,8 +254,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
|
||||
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -299,8 +307,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -311,13 +319,13 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -329,16 +337,16 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -355,13 +363,14 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -387,8 +396,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -18,20 +18,20 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/go-sqlbuilder v1.36.1
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10
|
||||
github.com/huandu/go-sqlbuilder v1.37.0
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/peterh/liner v1.2.2
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/tchap/go-patricia/v2 v2.3.3
|
||||
github.com/vektah/gqlparser/v2 v2.5.30
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
|
||||
@@ -44,10 +44,10 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.37.0
|
||||
go.opentelemetry.io/otel/trace v1.37.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/time v0.12.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.8
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/time v0.13.0
|
||||
google.golang.org/grpc v1.75.1
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
oras.land/oras-go/v2 v2.6.0
|
||||
@@ -72,6 +72,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/huandu/go-clone v1.7.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
@@ -79,8 +80,10 @@ require (
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
@@ -89,30 +92,29 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
|
||||
@@ -81,10 +81,13 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+u
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
||||
github.com/huandu/go-sqlbuilder v1.36.1 h1:4S17aR2BPW8L2PeotAD4iVhSMjwzk6CO8ABmp3tMEhY=
|
||||
github.com/huandu/go-sqlbuilder v1.36.1/go.mod h1:59Zjq93ndlKI6O5kHmkXQpDgriBAPMKuhByEOy6xYK8=
|
||||
github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ=
|
||||
github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
|
||||
github.com/huandu/go-sqlbuilder v1.37.0 h1:hXgk2rTnlgFgKsmFpizhe6g/oz1wxef4qk3ixFhK6a0=
|
||||
github.com/huandu/go-sqlbuilder v1.37.0/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -105,12 +108,16 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
||||
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0 h1:nZUx/zFg5uc2rhlu1L1DidGr5Sj02JbXvGSpnY4LMrc=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.0/go.mod h1:k2U1QIiyVqAKtkffbg+cUmsyiPGQsb9aAfNQiNFuQ9Q=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11 h1:yEeUGNUuNjcez/Voxvr7XPTYNraSQTENJgtVTfwvG/w=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11/go.mod h1:XSOAh2SiXm0QgRe3DulLZLyt+wUuEdFo81zuKTLcvgQ=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
||||
@@ -141,12 +148,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
@@ -159,27 +166,27 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -230,12 +237,10 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -243,8 +248,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -264,8 +269,8 @@ 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.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -274,8 +279,8 @@ 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.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -290,8 +295,8 @@ 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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
@@ -306,10 +311,10 @@ 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -330,10 +335,10 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea/
|
||||
|
||||
# VS Code
|
||||
debug
|
||||
debug_test
|
||||
.vscode/
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# go workspace
|
||||
go.work
|
||||
go.work.sum
|
||||
Generated
Vendored
+19
-19
@@ -1,19 +1,19 @@
|
||||
Copyright (c) 2017-2021 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Copyright (c) 2019 Huan Du
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
# go-clone: Clone any Go data structure deeply and thoroughly
|
||||
|
||||
[](https://github.com/huandu/go-clone/actions)
|
||||
[](https://pkg.go.dev/github.com/huandu/go-clone)
|
||||
[](https://goreportcard.com/report/github.com/huandu/go-clone)
|
||||
[](https://coveralls.io/github/huandu/go-clone?branch=master)
|
||||
|
||||
Package `clone` provides functions to deep clone any Go data. It also provides a wrapper to protect a pointer from any unexpected mutation.
|
||||
|
||||
For users who use Go 1.18+, it's recommended to import `github.com/huandu/go-clone/generic` for generic APIs and arena support.
|
||||
|
||||
`Clone`/`Slowly` can clone unexported fields and "no-copy" structs as well. Use this feature wisely.
|
||||
|
||||
## Install
|
||||
|
||||
Use `go get` to install this package.
|
||||
|
||||
```shell
|
||||
go get github.com/huandu/go-clone
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### `Clone` and `Slowly`
|
||||
|
||||
If we want to clone any Go value, use `Clone`.
|
||||
|
||||
```go
|
||||
t := &T{...}
|
||||
v := clone.Clone(t).(*T)
|
||||
reflect.DeepEqual(t, v) // true
|
||||
```
|
||||
|
||||
For the sake of performance, `Clone` doesn't deal with values containing pointer cycles.
|
||||
If we need to clone such values, use `Slowly` instead.
|
||||
|
||||
```go
|
||||
type ListNode struct {
|
||||
Data int
|
||||
Next *ListNode
|
||||
}
|
||||
node1 := &ListNode{
|
||||
Data: 1,
|
||||
}
|
||||
node2 := &ListNode{
|
||||
Data: 2,
|
||||
}
|
||||
node3 := &ListNode{
|
||||
Data: 3,
|
||||
}
|
||||
node1.Next = node2
|
||||
node2.Next = node3
|
||||
node3.Next = node1
|
||||
|
||||
// We must use `Slowly` to clone a circular linked list.
|
||||
node := Slowly(node1).(*ListNode)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(node.Data)
|
||||
node = node.Next
|
||||
}
|
||||
```
|
||||
|
||||
### Generic APIs
|
||||
|
||||
Starting from go1.18, Go started to support generic. With generic syntax, `Clone`/`Slowly` and other APIs can be called much cleaner like following.
|
||||
|
||||
```go
|
||||
import "github.com/huandu/go-clone/generic"
|
||||
|
||||
type MyType struct {
|
||||
Foo string
|
||||
}
|
||||
|
||||
original := &MyType{
|
||||
Foo: "bar",
|
||||
}
|
||||
|
||||
// The type of cloned is *MyType instead of interface{}.
|
||||
cloned := Clone(original)
|
||||
println(cloned.Foo) // Output: bar
|
||||
```
|
||||
|
||||
It's required to update minimal Go version to 1.18 to opt-in generic syntax. It may not be a wise choice to update this package's `go.mod` and drop so many old Go compilers for such syntax candy. Therefore, I decide to create a new standalone package `github.com/huandu/go-clone/generic` to provide APIs with generic syntax.
|
||||
|
||||
For new users who use Go 1.18+, the generic package is preferred and recommended.
|
||||
|
||||
### Arena support
|
||||
|
||||
Starting from Go1.20, arena is introduced as a new way to allocate memory. It's quite useful to improve overall performance in special scenarios.
|
||||
In order to clone a value with memory allocated from an arena, there are new methods `ArenaClone` and `ArenaCloneSlowly` available in `github.com/huandu/go-clone/generic`.
|
||||
|
||||
```go
|
||||
// ArenaClone recursively deep clones v to a new value in arena a.
|
||||
// It works in the same way as Clone, except it allocates all memory from arena.
|
||||
func ArenaClone[T any](a *arena.Arena, v T) (nv T)
|
||||
|
||||
// ArenaCloneSlowly recursively deep clones v to a new value in arena a.
|
||||
// It works in the same way as Slowly, except it allocates all memory from arena.
|
||||
func ArenaCloneSlowly[T any](a *arena.Arena, v T) (nv T)
|
||||
```
|
||||
|
||||
Due to limitations in arena API, memory of the internal data structure of `map` and `chan` is always allocated in heap by Go runtime ([see this issue](https://github.com/golang/go/issues/56230)).
|
||||
|
||||
**Warning**: Per [discussion in the arena proposal](https://github.com/golang/go/issues/51317), the arena package may be changed incompatibly or removed in future. All arena related APIs in this package will be changed accordingly.
|
||||
|
||||
### Struct tags
|
||||
|
||||
There are some struct tags to control how to clone a struct field.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
Normal *int
|
||||
Foo *int `clone:"skip"` // Skip cloning this field so that Foo will be zero in cloned value.
|
||||
Bar *int `clone:"-"` // "-" is an alias of skip.
|
||||
Baz *int `clone:"shadowcopy"` // Copy this field by shadow copy.
|
||||
}
|
||||
|
||||
a := 1
|
||||
t := &T{
|
||||
Normal: &a,
|
||||
Foo: &a,
|
||||
Bar: &a,
|
||||
Baz: &a,
|
||||
}
|
||||
v := clone.Clone(t).(*T)
|
||||
|
||||
fmt.Println(v.Normal == t.Normal) // false
|
||||
fmt.Println(v.Foo == nil) // true
|
||||
fmt.Println(v.Bar == nil) // true
|
||||
fmt.Println(v.Baz == t.Baz) // true
|
||||
```
|
||||
|
||||
### Memory allocations and the `Allocator`
|
||||
|
||||
The `Allocator` is designed to allocate memory when cloning. It's also used to hold all customizations, e.g. custom clone functions, scalar types and opaque pointers, etc. There is a default allocator which allocates memory from heap. Almost all public APIs in this package use this default allocator to do their job.
|
||||
|
||||
We can control how to allocate memory by creating a new `Allocator` by `NewAllocator`. It enables us to take full control over memory allocation when cloning. See [Allocator sample code](https://pkg.go.dev/github.com/huandu/go-clone#example-Allocator) to understand how to customize an allocator.
|
||||
|
||||
Let's take a closer look at the `NewAllocator` function.
|
||||
|
||||
```go
|
||||
func NewAllocator(pool unsafe.Pointer, methods *AllocatorMethods) *Allocator
|
||||
```
|
||||
|
||||
- The first parameter `pool` is a pointer to a memory pool. It's used to allocate memory for cloning. It can be `nil` if we don't need a memory pool.
|
||||
- The second parameter `methods` is a pointer to a struct which contains all methods to allocate memory. It can be `nil` if we don't need to customize memory allocation.
|
||||
- The `Allocator` struct is allocated from the `methods.New` or the `methods.Parent` allocator or from heap.
|
||||
|
||||
The `Parent` in `AllocatorMethods` is used to indicate the parent of the new allocator. With this feature, we can orgnize allocators into a tree structure. All customizations, including custom clone functions, scalar types and opaque pointers, etc, are inherited from parent allocators.
|
||||
|
||||
There are some APIs designed for convenience.
|
||||
|
||||
- We can create dedicated allocators for heap or arena by calling `FromHeap()` or `FromArena(a *arena.Arena)`.
|
||||
- We can call `MakeCloner(allocator)` to create a helper struct with `Clone` and `CloneSlowly` methods in which the type of in and out parameters is `interface{}`.
|
||||
|
||||
### Mark struct type as scalar
|
||||
|
||||
Some struct types can be considered as scalar.
|
||||
|
||||
A well-known case is `time.Time`.
|
||||
Although there is a pointer `loc *time.Location` inside `time.Time`, we always use `time.Time` by value in all methods.
|
||||
When cloning `time.Time`, it should be OK to return a shadow copy.
|
||||
|
||||
Currently, following types are marked as scalar by default.
|
||||
|
||||
- `time.Time`
|
||||
- `reflect.Value`
|
||||
|
||||
If there is any type defined in built-in package should be considered as scalar, please open new issue to let me know.
|
||||
I will update the default.
|
||||
|
||||
If there is any custom type should be considered as scalar, call `MarkAsScalar` to mark it manually. See [MarkAsScalar sample code](https://pkg.go.dev/github.com/huandu/go-clone#example-MarkAsScalar) for more details.
|
||||
|
||||
### Mark pointer type as opaque
|
||||
|
||||
Some pointer values are used as enumerable const values.
|
||||
|
||||
A well-known case is `elliptic.Curve`. In package `crypto/tls`, curve type of a certificate is checked by comparing values to pre-defined curve values, e.g. `elliptic.P521()`. In this case, the curve values, which are pointers or structs, cannot be cloned deeply.
|
||||
|
||||
Currently, following types are marked as scalar by default.
|
||||
|
||||
- `elliptic.Curve`, which is `*elliptic.CurveParam` or `elliptic.p256Curve`.
|
||||
- `reflect.Type`, which is `*reflect.rtype` defined in `runtime`.
|
||||
|
||||
If there is any pointer type defined in built-in package should be considered as opaque, please open new issue to let me know.
|
||||
I will update the default.
|
||||
|
||||
If there is any custom pointer type should be considered as opaque, call `MarkAsOpaquePointer` to mark it manually. See [MarkAsOpaquePointer sample code](https://pkg.go.dev/github.com/huandu/go-clone#example-MarkAsOpaquePointer) for more details.
|
||||
|
||||
### Clone "no-copy" types defined in `sync` and `sync/atomic`
|
||||
|
||||
There are some "no-copy" types like `sync.Mutex`, `atomic.Value`, etc.
|
||||
They cannot be cloned by copying all fields one by one, but we can alloc a new zero value and call methods to do proper initialization.
|
||||
|
||||
Currently, all "no-copy" types defined in `sync` and `sync/atomic` can be cloned properly using following strategies.
|
||||
|
||||
- `sync.Mutex`: Cloned value is a newly allocated zero mutex.
|
||||
- `sync.RWMutex`: Cloned value is a newly allocated zero mutex.
|
||||
- `sync.WaitGroup`: Cloned value is a newly allocated zero wait group.
|
||||
- `sync.Cond`: Cloned value is a cond with a newly allocated zero lock.
|
||||
- `sync.Pool`: Cloned value is an empty pool with the same `New` function.
|
||||
- `sync.Map`: Cloned value is a sync map with cloned key/value pairs.
|
||||
- `sync.Once`: Cloned value is a once type with the same done flag.
|
||||
- `atomic.Value`/`atomic.Bool`/`atomic.Int32`/`atomic.Int64`/`atomic.Uint32`/`atomic.Uint64`/`atomic.Uintptr`: Cloned value is a new atomic value with the same value.
|
||||
|
||||
If there is any type defined in built-in package should be considered as "no-copy" types, please open new issue to let me know.
|
||||
I will update the default.
|
||||
|
||||
### Set custom clone functions
|
||||
|
||||
If default clone strategy doesn't work for a struct type, we can call `SetCustomFunc` to register a custom clone function.
|
||||
|
||||
```go
|
||||
SetCustomFunc(reflect.TypeOf(MyType{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
// Customized logic to copy the old to the new.
|
||||
// The old's type is MyType.
|
||||
// The new is a zero value of MyType and new.CanAddr() always returns true.
|
||||
})
|
||||
```
|
||||
|
||||
We can use `allocator` to clone any value or allocate new memory.
|
||||
It's allowed to call `allocator.Clone` or `allocator.CloneSlowly` on `old` to clone its struct fields in depth without worrying about dead loop.
|
||||
|
||||
See [SetCustomFunc sample code](https://pkg.go.dev/github.com/huandu/go-clone#example-SetCustomFunc) for more details.
|
||||
|
||||
### Clone `atomic.Pointer[T]`
|
||||
|
||||
As there is no way to predefine a custom clone function for generic type `atomic.Pointer[T]`, cloning such atomic type is not supported by default. If we want to support it, we need to register a custom clone function manually.
|
||||
|
||||
Suppose we instantiate `atomic.Pointer[T]` with type `MyType1` and `MyType2` in a project, and then we can register custom clone functions like following.
|
||||
|
||||
```go
|
||||
import "github.com/huandu/go-clone/generic"
|
||||
|
||||
func init() {
|
||||
// Register all instantiated atomic.Pointer[T] types in this project.
|
||||
clone.RegisterAtomicPointer[MyType1]()
|
||||
clone.RegisterAtomicPointer[MyType2]()
|
||||
}
|
||||
```
|
||||
|
||||
### `Wrap`, `Unwrap` and `Undo`
|
||||
|
||||
Package `clone` provides `Wrap`/`Unwrap` functions to protect a pointer value from any unexpected mutation.
|
||||
It's useful when we want to protect a variable which should be immutable by design,
|
||||
e.g. global config, the value stored in context, the value sent to a chan, etc.
|
||||
|
||||
```go
|
||||
// Suppose we have a type T defined as following.
|
||||
// type T struct {
|
||||
// Foo int
|
||||
// }
|
||||
v := &T{
|
||||
Foo: 123,
|
||||
}
|
||||
w := Wrap(v).(*T) // Wrap value to protect it.
|
||||
|
||||
// Use w freely. The type of w is the same as that of v.
|
||||
|
||||
// It's OK to modify w. The change will not affect v.
|
||||
w.Foo = 456
|
||||
fmt.Println(w.Foo) // 456
|
||||
fmt.Println(v.Foo) // 123
|
||||
|
||||
// Once we need the original value stored in w, call `Unwrap`.
|
||||
orig := Unwrap(w).(*T)
|
||||
fmt.Println(orig == v) // true
|
||||
fmt.Println(orig.Foo) // 123
|
||||
|
||||
// Or, we can simply undo any change made in w.
|
||||
// Note that `Undo` is significantly slower than `Unwrap`, thus
|
||||
// the latter is always preferred.
|
||||
Undo(w)
|
||||
fmt.Println(w.Foo) // 123
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Here is the performance data running on my dev machine.
|
||||
|
||||
```text
|
||||
go 1.20.1
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
|
||||
BenchmarkSimpleClone-12 7164530 156.7 ns/op 24 B/op 1 allocs/op
|
||||
BenchmarkComplexClone-12 628056 1871 ns/op 1488 B/op 21 allocs/op
|
||||
BenchmarkUnwrap-12 15498139 78.02 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSimpleWrap-12 3882360 309.7 ns/op 72 B/op 2 allocs/op
|
||||
BenchmarkComplexWrap-12 949654 1245 ns/op 736 B/op 15 allocs/op
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This package is licensed under MIT license. See LICENSE for details.
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
// Copyright 2023 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const fieldTagName = "clone"
|
||||
const fieldTagValueSkip = "skip"
|
||||
const fieldTagValueSkipAlias = "-"
|
||||
const fieldTagValueShadowCopy = "shadowcopy"
|
||||
|
||||
var typeOfAllocator = reflect.TypeOf(Allocator{})
|
||||
|
||||
// defaultAllocator is the default allocator and allocates memory from heap.
|
||||
var defaultAllocator = &Allocator{
|
||||
new: heapNew,
|
||||
makeSlice: heapMakeSlice,
|
||||
makeMap: heapMakeMap,
|
||||
makeChan: heapMakeChan,
|
||||
isScalar: IsScalar,
|
||||
}
|
||||
|
||||
// Allocator is a utility type for memory allocation.
|
||||
type Allocator struct {
|
||||
parent *Allocator
|
||||
|
||||
pool unsafe.Pointer
|
||||
new func(pool unsafe.Pointer, t reflect.Type) reflect.Value
|
||||
makeSlice func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value
|
||||
makeMap func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value
|
||||
makeChan func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value
|
||||
isScalar func(t reflect.Kind) bool
|
||||
|
||||
cachedStructTypes sync.Map
|
||||
cachedPointerTypes sync.Map
|
||||
cachedCustomFuncTypes sync.Map
|
||||
}
|
||||
|
||||
// FromHeap creates an allocator which allocate memory from heap.
|
||||
func FromHeap() *Allocator {
|
||||
return NewAllocator(nil, nil)
|
||||
}
|
||||
|
||||
// NewAllocator creates an allocator which allocate memory from the pool.
|
||||
// Both pool and methods are optional.
|
||||
//
|
||||
// If methods.New is not nil, the allocator itself is created by calling methods.New.
|
||||
//
|
||||
// The pool is a pointer to the memory pool which is opaque to the allocator.
|
||||
// It's methods's responsibility to allocate memory from the pool properly.
|
||||
func NewAllocator(pool unsafe.Pointer, methods *AllocatorMethods) (allocator *Allocator) {
|
||||
parent := methods.parent()
|
||||
new := methods.new(parent, pool)
|
||||
|
||||
// Allocate the allocator from the pool.
|
||||
val := new(pool, typeOfAllocator)
|
||||
allocator = (*Allocator)(unsafe.Pointer(val.Pointer()))
|
||||
runtime.KeepAlive(val)
|
||||
|
||||
allocator.pool = pool
|
||||
allocator.new = new
|
||||
allocator.makeSlice = methods.makeSlice(parent, pool)
|
||||
allocator.makeMap = methods.makeMap(parent, pool)
|
||||
allocator.makeChan = methods.makeChan(parent, pool)
|
||||
allocator.isScalar = methods.isScalar(parent)
|
||||
|
||||
if parent == nil {
|
||||
parent = defaultAllocator
|
||||
}
|
||||
|
||||
allocator.parent = parent
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new zero value of t.
|
||||
func (a *Allocator) New(t reflect.Type) reflect.Value {
|
||||
return a.new(a.pool, t)
|
||||
}
|
||||
|
||||
// MakeSlice creates a new zero-initialized slice value of t with len and cap.
|
||||
func (a *Allocator) MakeSlice(t reflect.Type, len, cap int) reflect.Value {
|
||||
return a.makeSlice(a.pool, t, len, cap)
|
||||
}
|
||||
|
||||
// MakeMap creates a new map with minimum size n.
|
||||
func (a *Allocator) MakeMap(t reflect.Type, n int) reflect.Value {
|
||||
return a.makeMap(a.pool, t, n)
|
||||
}
|
||||
|
||||
// MakeChan creates a new chan with buffer.
|
||||
func (a *Allocator) MakeChan(t reflect.Type, buffer int) reflect.Value {
|
||||
return a.makeChan(a.pool, t, buffer)
|
||||
}
|
||||
|
||||
// Clone recursively deep clone val to a new value with memory allocated from a.
|
||||
func (a *Allocator) Clone(val reflect.Value) reflect.Value {
|
||||
return a.clone(val, true)
|
||||
}
|
||||
|
||||
func (a *Allocator) clone(val reflect.Value, inCustomFunc bool) reflect.Value {
|
||||
if !val.IsValid() {
|
||||
return val
|
||||
}
|
||||
|
||||
state := &cloneState{
|
||||
allocator: a,
|
||||
}
|
||||
|
||||
if inCustomFunc {
|
||||
state.skipCustomFuncValue = val
|
||||
}
|
||||
|
||||
return state.clone(val)
|
||||
}
|
||||
|
||||
// CloneSlowly recursively deep clone val to a new value with memory allocated from a.
|
||||
// It marks all cloned values internally, thus it can clone v with cycle pointer.
|
||||
func (a *Allocator) CloneSlowly(val reflect.Value) reflect.Value {
|
||||
return a.cloneSlowly(val, true)
|
||||
}
|
||||
|
||||
func (a *Allocator) cloneSlowly(val reflect.Value, inCustomFunc bool) reflect.Value {
|
||||
if !val.IsValid() {
|
||||
return val
|
||||
}
|
||||
|
||||
state := &cloneState{
|
||||
allocator: a,
|
||||
visited: visitMap{},
|
||||
invalid: invalidPointers{},
|
||||
}
|
||||
|
||||
if inCustomFunc {
|
||||
state.skipCustomFuncValue = val
|
||||
}
|
||||
|
||||
cloned := state.clone(val)
|
||||
state.fix(cloned)
|
||||
return cloned
|
||||
}
|
||||
|
||||
func (a *Allocator) loadStructType(t reflect.Type) (st structType) {
|
||||
st, ok := a.lookupStructType(t)
|
||||
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
num := t.NumField()
|
||||
zeroFeilds := make([]structFieldSize, 0, num)
|
||||
pointerFields := make([]structFieldType, 0, num)
|
||||
|
||||
// Find pointer fields in depth-first order.
|
||||
for i := 0; i < num; i++ {
|
||||
field := t.Field(i)
|
||||
ft := field.Type
|
||||
k := ft.Kind()
|
||||
tag := field.Tag.Get(fieldTagName)
|
||||
|
||||
if tag == fieldTagValueSkip || tag == fieldTagValueSkipAlias {
|
||||
zeroFeilds = append(zeroFeilds, structFieldSize{
|
||||
Offset: field.Offset,
|
||||
Size: uintptr(ft.Size()),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if tag == fieldTagValueShadowCopy || a.isScalar(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch k {
|
||||
case reflect.Array:
|
||||
if ft.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
elem := ft.Elem()
|
||||
|
||||
if a.isScalar(elem.Kind()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if elem.Kind() == reflect.Struct {
|
||||
if fst := a.loadStructType(elem); fst.CanShadowCopy() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
if fst := a.loadStructType(ft); fst.CanShadowCopy() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
pointerFields = append(pointerFields, structFieldType{
|
||||
Offset: field.Offset,
|
||||
Index: i,
|
||||
})
|
||||
}
|
||||
|
||||
st = structType{}
|
||||
|
||||
if len(zeroFeilds) != 0 {
|
||||
st.ZeroFields = append(st.ZeroFields, zeroFeilds...)
|
||||
}
|
||||
|
||||
if len(pointerFields) != 0 {
|
||||
st.PointerFields = append(st.PointerFields, pointerFields...)
|
||||
}
|
||||
|
||||
// Load custom function.
|
||||
current := a
|
||||
|
||||
for current != nil {
|
||||
if fn, ok := current.cachedCustomFuncTypes.Load(t); ok {
|
||||
st.fn = fn.(Func)
|
||||
break
|
||||
}
|
||||
|
||||
current = current.parent
|
||||
}
|
||||
|
||||
a.cachedStructTypes.LoadOrStore(t, st)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Allocator) lookupStructType(t reflect.Type) (st structType, ok bool) {
|
||||
var v interface{}
|
||||
current := a
|
||||
|
||||
for current != nil {
|
||||
v, ok = current.cachedStructTypes.Load(t)
|
||||
|
||||
if ok {
|
||||
st = v.(structType)
|
||||
return
|
||||
}
|
||||
|
||||
current = current.parent
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Allocator) isOpaquePointer(t reflect.Type) (ok bool) {
|
||||
current := a
|
||||
|
||||
for current != nil {
|
||||
if _, ok = current.cachedPointerTypes.Load(t); ok {
|
||||
return
|
||||
}
|
||||
|
||||
current = current.parent
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkAsScalar marks t as a scalar type so that all clone methods will copy t by value.
|
||||
// If t is not struct or pointer to struct, MarkAsScalar ignores t.
|
||||
//
|
||||
// In the most cases, it's not necessary to call it explicitly.
|
||||
// If a struct type contains scalar type fields only, the struct will be marked as scalar automatically.
|
||||
//
|
||||
// Here is a list of types marked as scalar by default:
|
||||
// - time.Time
|
||||
// - reflect.Value
|
||||
func (a *Allocator) MarkAsScalar(t reflect.Type) {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
a.cachedStructTypes.Store(t, zeroStructType)
|
||||
}
|
||||
|
||||
// MarkAsOpaquePointer marks t as an opaque pointer so that all clone methods will copy t by value.
|
||||
// If t is not a pointer, MarkAsOpaquePointer ignores t.
|
||||
//
|
||||
// Here is a list of types marked as opaque pointers by default:
|
||||
// - `elliptic.Curve`, which is `*elliptic.CurveParam` or `elliptic.p256Curve`;
|
||||
// - `reflect.Type`, which is `*reflect.rtype` defined in `runtime`.
|
||||
func (a *Allocator) MarkAsOpaquePointer(t reflect.Type) {
|
||||
if t.Kind() != reflect.Ptr {
|
||||
return
|
||||
}
|
||||
|
||||
a.cachedPointerTypes.Store(t, struct{}{})
|
||||
}
|
||||
|
||||
// SetCustomFunc sets a custom clone function for type t.
|
||||
// If t is not struct or pointer to struct, SetCustomFunc ignores t.
|
||||
//
|
||||
// If fn is nil, remove the custom clone function for type t.
|
||||
func (a *Allocator) SetCustomFunc(t reflect.Type, fn Func) {
|
||||
if fn == nil {
|
||||
a.cachedCustomFuncTypes.Delete(t)
|
||||
return
|
||||
}
|
||||
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
a.cachedCustomFuncTypes.Store(t, fn)
|
||||
}
|
||||
|
||||
func heapNew(pool unsafe.Pointer, t reflect.Type) reflect.Value {
|
||||
return reflect.New(t)
|
||||
}
|
||||
|
||||
func heapMakeSlice(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value {
|
||||
return reflect.MakeSlice(t, len, cap)
|
||||
}
|
||||
|
||||
func heapMakeMap(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value {
|
||||
return reflect.MakeMapWithSize(t, n)
|
||||
}
|
||||
|
||||
func heapMakeChan(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value {
|
||||
return reflect.MakeChan(t, buffer)
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright 2023 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AllocatorMethods defines all methods required by allocator.
|
||||
// If any of these methods is nil, allocator will use default method which allocates memory from heap.
|
||||
type AllocatorMethods struct {
|
||||
// Parent is the allocator which handles all unhandled methods.
|
||||
// If it's nil, it will be the default allocator.
|
||||
Parent *Allocator
|
||||
|
||||
New func(pool unsafe.Pointer, t reflect.Type) reflect.Value
|
||||
MakeSlice func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value
|
||||
MakeMap func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value
|
||||
MakeChan func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value
|
||||
IsScalar func(k reflect.Kind) bool
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) parent() *Allocator {
|
||||
if am != nil && am.Parent != nil {
|
||||
return am.Parent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) new(parent *Allocator, pool unsafe.Pointer) func(pool unsafe.Pointer, t reflect.Type) reflect.Value {
|
||||
if am != nil && am.New != nil {
|
||||
return am.New
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
if parent.pool == pool {
|
||||
return parent.new
|
||||
} else {
|
||||
return func(pool unsafe.Pointer, t reflect.Type) reflect.Value {
|
||||
return parent.New(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultAllocator.new
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) makeSlice(parent *Allocator, pool unsafe.Pointer) func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value {
|
||||
if am != nil && am.MakeSlice != nil {
|
||||
return am.MakeSlice
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
if parent.pool == pool {
|
||||
return parent.makeSlice
|
||||
} else {
|
||||
return func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value {
|
||||
return parent.MakeSlice(t, len, cap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultAllocator.makeSlice
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) makeMap(parent *Allocator, pool unsafe.Pointer) func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value {
|
||||
if am != nil && am.MakeMap != nil {
|
||||
return am.MakeMap
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
if parent.pool == pool {
|
||||
return parent.makeMap
|
||||
} else {
|
||||
return func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value {
|
||||
return parent.MakeMap(t, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultAllocator.makeMap
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) makeChan(parent *Allocator, pool unsafe.Pointer) func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value {
|
||||
if am != nil && am.MakeChan != nil {
|
||||
return am.MakeChan
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
if parent.pool == pool {
|
||||
return parent.makeChan
|
||||
} else {
|
||||
return func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value {
|
||||
return parent.MakeChan(t, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultAllocator.makeChan
|
||||
}
|
||||
|
||||
func (am *AllocatorMethods) isScalar(parent *Allocator) func(t reflect.Kind) bool {
|
||||
if am != nil && am.IsScalar != nil {
|
||||
return am.IsScalar
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
return parent.isScalar
|
||||
}
|
||||
|
||||
return defaultAllocator.isScalar
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.20 && goexperiment.arenas)
|
||||
// +build !go1.20 !goexperiment.arenas
|
||||
|
||||
package clone
|
||||
|
||||
const arenaIsEnabled = false
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20 && goexperiment.arenas
|
||||
// +build go1.20,goexperiment.arenas
|
||||
|
||||
package clone
|
||||
|
||||
const arenaIsEnabled = true
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Bool{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Bool.
|
||||
oldValue := old.Addr().Interface().(*atomic.Bool)
|
||||
newValue := new.Addr().Interface().(*atomic.Bool)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Int32{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Int32.
|
||||
oldValue := old.Addr().Interface().(*atomic.Int32)
|
||||
newValue := new.Addr().Interface().(*atomic.Int32)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Int64{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Int64.
|
||||
oldValue := old.Addr().Interface().(*atomic.Int64)
|
||||
newValue := new.Addr().Interface().(*atomic.Int64)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Uint32{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Uint32.
|
||||
oldValue := old.Addr().Interface().(*atomic.Uint32)
|
||||
newValue := new.Addr().Interface().(*atomic.Uint32)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Uint64{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Uint64.
|
||||
oldValue := old.Addr().Interface().(*atomic.Uint64)
|
||||
newValue := new.Addr().Interface().(*atomic.Uint64)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Uintptr{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Uintptr.
|
||||
oldValue := old.Addr().Interface().(*atomic.Uintptr)
|
||||
newValue := new.Addr().Interface().(*atomic.Uintptr)
|
||||
v := oldValue.Load()
|
||||
newValue.Store(v)
|
||||
})
|
||||
}
|
||||
+891
@@ -0,0 +1,891 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
// Package clone provides functions to deep clone any Go data.
|
||||
// It also provides a wrapper to protect a pointer from any unexpected mutation.
|
||||
package clone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var heapCloneState = &cloneState{
|
||||
allocator: defaultAllocator,
|
||||
}
|
||||
var cloner = MakeCloner(defaultAllocator)
|
||||
|
||||
const zeroBytesCount = 256
|
||||
|
||||
var zeroBytes [zeroBytesCount]byte
|
||||
var zero = zeroBytes[:]
|
||||
|
||||
// Clone recursively deep clone v to a new value in heap.
|
||||
// It assumes that there is no pointer cycle in v,
|
||||
// e.g. v has a pointer points to v itself.
|
||||
// If there is a pointer cycle, use Slowly instead.
|
||||
//
|
||||
// Clone allocates memory and deeply copies values inside v in depth-first sequence.
|
||||
// There are a few special rules for following types.
|
||||
//
|
||||
// - Scalar types: all number-like types are copied by value.
|
||||
// - func: Copied by value as func is an opaque pointer at runtime.
|
||||
// - string: Copied by value as string is immutable by design.
|
||||
// - unsafe.Pointer: Copied by value as we don't know what's in it.
|
||||
// - chan: A new empty chan is created as we cannot read data inside the old chan.
|
||||
//
|
||||
// Unlike many other packages, Clone is able to clone unexported fields of any struct.
|
||||
// Use this feature wisely.
|
||||
func Clone(v interface{}) interface{} {
|
||||
return cloner.Clone(v)
|
||||
}
|
||||
|
||||
func clone(allocator *Allocator, v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
cloned := allocator.clone(val, false)
|
||||
return cloned.Interface()
|
||||
}
|
||||
|
||||
// Slowly recursively deep clone v to a new value in heap.
|
||||
// It marks all cloned values internally, thus it can clone v with cycle pointer.
|
||||
//
|
||||
// Slowly works exactly the same as Clone. See Clone doc for more details.
|
||||
func Slowly(v interface{}) interface{} {
|
||||
return cloner.CloneSlowly(v)
|
||||
}
|
||||
|
||||
func cloneSlowly(allocator *Allocator, v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
cloned := allocator.cloneSlowly(val, false)
|
||||
return cloned.Interface()
|
||||
}
|
||||
|
||||
type cloneState struct {
|
||||
allocator *Allocator
|
||||
visited visitMap
|
||||
invalid invalidPointers
|
||||
|
||||
// The value that should not be cloned by custom func.
|
||||
// It's useful to avoid infinite loop when custom func calls allocator.Clone().
|
||||
skipCustomFuncValue reflect.Value
|
||||
}
|
||||
|
||||
type visit struct {
|
||||
p uintptr
|
||||
extra int
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
type visitMap map[visit]reflect.Value
|
||||
type invalidPointers map[visit]reflect.Value
|
||||
|
||||
func (state *cloneState) clone(v reflect.Value) reflect.Value {
|
||||
if state.allocator.isScalar(v.Kind()) {
|
||||
return copyScalarValue(v)
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Array:
|
||||
return state.cloneArray(v)
|
||||
case reflect.Chan:
|
||||
return state.allocator.MakeChan(v.Type(), v.Cap())
|
||||
case reflect.Interface:
|
||||
return state.cloneInterface(v)
|
||||
case reflect.Map:
|
||||
return state.cloneMap(v)
|
||||
case reflect.Ptr:
|
||||
return state.clonePtr(v)
|
||||
case reflect.Slice:
|
||||
return state.cloneSlice(v)
|
||||
case reflect.Struct:
|
||||
return state.cloneStruct(v)
|
||||
case reflect.String:
|
||||
return state.cloneString(v)
|
||||
default:
|
||||
panic(fmt.Errorf("go-clone: <bug> unsupported type `%v`", v.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
func (state *cloneState) cloneArray(v reflect.Value) reflect.Value {
|
||||
dst := state.allocator.New(v.Type())
|
||||
state.copyArray(v, dst)
|
||||
return dst.Elem()
|
||||
}
|
||||
|
||||
func (state *cloneState) copyArray(src, nv reflect.Value) {
|
||||
p := unsafe.Pointer(nv.Pointer()) // dst must be a Ptr.
|
||||
dst := nv.Elem()
|
||||
num := src.Len()
|
||||
|
||||
if state.allocator.isScalar(src.Type().Elem().Kind()) {
|
||||
shadowCopy(src, p)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
dst.Index(i).Set(state.clone(src.Index(i)))
|
||||
}
|
||||
}
|
||||
|
||||
func (state *cloneState) cloneInterface(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
elem := v.Elem()
|
||||
return state.clone(elem).Convert(elem.Type()).Convert(t)
|
||||
}
|
||||
|
||||
func (state *cloneState) cloneMap(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
|
||||
if val, ok := state.visited[vst]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
nv := state.allocator.MakeMap(t, v.Len())
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
state.visited[vst] = nv
|
||||
}
|
||||
|
||||
for iter := mapIter(v); iter.Next(); {
|
||||
key := state.clone(iter.Key())
|
||||
value := state.clone(iter.Value())
|
||||
nv.SetMapIndex(key, value)
|
||||
}
|
||||
|
||||
return nv
|
||||
}
|
||||
|
||||
func (state *cloneState) clonePtr(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
if state.allocator.isOpaquePointer(t) {
|
||||
if v.CanInterface() {
|
||||
return v
|
||||
}
|
||||
|
||||
ptr := state.allocator.New(t)
|
||||
p := unsafe.Pointer(ptr.Pointer())
|
||||
shadowCopy(v, p)
|
||||
return ptr.Elem()
|
||||
}
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
|
||||
if val, ok := state.visited[vst]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
src := v.Elem()
|
||||
elemType := src.Type()
|
||||
elemKind := src.Kind()
|
||||
nv := state.allocator.New(elemType)
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
state.visited[vst] = nv
|
||||
}
|
||||
|
||||
switch elemKind {
|
||||
case reflect.Struct:
|
||||
state.copyStruct(src, nv)
|
||||
case reflect.Array:
|
||||
state.copyArray(src, nv)
|
||||
default:
|
||||
nv.Elem().Set(state.clone(src))
|
||||
}
|
||||
|
||||
// If this pointer is the address of a struct field and it's a cycle pointer,
|
||||
// it may be updated.
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
nv = state.visited[vst]
|
||||
}
|
||||
|
||||
return nv
|
||||
}
|
||||
|
||||
func (state *cloneState) cloneSlice(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
num := v.Len()
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
extra: num,
|
||||
t: t,
|
||||
}
|
||||
|
||||
if val, ok := state.visited[vst]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
c := v.Cap()
|
||||
nv := state.allocator.MakeSlice(t, num, c)
|
||||
|
||||
if state.visited != nil {
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
extra: num,
|
||||
t: t,
|
||||
}
|
||||
state.visited[vst] = nv
|
||||
}
|
||||
|
||||
// For scalar slice, copy underlying values directly.
|
||||
if state.allocator.isScalar(t.Elem().Kind()) {
|
||||
src := unsafe.Pointer(v.Pointer())
|
||||
dst := unsafe.Pointer(nv.Pointer())
|
||||
sz := int(t.Elem().Size())
|
||||
l := num * sz
|
||||
cc := c * sz
|
||||
copy((*[maxByteSize]byte)(dst)[:l:cc], (*[maxByteSize]byte)(src)[:l:cc])
|
||||
} else {
|
||||
for i := 0; i < num; i++ {
|
||||
nv.Index(i).Set(state.clone(v.Index(i)))
|
||||
}
|
||||
}
|
||||
|
||||
return nv
|
||||
}
|
||||
|
||||
func (state *cloneState) cloneStruct(v reflect.Value) reflect.Value {
|
||||
t := v.Type()
|
||||
nv := state.allocator.New(t)
|
||||
state.copyStruct(v, nv)
|
||||
return nv.Elem()
|
||||
}
|
||||
|
||||
var typeOfByteSlice = reflect.TypeOf([]byte(nil))
|
||||
|
||||
func (state *cloneState) cloneString(v reflect.Value) reflect.Value {
|
||||
t := v.Type()
|
||||
l := v.Len()
|
||||
data := state.allocator.MakeSlice(typeOfByteSlice, l, l)
|
||||
|
||||
// The v is an unexported struct field.
|
||||
if !v.CanInterface() {
|
||||
v = reflect.ValueOf(v.String())
|
||||
}
|
||||
|
||||
reflect.Copy(data, v)
|
||||
|
||||
nv := state.allocator.New(t)
|
||||
slice := data.Interface().([]byte)
|
||||
*(*stringHeader)(unsafe.Pointer(nv.Pointer())) = *(*stringHeader)(unsafe.Pointer(&slice))
|
||||
|
||||
return nv.Elem()
|
||||
}
|
||||
|
||||
func (state *cloneState) copyStruct(src, nv reflect.Value) {
|
||||
t := src.Type()
|
||||
st := state.allocator.loadStructType(t)
|
||||
ptr := unsafe.Pointer(nv.Pointer())
|
||||
|
||||
if st.Init(state.allocator, src, nv, state.skipCustomFuncValue == src) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pf := range st.ZeroFields {
|
||||
p := unsafe.Pointer(uintptr(ptr) + pf.Offset)
|
||||
sz := pf.Size
|
||||
|
||||
for sz > zeroBytesCount {
|
||||
copy((*[zeroBytesCount]byte)(p)[:zeroBytesCount:zeroBytesCount], zero)
|
||||
sz -= zeroBytesCount
|
||||
p = unsafe.Pointer(uintptr(p) + zeroBytesCount)
|
||||
}
|
||||
|
||||
copy((*[zeroBytesCount]byte)(p)[:sz:sz], zero)
|
||||
}
|
||||
|
||||
for _, pf := range st.PointerFields {
|
||||
i := int(pf.Index)
|
||||
p := unsafe.Pointer(uintptr(ptr) + pf.Offset)
|
||||
field := src.Field(i)
|
||||
|
||||
// This field can be referenced by a pointer or interface inside itself.
|
||||
// Put the pointer to this field to visited to avoid any error.
|
||||
//
|
||||
// See https://github.com/huandu/go-clone/issues/3.
|
||||
if state.visited != nil && field.CanAddr() {
|
||||
ft := field.Type()
|
||||
fp := field.Addr().Pointer()
|
||||
vst := visit{
|
||||
p: fp,
|
||||
t: reflect.PtrTo(ft),
|
||||
}
|
||||
nv := reflect.NewAt(ft, p)
|
||||
|
||||
// The address of this field was visited, so fp must be a cycle pointer.
|
||||
// As this field is not fully cloned, the val stored in visited[visit] must be wrong.
|
||||
// It must be replaced by nv which will be the right value (it's incomplete right now).
|
||||
//
|
||||
// Unfortunately, if the val was used by previous clone routines,
|
||||
// there is no easy way to fix wrong values - all pointers must be traversed and fixed.
|
||||
if val, ok := state.visited[vst]; ok {
|
||||
state.invalid[visit{
|
||||
p: val.Pointer(),
|
||||
t: vst.t,
|
||||
}] = nv
|
||||
}
|
||||
|
||||
state.visited[vst] = nv
|
||||
}
|
||||
|
||||
v := state.clone(field)
|
||||
shadowCopy(v, p)
|
||||
}
|
||||
}
|
||||
|
||||
var typeOfString = reflect.TypeOf("")
|
||||
|
||||
func shadowCopy(src reflect.Value, p unsafe.Pointer) {
|
||||
switch src.Kind() {
|
||||
case reflect.Bool:
|
||||
*(*bool)(p) = src.Bool()
|
||||
case reflect.Int:
|
||||
*(*int)(p) = int(src.Int())
|
||||
case reflect.Int8:
|
||||
*(*int8)(p) = int8(src.Int())
|
||||
case reflect.Int16:
|
||||
*(*int16)(p) = int16(src.Int())
|
||||
case reflect.Int32:
|
||||
*(*int32)(p) = int32(src.Int())
|
||||
case reflect.Int64:
|
||||
*(*int64)(p) = src.Int()
|
||||
case reflect.Uint:
|
||||
*(*uint)(p) = uint(src.Uint())
|
||||
case reflect.Uint8:
|
||||
*(*uint8)(p) = uint8(src.Uint())
|
||||
case reflect.Uint16:
|
||||
*(*uint16)(p) = uint16(src.Uint())
|
||||
case reflect.Uint32:
|
||||
*(*uint32)(p) = uint32(src.Uint())
|
||||
case reflect.Uint64:
|
||||
*(*uint64)(p) = src.Uint()
|
||||
case reflect.Uintptr:
|
||||
*(*uintptr)(p) = uintptr(src.Uint())
|
||||
case reflect.Float32:
|
||||
*(*float32)(p) = float32(src.Float())
|
||||
case reflect.Float64:
|
||||
*(*float64)(p) = src.Float()
|
||||
case reflect.Complex64:
|
||||
*(*complex64)(p) = complex64(src.Complex())
|
||||
case reflect.Complex128:
|
||||
*(*complex128)(p) = src.Complex()
|
||||
|
||||
case reflect.Array:
|
||||
t := src.Type()
|
||||
|
||||
if src.CanAddr() {
|
||||
srcPtr := unsafe.Pointer(src.UnsafeAddr())
|
||||
sz := t.Size()
|
||||
copy((*[maxByteSize]byte)(p)[:sz:sz], (*[maxByteSize]byte)(srcPtr)[:sz:sz])
|
||||
return
|
||||
}
|
||||
|
||||
val := reflect.NewAt(t, p).Elem()
|
||||
|
||||
if src.CanInterface() {
|
||||
val.Set(src)
|
||||
return
|
||||
}
|
||||
|
||||
sz := t.Elem().Size()
|
||||
num := src.Len()
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
elemPtr := unsafe.Pointer(uintptr(p) + uintptr(i)*sz)
|
||||
shadowCopy(src.Index(i), elemPtr)
|
||||
}
|
||||
case reflect.Chan:
|
||||
*((*uintptr)(p)) = src.Pointer()
|
||||
case reflect.Func:
|
||||
t := src.Type()
|
||||
src = copyScalarValue(src)
|
||||
val := reflect.NewAt(t, p).Elem()
|
||||
val.Set(src)
|
||||
case reflect.Interface:
|
||||
*((*interfaceData)(p)) = parseReflectValue(src)
|
||||
case reflect.Map:
|
||||
*((*uintptr)(p)) = src.Pointer()
|
||||
case reflect.Ptr:
|
||||
*((*uintptr)(p)) = src.Pointer()
|
||||
case reflect.Slice:
|
||||
*(*sliceHeader)(p) = sliceHeader{
|
||||
Data: src.Pointer(),
|
||||
Len: src.Len(),
|
||||
Cap: src.Cap(),
|
||||
}
|
||||
case reflect.String:
|
||||
s := src.String()
|
||||
val := reflect.NewAt(typeOfString, p).Elem()
|
||||
val.SetString(s)
|
||||
case reflect.Struct:
|
||||
t := src.Type()
|
||||
val := reflect.NewAt(t, p).Elem()
|
||||
|
||||
if src.CanInterface() {
|
||||
val.Set(src)
|
||||
return
|
||||
}
|
||||
|
||||
num := t.NumField()
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
field := t.Field(i)
|
||||
fieldPtr := unsafe.Pointer(uintptr(p) + field.Offset)
|
||||
shadowCopy(src.Field(i), fieldPtr)
|
||||
}
|
||||
case reflect.UnsafePointer:
|
||||
// There is no way to copy unsafe.Pointer value.
|
||||
*((*uintptr)(p)) = src.Pointer()
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("go-clone: <bug> impossible type `%v` when cloning private field", src.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
// fix tranverses v to update all pointer values in state.invalid.
|
||||
func (state *cloneState) fix(v reflect.Value) {
|
||||
if state == nil || len(state.invalid) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fix := &fixState{
|
||||
allocator: state.allocator,
|
||||
fixed: fixMap{},
|
||||
invalid: state.invalid,
|
||||
}
|
||||
fix.fix(v)
|
||||
}
|
||||
|
||||
type fixState struct {
|
||||
allocator *Allocator
|
||||
fixed fixMap
|
||||
invalid invalidPointers
|
||||
}
|
||||
|
||||
type fixMap map[visit]struct{}
|
||||
|
||||
func (fix *fixState) new(t reflect.Type) reflect.Value {
|
||||
return fix.allocator.New(t)
|
||||
}
|
||||
|
||||
func (fix *fixState) fix(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
if fix.allocator.isScalar(v.Kind()) {
|
||||
return
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Array:
|
||||
return fix.fixArray(v)
|
||||
case reflect.Chan:
|
||||
// Do nothing.
|
||||
return
|
||||
case reflect.Interface:
|
||||
return fix.fixInterface(v)
|
||||
case reflect.Map:
|
||||
return fix.fixMap(v)
|
||||
case reflect.Ptr:
|
||||
return fix.fixPtr(v)
|
||||
case reflect.Slice:
|
||||
return fix.fixSlice(v)
|
||||
case reflect.Struct:
|
||||
return fix.fixStruct(v)
|
||||
case reflect.String:
|
||||
// Do nothing.
|
||||
return
|
||||
default:
|
||||
panic(fmt.Errorf("go-clone: <bug> unsupported type `%v`", v.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
func (fix *fixState) fixArray(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
t := v.Type()
|
||||
et := t.Elem()
|
||||
kind := et.Kind()
|
||||
|
||||
if fix.allocator.isScalar(kind) {
|
||||
return
|
||||
}
|
||||
|
||||
l := v.Len()
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
elem := v.Index(i)
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: elem.Pointer(),
|
||||
t: et,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
// If elem cannot be set, v must be copied to make it settable.
|
||||
// Don't do it unless there is no other choices.
|
||||
if !elem.CanSet() {
|
||||
copied = fix.new(t).Elem()
|
||||
shadowCopy(v, unsafe.Pointer(copied.Addr().Pointer()))
|
||||
_, changed = fix.fixArray(copied)
|
||||
return
|
||||
}
|
||||
|
||||
elem.Set(nv)
|
||||
changed++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fixed, c := fix.fix(elem)
|
||||
changed += c
|
||||
|
||||
if fixed.IsValid() {
|
||||
// If elem cannot be set, v must be copied to make it settable.
|
||||
// Don't do it unless there is no other choices.
|
||||
if !elem.CanSet() {
|
||||
copied = fix.new(t).Elem()
|
||||
shadowCopy(v, unsafe.Pointer(copied.Addr().Pointer()))
|
||||
_, changed = fix.fixArray(copied)
|
||||
return
|
||||
}
|
||||
|
||||
elem.Set(fixed)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fix *fixState) fixInterface(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
if v.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
elem := v.Elem()
|
||||
t := elem.Type()
|
||||
kind := elem.Kind()
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: elem.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
copied = nv.Convert(v.Type())
|
||||
changed++
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
copied, changed = fix.fix(elem)
|
||||
|
||||
if copied.IsValid() {
|
||||
copied = copied.Convert(v.Type())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fix *fixState) fixMap(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
if v.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: t,
|
||||
}
|
||||
|
||||
if _, ok := fix.fixed[vst]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
fix.fixed[vst] = struct{}{}
|
||||
|
||||
kt := t.Key()
|
||||
et := t.Elem()
|
||||
keyKind := kt.Kind()
|
||||
elemKind := et.Kind()
|
||||
|
||||
if isScalar := fix.allocator.isScalar; isScalar(keyKind) && isScalar(elemKind) {
|
||||
return
|
||||
}
|
||||
|
||||
invalidKeys := map[reflect.Value][2]reflect.Value{}
|
||||
|
||||
for iter := mapIter(v); iter.Next(); {
|
||||
key := iter.Key()
|
||||
elem := iter.Value()
|
||||
var fixed reflect.Value
|
||||
c := 0
|
||||
|
||||
if elemKind == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: elem.Pointer(),
|
||||
t: et,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
fixed = nv
|
||||
c++
|
||||
} else {
|
||||
fixed, c = fix.fixPtr(elem)
|
||||
}
|
||||
} else {
|
||||
fixed, c = fix.fix(elem)
|
||||
}
|
||||
|
||||
changed += c
|
||||
c = 0
|
||||
|
||||
if fixed.IsValid() {
|
||||
v = forceSetMapIndex(v, key, fixed)
|
||||
elem = fixed
|
||||
fixed = reflect.Value{}
|
||||
}
|
||||
|
||||
if keyKind == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: key.Pointer(),
|
||||
t: kt,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
fixed = nv
|
||||
c++
|
||||
} else {
|
||||
fixed, c = fix.fixPtr(key)
|
||||
}
|
||||
} else {
|
||||
fixed, c = fix.fix(key)
|
||||
}
|
||||
|
||||
changed += c
|
||||
|
||||
// Key cannot be changed immediately inside map range iteration.
|
||||
// Do it later.
|
||||
if fixed.IsValid() {
|
||||
invalidKeys[key] = [2]reflect.Value{fixed, elem}
|
||||
}
|
||||
}
|
||||
|
||||
for key, kv := range invalidKeys {
|
||||
v = forceSetMapIndex(v, key, reflect.Value{})
|
||||
v = forceSetMapIndex(v, kv[0], kv[1])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func forceSetMapIndex(v, key, elem reflect.Value) (nv reflect.Value) {
|
||||
nv = v
|
||||
|
||||
if !v.CanInterface() {
|
||||
nv = forceClearROFlag(v)
|
||||
}
|
||||
|
||||
if !key.CanInterface() {
|
||||
key = forceClearROFlag(key)
|
||||
}
|
||||
|
||||
if elem.IsValid() && !elem.CanInterface() {
|
||||
elem = forceClearROFlag(elem)
|
||||
}
|
||||
|
||||
nv.SetMapIndex(key, elem)
|
||||
return
|
||||
}
|
||||
|
||||
func (fix *fixState) fixPtr(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
if v.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
vst := visit{
|
||||
p: v.Pointer(),
|
||||
t: v.Type(),
|
||||
}
|
||||
|
||||
if _, ok := fix.invalid[vst]; ok {
|
||||
panic(fmt.Errorf("go-clone: <bug> invalid pointers must have been fixed in other methods"))
|
||||
}
|
||||
|
||||
if _, ok := fix.fixed[vst]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
fix.fixed[vst] = struct{}{}
|
||||
|
||||
elem := v.Elem()
|
||||
_, changed = fix.fix(elem)
|
||||
return
|
||||
}
|
||||
|
||||
func (fix *fixState) fixSlice(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
if v.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
et := t.Elem()
|
||||
kind := et.Kind()
|
||||
|
||||
if fix.allocator.isScalar(kind) {
|
||||
return
|
||||
}
|
||||
|
||||
l := v.Len()
|
||||
p := unsafe.Pointer(v.Pointer())
|
||||
vst := visit{
|
||||
p: uintptr(p),
|
||||
extra: l,
|
||||
t: t,
|
||||
}
|
||||
|
||||
if _, ok := fix.fixed[vst]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
fix.fixed[vst] = struct{}{}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
elem := v.Index(i)
|
||||
var fixed reflect.Value
|
||||
c := 0
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: elem.Pointer(),
|
||||
t: et,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
fixed = nv
|
||||
} else {
|
||||
fixed, c = fix.fixPtr(elem)
|
||||
}
|
||||
} else {
|
||||
fixed, c = fix.fix(elem)
|
||||
}
|
||||
|
||||
changed += c
|
||||
|
||||
if fixed.IsValid() {
|
||||
sz := et.Size()
|
||||
elemPtr := unsafe.Pointer(uintptr(p) + sz*uintptr(i))
|
||||
shadowCopy(fixed, elemPtr)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fix *fixState) fixStruct(v reflect.Value) (copied reflect.Value, changed int) {
|
||||
t := v.Type()
|
||||
st := fix.allocator.loadStructType(t)
|
||||
|
||||
if len(st.PointerFields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pf := range st.PointerFields {
|
||||
i := int(pf.Index)
|
||||
field := v.Field(i)
|
||||
|
||||
ft := field.Type()
|
||||
|
||||
if ft.Kind() == reflect.Ptr {
|
||||
vst := visit{
|
||||
p: field.Pointer(),
|
||||
t: ft,
|
||||
}
|
||||
|
||||
if nv, ok := fix.invalid[vst]; ok {
|
||||
// If v is not addressable, a new struct must be allocated.
|
||||
// Don't do it unless there is no other choices.
|
||||
if !v.CanAddr() {
|
||||
copied = fix.new(t).Elem()
|
||||
shadowCopy(v, unsafe.Pointer(copied.Addr().Pointer()))
|
||||
_, changed = fix.fixStruct(copied)
|
||||
return
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(v.Addr().Pointer())
|
||||
p := unsafe.Pointer(uintptr(ptr) + pf.Offset)
|
||||
shadowCopy(nv, p)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fixed, c := fix.fix(field)
|
||||
changed += c
|
||||
|
||||
if fixed.IsValid() {
|
||||
// If v is not addressable, a new struct must be allocated.
|
||||
// Don't do it unless there is no other choices.
|
||||
if !v.CanAddr() {
|
||||
copied = fix.new(t).Elem()
|
||||
shadowCopy(v, unsafe.Pointer(copied.Addr().Pointer()))
|
||||
_, changed = fix.fixStruct(copied)
|
||||
return
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(v.Addr().Pointer())
|
||||
p := unsafe.Pointer(uintptr(ptr) + pf.Offset)
|
||||
shadowCopy(fixed, p)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
// Cloner implements clone API with given allocator.
|
||||
type Cloner struct {
|
||||
allocator *Allocator
|
||||
}
|
||||
|
||||
// MakeCloner creates a cloner with given allocator.
|
||||
func MakeCloner(allocator *Allocator) Cloner {
|
||||
return Cloner{
|
||||
allocator: allocator,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone clones v with given allocator.
|
||||
func (c Cloner) Clone(v interface{}) interface{} {
|
||||
return clone(c.allocator, v)
|
||||
}
|
||||
|
||||
// CloneSlowly clones v with given allocator.
|
||||
// It can clone v with cycle pointer.
|
||||
func (c Cloner) CloneSlowly(v interface{}) interface{} {
|
||||
return cloneSlowly(c.allocator, v)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
import "reflect"
|
||||
|
||||
// As golint reports warning on possible misuse of these headers,
|
||||
// avoid to use these header types directly to silience golint.
|
||||
|
||||
type sliceHeader reflect.SliceHeader
|
||||
type stringHeader reflect.StringHeader
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const sizeOfPointers = unsafe.Sizeof((interface{})(0)) / unsafe.Sizeof(uintptr(0))
|
||||
|
||||
// interfaceData is the underlying data of an interface.
|
||||
// As the reflect.Value's interfaceData method is deprecated,
|
||||
// it may be broken in any Go release.
|
||||
// It's better to create a custom to hold the data.
|
||||
//
|
||||
// The type of interfaceData fields must be poniters.
|
||||
// It's a way to cheat Go compile to generate calls to write barrier
|
||||
// when copying interfaces.
|
||||
type interfaceData struct {
|
||||
_ [sizeOfPointers]unsafe.Pointer
|
||||
}
|
||||
|
||||
var reflectValuePtrOffset uintptr
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(reflect.Value{})
|
||||
found := false
|
||||
fields := t.NumField()
|
||||
|
||||
for i := 0; i < fields; i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
if field.Type.Kind() == reflect.UnsafePointer {
|
||||
found = true
|
||||
reflectValuePtrOffset = field.Offset
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
panic("go-clone: fail to find internal ptr field in reflect.Value")
|
||||
}
|
||||
}
|
||||
|
||||
// parseReflectValue returns the underlying interface data in a reflect value.
|
||||
// It assumes that v is an interface value.
|
||||
func parseReflectValue(v reflect.Value) interfaceData {
|
||||
pv := (unsafe.Pointer)(uintptr(unsafe.Pointer(&v)) + reflectValuePtrOffset)
|
||||
ptr := *(*unsafe.Pointer)(pv)
|
||||
return *(*interfaceData)(ptr)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.12
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type iter struct {
|
||||
m reflect.Value
|
||||
k reflect.Value
|
||||
keys []reflect.Value
|
||||
}
|
||||
|
||||
func mapIter(m reflect.Value) *iter {
|
||||
return &iter{
|
||||
m: m,
|
||||
keys: m.MapKeys(),
|
||||
}
|
||||
}
|
||||
|
||||
func (it *iter) Next() bool {
|
||||
if len(it.keys) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
it.k = it.keys[0]
|
||||
it.keys = it.keys[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *iter) Key() reflect.Value {
|
||||
return it.k
|
||||
}
|
||||
|
||||
func (it *iter) Value() reflect.Value {
|
||||
return it.m.MapIndex(it.k)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.12
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func mapIter(m reflect.Value) *reflect.MapIter {
|
||||
return m.MapRange()
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
// maxByteSize is a large enough value to cheat Go compiler
|
||||
// when converting unsafe address to []byte.
|
||||
// It's not actually used in runtime.
|
||||
//
|
||||
// The value 2^30 is the max value AFAIK to make Go compiler happy on all archs.
|
||||
const maxByteSize = 1 << 30
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type structType struct {
|
||||
ZeroFields []structFieldSize
|
||||
PointerFields []structFieldType
|
||||
fn Func
|
||||
}
|
||||
|
||||
type structFieldSize struct {
|
||||
Offset uintptr // The offset from the beginning of the struct.
|
||||
Size uintptr // The size of the field.
|
||||
}
|
||||
|
||||
type structFieldType struct {
|
||||
Offset uintptr // The offset from the beginning of the struct.
|
||||
Index int // The index of the field.
|
||||
}
|
||||
|
||||
var zeroStructType = structType{}
|
||||
|
||||
func init() {
|
||||
// Some well-known scalar-like structs.
|
||||
MarkAsScalar(reflect.TypeOf(time.Time{}))
|
||||
MarkAsScalar(reflect.TypeOf(reflect.Value{}))
|
||||
|
||||
// Special case for elliptic.Curve which is used by TLS ECC certificate.
|
||||
// Package crypto/tls uses elliptic.Curve as enum values
|
||||
// so that they should be treated as opaque pointers.
|
||||
//
|
||||
// As elliptic.Curve is an interface, it can be *elliptic.CurveParam or elliptic.p256Curve.
|
||||
MarkAsOpaquePointer(reflect.TypeOf(&elliptic.CurveParams{}))
|
||||
curves := []elliptic.Curve{
|
||||
elliptic.P224(),
|
||||
elliptic.P256(),
|
||||
elliptic.P384(),
|
||||
elliptic.P521(),
|
||||
}
|
||||
|
||||
for _, curve := range curves {
|
||||
MarkAsOpaquePointer(reflect.ValueOf(curve).Type())
|
||||
}
|
||||
|
||||
// Special case for reflect.Type (actually *reflect.rtype):
|
||||
// The *reflect.rtype should not be copied as it is immutable and
|
||||
// may point to a variable that actual type is not reflect.rtype,
|
||||
// e.g. *reflect.arrayType or *reflect.chanType.
|
||||
MarkAsOpaquePointer(reflect.TypeOf(reflect.TypeOf(0)))
|
||||
|
||||
// Some well-known no-copy structs.
|
||||
//
|
||||
// Almost all structs defined in package "sync" and "sync/atomic" are set
|
||||
// except `sync.Once` which can be safely cloned with a correct done value.
|
||||
SetCustomFunc(reflect.TypeOf(sync.Mutex{}), emptyCloneFunc)
|
||||
SetCustomFunc(reflect.TypeOf(sync.RWMutex{}), emptyCloneFunc)
|
||||
SetCustomFunc(reflect.TypeOf(sync.WaitGroup{}), emptyCloneFunc)
|
||||
SetCustomFunc(reflect.TypeOf(sync.Cond{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
// Copy the New func from old value.
|
||||
oldL := old.FieldByName("L")
|
||||
newL := allocator.Clone(oldL)
|
||||
new.FieldByName("L").Set(newL)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(sync.Pool{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
// Copy the New func from old value.
|
||||
oldFn := old.FieldByName("New")
|
||||
newFn := allocator.Clone(oldFn)
|
||||
new.FieldByName("New").Set(newFn)
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(sync.Map{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone all values inside sync.Map.
|
||||
oldMap := old.Addr().Interface().(*sync.Map)
|
||||
newMap := new.Addr().Interface().(*sync.Map)
|
||||
oldMap.Range(func(key, value interface{}) bool {
|
||||
k := clone(allocator, key)
|
||||
v := clone(allocator, value)
|
||||
newMap.Store(k, v)
|
||||
return true
|
||||
})
|
||||
})
|
||||
SetCustomFunc(reflect.TypeOf(atomic.Value{}), func(allocator *Allocator, old, new reflect.Value) {
|
||||
if !old.CanAddr() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone value inside atomic.Value.
|
||||
oldValue := old.Addr().Interface().(*atomic.Value)
|
||||
newValue := new.Addr().Interface().(*atomic.Value)
|
||||
|
||||
if v := oldValue.Load(); v != nil {
|
||||
cloned := clone(allocator, v)
|
||||
newValue.Store(cloned)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MarkAsScalar marks t as a scalar type in heap allocator,
|
||||
// so that all clone methods will copy t by value.
|
||||
// If t is not struct or pointer to struct, MarkAsScalar ignores t.
|
||||
//
|
||||
// In the most cases, it's not necessary to call it explicitly.
|
||||
// If a struct type contains scalar type fields only, the struct will be marked as scalar automatically.
|
||||
//
|
||||
// Here is a list of types marked as scalar by default:
|
||||
// - time.Time
|
||||
// - reflect.Value
|
||||
func MarkAsScalar(t reflect.Type) {
|
||||
defaultAllocator.MarkAsScalar(t)
|
||||
}
|
||||
|
||||
// MarkAsOpaquePointer marks t as an opaque pointer in heap allocator,
|
||||
// so that all clone methods will copy t by value.
|
||||
// If t is not a pointer, MarkAsOpaquePointer ignores t.
|
||||
//
|
||||
// Here is a list of types marked as opaque pointers by default:
|
||||
// - `elliptic.Curve`, which is `*elliptic.CurveParam` or `elliptic.p256Curve`;
|
||||
// - `reflect.Type`, which is `*reflect.rtype` defined in `runtime`.
|
||||
func MarkAsOpaquePointer(t reflect.Type) {
|
||||
defaultAllocator.MarkAsOpaquePointer(t)
|
||||
}
|
||||
|
||||
// Func is a custom func to clone value from old to new.
|
||||
// The new is a zero value
|
||||
// which `new.CanSet()` and `new.CanAddr()` is guaranteed to be true.
|
||||
//
|
||||
// Func must update the new to return result.
|
||||
type Func func(allocator *Allocator, old, new reflect.Value)
|
||||
|
||||
// emptyCloneFunc is used to disable shadow copy.
|
||||
// It's useful when cloning sync.Mutex as cloned value must be a zero value.
|
||||
func emptyCloneFunc(allocator *Allocator, old, new reflect.Value) {}
|
||||
|
||||
// SetCustomFunc sets a custom clone function for type t in heap allocator.
|
||||
// If t is not struct or pointer to struct, SetCustomFunc ignores t.
|
||||
//
|
||||
// If fn is nil, remove the custom clone function for type t.
|
||||
func SetCustomFunc(t reflect.Type, fn Func) {
|
||||
defaultAllocator.SetCustomFunc(t, fn)
|
||||
}
|
||||
|
||||
// Init creates a new value of src.Type() and shadow copies all content from src.
|
||||
// If noCustomFunc is set to true, custom clone function will be ignored.
|
||||
//
|
||||
// Init returns true if the value is cloned by a custom func.
|
||||
// Caller should skip cloning struct fields in depth.
|
||||
func (st *structType) Init(allocator *Allocator, src, nv reflect.Value, noCustomFunc bool) (done bool) {
|
||||
dst := nv.Elem()
|
||||
|
||||
if !noCustomFunc && st.fn != nil {
|
||||
if !src.CanInterface() {
|
||||
src = forceClearROFlag(src)
|
||||
}
|
||||
|
||||
st.fn(allocator, src, dst)
|
||||
done = true
|
||||
return
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(nv.Pointer())
|
||||
shadowCopy(src, ptr)
|
||||
done = len(st.PointerFields) == 0
|
||||
return
|
||||
}
|
||||
|
||||
func (st *structType) CanShadowCopy() bool {
|
||||
return len(st.PointerFields) == 0 && st.fn == nil
|
||||
}
|
||||
|
||||
// IsScalar returns true if k should be considered as a scalar type.
|
||||
//
|
||||
// For the sake of performance, string is considered as a scalar type unless arena is enabled.
|
||||
// If we need to deep copy string value in some cases, we can create a new allocator with custom isScalar function
|
||||
// in which we can return false when k is reflect.String.
|
||||
//
|
||||
// // Create a new allocator which treats string as non-scalar type.
|
||||
// allocator := NewAllocator(nil, &AllocatorMethods{
|
||||
// IsScalar: func(k reflect.Kind) bool {
|
||||
// return k != reflect.String && IsScalar(k)
|
||||
// },
|
||||
// })
|
||||
func IsScalar(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.Complex64, reflect.Complex128,
|
||||
reflect.Func,
|
||||
reflect.UnsafePointer,
|
||||
reflect.Invalid:
|
||||
return true
|
||||
|
||||
case reflect.String:
|
||||
// If arena is not enabled, string can be copied as scalar safely
|
||||
// as it's immutable by design.
|
||||
return !arenaIsEnabled
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func copyScalarValue(src reflect.Value) reflect.Value {
|
||||
if src.CanInterface() {
|
||||
return src
|
||||
}
|
||||
|
||||
dst := newScalarValue(src)
|
||||
return dst.Convert(src.Type())
|
||||
}
|
||||
|
||||
func newScalarValue(src reflect.Value) reflect.Value {
|
||||
// src is an unexported field value. Copy its value.
|
||||
switch src.Kind() {
|
||||
case reflect.Bool:
|
||||
return reflect.ValueOf(src.Bool())
|
||||
|
||||
case reflect.Int:
|
||||
return reflect.ValueOf(int(src.Int()))
|
||||
case reflect.Int8:
|
||||
return reflect.ValueOf(int8(src.Int()))
|
||||
case reflect.Int16:
|
||||
return reflect.ValueOf(int16(src.Int()))
|
||||
case reflect.Int32:
|
||||
return reflect.ValueOf(int32(src.Int()))
|
||||
case reflect.Int64:
|
||||
return reflect.ValueOf(src.Int())
|
||||
|
||||
case reflect.Uint:
|
||||
return reflect.ValueOf(uint(src.Uint()))
|
||||
case reflect.Uint8:
|
||||
return reflect.ValueOf(uint8(src.Uint()))
|
||||
case reflect.Uint16:
|
||||
return reflect.ValueOf(uint16(src.Uint()))
|
||||
case reflect.Uint32:
|
||||
return reflect.ValueOf(uint32(src.Uint()))
|
||||
case reflect.Uint64:
|
||||
return reflect.ValueOf(src.Uint())
|
||||
case reflect.Uintptr:
|
||||
return reflect.ValueOf(uintptr(src.Uint()))
|
||||
|
||||
case reflect.Float32:
|
||||
return reflect.ValueOf(float32(src.Float()))
|
||||
case reflect.Float64:
|
||||
return reflect.ValueOf(src.Float())
|
||||
|
||||
case reflect.Complex64:
|
||||
return reflect.ValueOf(complex64(src.Complex()))
|
||||
case reflect.Complex128:
|
||||
return reflect.ValueOf(src.Complex())
|
||||
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(src.String())
|
||||
case reflect.Func:
|
||||
t := src.Type()
|
||||
|
||||
if src.IsNil() {
|
||||
return reflect.Zero(t)
|
||||
}
|
||||
|
||||
// Don't use this trick unless we have no choice.
|
||||
return forceClearROFlag(src)
|
||||
case reflect.UnsafePointer:
|
||||
return reflect.ValueOf(unsafe.Pointer(src.Pointer()))
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("go-clone: <bug> impossible type `%v` when cloning private field", src.Type()))
|
||||
}
|
||||
|
||||
var typeOfInterface = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
|
||||
// forceClearROFlag clears all RO flags in v to make v accessible.
|
||||
// It's a hack based on the fact that InterfaceData is always available on RO data.
|
||||
// This hack can be broken in any Go version.
|
||||
// Don't use it unless we have no choice, e.g. copying func in some edge cases.
|
||||
func forceClearROFlag(v reflect.Value) reflect.Value {
|
||||
var i interface{}
|
||||
indirect := 0
|
||||
|
||||
// Save flagAddr.
|
||||
for v.CanAddr() {
|
||||
v = v.Addr()
|
||||
indirect++
|
||||
}
|
||||
|
||||
v = v.Convert(typeOfInterface)
|
||||
nv := reflect.ValueOf(&i)
|
||||
*(*interfaceData)(unsafe.Pointer(nv.Pointer())) = parseReflectValue(v)
|
||||
cleared := nv.Elem().Elem()
|
||||
|
||||
for indirect > 0 {
|
||||
cleared = cleared.Elem()
|
||||
indirect--
|
||||
}
|
||||
|
||||
return cleared
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright 2019 Huan Du. All rights reserved.
|
||||
// Licensed under the MIT license that can be found in the LICENSE file.
|
||||
|
||||
package clone
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash/crc64"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
sizeOfChecksum = unsafe.Sizeof(uint64(0))
|
||||
|
||||
crc64Table = crc64.MakeTable(crc64.ECMA)
|
||||
|
||||
cachedWrapperTypes sync.Map
|
||||
)
|
||||
|
||||
// Wrap creates a wrapper of v, which must be a pointer.
|
||||
// If v is not a pointer, Wrap simply returns v and do nothing.
|
||||
//
|
||||
// The wrapper is a deep clone of v's value. It holds a shadow copy to v internally.
|
||||
//
|
||||
// t := &T{Foo: 123}
|
||||
// v := Wrap(t).(*T) // v is a clone of t.
|
||||
// reflect.DeepEqual(t, v) == true // v equals t.
|
||||
// v.Foo = 456 // v.Foo is changed, but t.Foo doesn't change.
|
||||
// orig := Unwrap(v) // Use `Unwrap` to discard wrapper and return original value, which is t.
|
||||
// orig.(*T) == t // orig and t is exactly the same.
|
||||
// Undo(v) // Use `Undo` to discard any change on v.
|
||||
// v.Foo == t.Foo // Now, the value of v and t are the same again.
|
||||
func Wrap(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return v
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
pt := val.Type()
|
||||
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return v
|
||||
}
|
||||
|
||||
t := pt.Elem()
|
||||
elem := val.Elem()
|
||||
ptr := unsafe.Pointer(val.Pointer())
|
||||
cache, ok := cachedWrapperTypes.Load(t)
|
||||
|
||||
if !ok {
|
||||
cache = reflect.StructOf([]reflect.StructField{
|
||||
{
|
||||
Name: "T",
|
||||
Type: t,
|
||||
Anonymous: true,
|
||||
},
|
||||
{
|
||||
Name: "Checksum",
|
||||
Type: reflect.TypeOf(uint64(0)),
|
||||
},
|
||||
{
|
||||
Name: "Origin",
|
||||
Type: pt,
|
||||
},
|
||||
})
|
||||
cachedWrapperTypes.Store(t, cache)
|
||||
}
|
||||
|
||||
wrapperType := cache.(reflect.Type)
|
||||
pw := defaultAllocator.New(wrapperType)
|
||||
|
||||
wrapperPtr := unsafe.Pointer(pw.Pointer())
|
||||
wrapper := pw.Elem()
|
||||
|
||||
// Equivalent code: wrapper.T = Clone(v)
|
||||
field := wrapper.Field(0)
|
||||
field.Set(heapCloneState.clone(elem))
|
||||
|
||||
// Equivalent code: wrapper.Checksum = makeChecksum(v)
|
||||
checksumPtr := unsafe.Pointer((uintptr(wrapperPtr) + t.Size()))
|
||||
*(*uint64)(checksumPtr) = makeChecksum(t, uintptr(wrapperPtr), uintptr(ptr))
|
||||
|
||||
// Equivalent code: wrapper.Origin = v
|
||||
originPtr := unsafe.Pointer((uintptr(wrapperPtr) + t.Size() + sizeOfChecksum))
|
||||
*(*uintptr)(originPtr) = uintptr(ptr)
|
||||
|
||||
return field.Addr().Interface()
|
||||
}
|
||||
|
||||
func validateChecksum(t reflect.Type, ptr unsafe.Pointer) bool {
|
||||
pw := uintptr(ptr)
|
||||
orig := uintptr(getOrigin(t, ptr))
|
||||
checksum := *(*uint64)(unsafe.Pointer(uintptr(ptr) + t.Size()))
|
||||
expected := makeChecksum(t, pw, orig)
|
||||
|
||||
return checksum == expected
|
||||
}
|
||||
|
||||
func makeChecksum(t reflect.Type, pw uintptr, orig uintptr) uint64 {
|
||||
var data [binary.MaxVarintLen64 * 2]byte
|
||||
binary.PutUvarint(data[:binary.MaxVarintLen64], uint64(pw))
|
||||
binary.PutUvarint(data[binary.MaxVarintLen64:], uint64(orig))
|
||||
return crc64.Checksum(data[:], crc64Table)
|
||||
}
|
||||
|
||||
func getOrigin(t reflect.Type, ptr unsafe.Pointer) unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(uintptr(ptr) + t.Size() + sizeOfChecksum))
|
||||
}
|
||||
|
||||
// Unwrap returns v's original value if v is a wrapped value.
|
||||
// Otherwise, simply returns v itself.
|
||||
func Unwrap(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return v
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
if !isWrapped(val) {
|
||||
return v
|
||||
}
|
||||
|
||||
origVal := origin(val)
|
||||
return origVal.Interface()
|
||||
}
|
||||
|
||||
func origin(val reflect.Value) reflect.Value {
|
||||
pt := val.Type()
|
||||
t := pt.Elem()
|
||||
ptr := unsafe.Pointer(val.Pointer())
|
||||
orig := getOrigin(t, ptr)
|
||||
origVal := reflect.NewAt(t, orig)
|
||||
return origVal
|
||||
}
|
||||
|
||||
// Undo discards any change made in wrapped value.
|
||||
// If v is not a wrapped value, nothing happens.
|
||||
func Undo(v interface{}) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
if !isWrapped(val) {
|
||||
return
|
||||
}
|
||||
|
||||
origVal := origin(val)
|
||||
elem := val.Elem()
|
||||
elem.Set(heapCloneState.clone(origVal.Elem()))
|
||||
}
|
||||
|
||||
func isWrapped(val reflect.Value) bool {
|
||||
pt := val.Type()
|
||||
|
||||
if pt.Kind() != reflect.Ptr {
|
||||
return false
|
||||
}
|
||||
|
||||
t := pt.Elem()
|
||||
ptr := unsafe.Pointer(val.Pointer())
|
||||
return validateChecksum(t, ptr)
|
||||
}
|
||||
+46
@@ -17,6 +17,7 @@
|
||||
- [Use `sql.Named` in a builder](#use-sqlnamed-in-a-builder)
|
||||
- [Argument modifiers](#argument-modifiers)
|
||||
- [Freestyle builder](#freestyle-builder)
|
||||
- [Clone builders](#clone-builders)
|
||||
- [Using special syntax to build SQL](#using-special-syntax-to-build-sql)
|
||||
- [Interpolate `args` in the `sql`](#interpolate-args-in-the-sql)
|
||||
- [License](#license)
|
||||
@@ -381,6 +382,51 @@ fmt.Println(args)
|
||||
// [1 2]
|
||||
```
|
||||
|
||||
### Clone builders
|
||||
|
||||
The `Clone` methods make any builder reusable as a template. You can create a partially initialized builder once (even as a global), then call `Clone()` to get an independent copy to customize per request. This avoids repeated setup while keeping shared templates immutable and safe for concurrent use.
|
||||
|
||||
Supported builders with `Clone`:
|
||||
|
||||
- [CreateTableBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#CreateTableBuilder)
|
||||
- [CTEBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#CTEBuilder)
|
||||
- [CTEQueryBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#CTEQueryBuilder)
|
||||
- [DeleteBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#DeleteBuilder)
|
||||
- [InsertBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#InsertBuilder)
|
||||
- [SelectBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#SelectBuilder)
|
||||
- [UnionBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#UnionBuilder)
|
||||
- [UpdateBuilder](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#UpdateBuilder)
|
||||
|
||||
Example: define a global SELECT template and clone it per call
|
||||
|
||||
```go
|
||||
package yourpkg
|
||||
|
||||
import "github.com/huandu/go-sqlbuilder"
|
||||
|
||||
// Global template — safe to reuse by cloning.
|
||||
var baseUserSelect = sqlbuilder.NewSelectBuilder().
|
||||
Select("id", "name", "email").
|
||||
From("users").
|
||||
Where("deleted_at IS NULL")
|
||||
|
||||
func ListActiveUsers(limit, offset int) (string, []interface{}) {
|
||||
sb := baseUserSelect.Clone() // independent copy
|
||||
sb.OrderBy("id").Asc()
|
||||
sb.Limit(limit).Offset(offset)
|
||||
return sb.Build()
|
||||
}
|
||||
|
||||
func GetActiveUserByID(id int64) (string, []interface{}) {
|
||||
sb := baseUserSelect.Clone() // start from the same template
|
||||
sb.Where(sb.Equal("id", id))
|
||||
sb.Limit(1)
|
||||
return sb.Build()
|
||||
}
|
||||
```
|
||||
|
||||
The same template pattern applies to other builders. For example, keep a base `UpdateBuilder` with the table and common `SET` clauses, or a base `CTEBuilder` defining reusable CTEs, then `Clone()` and add query-specific `WHERE`/`ORDER BY`/`LIMIT`/`RETURNING` as needed.
|
||||
|
||||
### Using special syntax to build SQL
|
||||
|
||||
The `sqlbuilder` package incorporates special syntax for representing uncompiled SQL internally. To leverage this syntax for developing customized tools, the `Build` function can be utilized to compile it with the necessary arguments.
|
||||
|
||||
+79
-8
@@ -6,9 +6,12 @@ package sqlbuilder
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
// Args stores arguments associated with a SQL.
|
||||
@@ -17,7 +20,7 @@ type Args struct {
|
||||
Flavor Flavor
|
||||
|
||||
indexBase int
|
||||
argValues []interface{}
|
||||
argValues *valueStore
|
||||
namedArgs map[string]int
|
||||
sqlNamedArgs map[string]int
|
||||
onlyNamed bool
|
||||
@@ -48,7 +51,7 @@ func (args *Args) Add(arg interface{}) string {
|
||||
}
|
||||
|
||||
func (args *Args) add(arg interface{}) int {
|
||||
idx := len(args.argValues) + args.indexBase
|
||||
idx := args.argValues.Len() + args.indexBase
|
||||
|
||||
switch a := arg.(type) {
|
||||
case sql.NamedArg:
|
||||
@@ -57,7 +60,7 @@ func (args *Args) add(arg interface{}) int {
|
||||
}
|
||||
|
||||
if p, ok := args.sqlNamedArgs[a.Name]; ok {
|
||||
arg = args.argValues[p]
|
||||
arg = args.argValues.Load(p)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ func (args *Args) add(arg interface{}) int {
|
||||
}
|
||||
|
||||
if p, ok := args.namedArgs[a.name]; ok {
|
||||
arg = args.argValues[p]
|
||||
arg = args.argValues.Load(p)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -78,10 +81,31 @@ func (args *Args) add(arg interface{}) int {
|
||||
return idx
|
||||
}
|
||||
|
||||
args.argValues = append(args.argValues, arg)
|
||||
if args.argValues == nil {
|
||||
args.argValues = &valueStore{}
|
||||
}
|
||||
|
||||
args.argValues.Add(arg)
|
||||
return idx
|
||||
}
|
||||
|
||||
// Replace replaces the placeholder with arg.
|
||||
//
|
||||
// The placeholder must be the value returned by `Add`, e.g. "$1".
|
||||
// If the placeholder is not found, this method does nothing.
|
||||
func (args *Args) Replace(placeholder string, arg interface{}) {
|
||||
dollar := strings.IndexRune(placeholder, '$')
|
||||
|
||||
if dollar != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if i, err := strconv.Atoi(placeholder[1:]); err == nil {
|
||||
i -= args.indexBase
|
||||
args.argValues.Set(i, arg)
|
||||
}
|
||||
}
|
||||
|
||||
// Compile compiles builder's format to standard sql and returns associated args.
|
||||
//
|
||||
// The format string uses a special syntax to represent arguments.
|
||||
@@ -201,14 +225,14 @@ func (args *Args) compileDigits(ctx *argsCompileContext, format string, offset i
|
||||
}
|
||||
|
||||
func (args *Args) compileSuccessive(ctx *argsCompileContext, format string, offset int) (string, int) {
|
||||
if offset < 0 || offset >= len(args.argValues) {
|
||||
if offset < 0 || offset >= args.argValues.Len() {
|
||||
ctx.WriteString("/* INVALID ARG $")
|
||||
ctx.WriteString(strconv.Itoa(offset))
|
||||
ctx.WriteString(" */")
|
||||
return format, offset
|
||||
}
|
||||
|
||||
arg := args.argValues[offset]
|
||||
arg := args.argValues.Load(offset)
|
||||
ctx.WriteValue(arg)
|
||||
|
||||
return format, offset + 1
|
||||
@@ -245,7 +269,7 @@ func (args *Args) mergeSQLNamedArgs(ctx *argsCompileContext) []interface{} {
|
||||
sort.Ints(ints)
|
||||
|
||||
for _, i := range ints {
|
||||
values = append(values, args.argValues[i])
|
||||
values = append(values, args.argValues.Load(i))
|
||||
}
|
||||
|
||||
return values
|
||||
@@ -364,3 +388,50 @@ func (ctx *argsCompileContext) WriteValues(values []interface{}, sep string) {
|
||||
ctx.WriteValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
type valueStore struct {
|
||||
Values []interface{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The values in valueStore should be shadow-copied to avoid unnecessary cost.
|
||||
t := reflect.TypeOf(valueStore{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
values := old.FieldByName("Values")
|
||||
newValues := allocator.Clone(values)
|
||||
new.FieldByName("Values").Set(newValues)
|
||||
})
|
||||
}
|
||||
|
||||
func (as *valueStore) Len() int {
|
||||
if as == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(as.Values)
|
||||
}
|
||||
|
||||
// Add adds an arg to argsValues and returns its index.
|
||||
func (as *valueStore) Add(arg interface{}) int {
|
||||
as.Values = append(as.Values, arg)
|
||||
return len(as.Values) - 1
|
||||
}
|
||||
|
||||
// Set sets the arg value by index.
|
||||
func (as *valueStore) Set(index int, arg interface{}) {
|
||||
if as == nil || index < 0 || index >= len(as.Values) {
|
||||
return
|
||||
}
|
||||
|
||||
as.Values[index] = arg
|
||||
}
|
||||
|
||||
// Load returns the arg value by index.
|
||||
// Returns nil if index is out of range or as itself is nil.
|
||||
func (as *valueStore) Load(index int) interface{} {
|
||||
if as == nil || index < 0 || index >= len(as.Values) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return as.Values[index]
|
||||
}
|
||||
|
||||
+8
@@ -5,6 +5,8 @@ package sqlbuilder
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -29,6 +31,12 @@ func newCreateTableBuilder() *CreateTableBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of CreateTableBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (ctb *CreateTableBuilder) Clone() *CreateTableBuilder {
|
||||
return clone.Clone(ctb).(*CreateTableBuilder)
|
||||
}
|
||||
|
||||
// CreateTableBuilder is a builder to build CREATE TABLE.
|
||||
type CreateTableBuilder struct {
|
||||
verb string
|
||||
|
||||
+26
-1
@@ -3,6 +3,12 @@
|
||||
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
cteMarkerInit injectionMarker = iota
|
||||
cteMarkerAfterWith
|
||||
@@ -25,6 +31,25 @@ func newCTEBuilder() *CTEBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of CTEBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (cteb *CTEBuilder) Clone() *CTEBuilder {
|
||||
return clone.Clone(cteb).(*CTEBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(CTEBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
cteb := cloned.Addr().Interface().(*CTEBuilder)
|
||||
for i, b := range cteb.queries {
|
||||
cteb.args.Replace(cteb.queryBuilderVars[i], b)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// CTEBuilder is a CTE (Common Table Expression) builder.
|
||||
type CTEBuilder struct {
|
||||
recursive bool
|
||||
@@ -47,7 +72,7 @@ func (cteb *CTEBuilder) With(queries ...*CTEQueryBuilder) *CTEBuilder {
|
||||
queryBuilderVars = append(queryBuilderVars, cteb.args.Add(query))
|
||||
}
|
||||
|
||||
cteb.queries = queries
|
||||
cteb.queries = append([]*CTEQueryBuilder(nil), queries...)
|
||||
cteb.queryBuilderVars = queryBuilderVars
|
||||
cteb.marker = cteMarkerAfterWith
|
||||
return cteb
|
||||
|
||||
+25
@@ -3,6 +3,12 @@
|
||||
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
cteQueryMarkerInit injectionMarker = iota
|
||||
cteQueryMarkerAfterTable
|
||||
@@ -29,10 +35,28 @@ func newCTEQueryBuilder() *CTEQueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of CTEQueryBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (ctetb *CTEQueryBuilder) Clone() *CTEQueryBuilder {
|
||||
return clone.Clone(ctetb).(*CTEQueryBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(CTEQueryBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
ctetb := cloned.Addr().Interface().(*CTEQueryBuilder)
|
||||
ctetb.args.Replace(ctetb.builderVar, ctetb.builder)
|
||||
})
|
||||
}
|
||||
|
||||
// CTEQueryBuilder is a builder to build one table in CTE (Common Table Expression).
|
||||
type CTEQueryBuilder struct {
|
||||
name string
|
||||
cols []string
|
||||
builder Builder
|
||||
builderVar string
|
||||
|
||||
// if true, this query's table name will be automatically added to the table list
|
||||
@@ -62,6 +86,7 @@ func (ctetb *CTEQueryBuilder) Table(name string, cols ...string) *CTEQueryBuilde
|
||||
|
||||
// As sets the builder to select data.
|
||||
func (ctetb *CTEQueryBuilder) As(builder Builder) *CTEQueryBuilder {
|
||||
ctetb.builder = builder
|
||||
ctetb.builderVar = ctetb.args.Add(builder)
|
||||
ctetb.marker = cteQueryMarkerAfterAs
|
||||
return ctetb
|
||||
|
||||
+24
@@ -3,6 +3,12 @@
|
||||
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
deleteMarkerInit injectionMarker = iota
|
||||
deleteMarkerAfterWith
|
||||
@@ -33,6 +39,24 @@ func newDeleteBuilder() *DeleteBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of DeleteBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (db *DeleteBuilder) Clone() *DeleteBuilder {
|
||||
return clone.Clone(db).(*DeleteBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(DeleteBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
db := cloned.Addr().Interface().(*DeleteBuilder)
|
||||
db.args.Replace(db.whereClauseExpr, db.whereClauseProxy)
|
||||
db.args.Replace(db.cteBuilderVar, db.cteBuilder)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBuilder is a builder to build DELETE.
|
||||
type DeleteBuilder struct {
|
||||
*WhereClause
|
||||
|
||||
+8
@@ -6,6 +6,8 @@ package sqlbuilder
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,6 +33,12 @@ func newInsertBuilder() *InsertBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of InsertBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (ib *InsertBuilder) Clone() *InsertBuilder {
|
||||
return clone.Clone(ib).(*InsertBuilder)
|
||||
}
|
||||
|
||||
// InsertBuilder is a builder to build INSERT.
|
||||
type InsertBuilder struct {
|
||||
verb string
|
||||
|
||||
+21
@@ -5,7 +5,10 @@ package sqlbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,6 +58,24 @@ func newSelectBuilder() *SelectBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of SelectBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (sb *SelectBuilder) Clone() *SelectBuilder {
|
||||
return clone.Clone(sb).(*SelectBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(SelectBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
sb := cloned.Addr().Interface().(*SelectBuilder)
|
||||
sb.args.Replace(sb.whereClauseExpr, sb.whereClauseProxy)
|
||||
sb.args.Replace(sb.cteBuilderVar, sb.cteBuilder)
|
||||
})
|
||||
}
|
||||
|
||||
// SelectBuilder is a builder to build SELECT.
|
||||
type SelectBuilder struct {
|
||||
*WhereClause
|
||||
|
||||
+144
-6
@@ -3,6 +3,13 @@
|
||||
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
unionDistinct = " UNION " // Default union type is DISTINCT.
|
||||
unionAll = " UNION ALL "
|
||||
@@ -27,15 +34,36 @@ func newUnionBuilder() *UnionBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of UnionBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (ub *UnionBuilder) Clone() *UnionBuilder {
|
||||
return clone.Clone(ub).(*UnionBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(UnionBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
ub := cloned.Addr().Interface().(*UnionBuilder)
|
||||
for i, b := range ub.builders {
|
||||
ub.args.Replace(ub.builderVars[i], b)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnionBuilder is a builder to build UNION.
|
||||
type UnionBuilder struct {
|
||||
opt string
|
||||
builderVars []string
|
||||
orderByCols []string
|
||||
order string
|
||||
limitVar string
|
||||
offsetVar string
|
||||
|
||||
builders []Builder
|
||||
builderVars []string
|
||||
|
||||
args *Args
|
||||
|
||||
injection *injection
|
||||
@@ -72,6 +100,7 @@ func (ub *UnionBuilder) union(opt string, builders ...Builder) *UnionBuilder {
|
||||
}
|
||||
|
||||
ub.opt = opt
|
||||
ub.builders = append([]Builder(nil), builders...)
|
||||
ub.builderVars = builderVars
|
||||
ub.marker = unionMarkerAfterUnion
|
||||
return ub
|
||||
@@ -140,9 +169,16 @@ func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
|
||||
buf := newStringBuilder()
|
||||
ub.injection.WriteTo(buf, unionMarkerInit)
|
||||
|
||||
nestedSelect := (flavor == Oracle && (len(ub.limitVar) > 0 || len(ub.offsetVar) > 0)) ||
|
||||
(flavor == Informix && len(ub.limitVar) > 0)
|
||||
|
||||
if len(ub.builderVars) > 0 {
|
||||
needParen := flavor != SQLite
|
||||
|
||||
if nestedSelect {
|
||||
buf.WriteLeadingString("SELECT * FROM (")
|
||||
}
|
||||
|
||||
if needParen {
|
||||
buf.WriteLeadingString("(")
|
||||
buf.WriteString(ub.builderVars[0])
|
||||
@@ -164,6 +200,10 @@ func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
|
||||
buf.WriteRune(')')
|
||||
}
|
||||
}
|
||||
|
||||
if nestedSelect {
|
||||
buf.WriteLeadingString(")")
|
||||
}
|
||||
}
|
||||
|
||||
ub.injection.WriteTo(buf, unionMarkerAfterUnion)
|
||||
@@ -180,17 +220,115 @@ func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
|
||||
ub.injection.WriteTo(buf, unionMarkerAfterOrderBy)
|
||||
}
|
||||
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
switch flavor {
|
||||
case MySQL, SQLite, ClickHouse:
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
|
||||
}
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
}
|
||||
}
|
||||
|
||||
case CQL:
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
}
|
||||
|
||||
case PostgreSQL:
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
}
|
||||
|
||||
if ((MySQL == flavor || Informix == flavor) && len(ub.limitVar) > 0) || PostgreSQL == flavor {
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
}
|
||||
|
||||
case Presto:
|
||||
// There might be a hidden constraint in Presto requiring offset to be set before limit.
|
||||
// The select statement documentation (https://prestodb.io/docs/current/sql/select.html)
|
||||
// puts offset before limit, and Trino, which is based on Presto, seems
|
||||
// to require this specific order.
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
}
|
||||
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
}
|
||||
|
||||
case SQLServer:
|
||||
// If ORDER BY is not set, sort column #1 by default.
|
||||
// It's required to make OFFSET...FETCH work.
|
||||
if len(ub.orderByCols) == 0 && (len(ub.limitVar) > 0 || len(ub.offsetVar) > 0) {
|
||||
buf.WriteLeadingString("ORDER BY 1")
|
||||
}
|
||||
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
buf.WriteString(" ROWS")
|
||||
}
|
||||
|
||||
if len(ub.limitVar) > 0 {
|
||||
if len(ub.offsetVar) == 0 {
|
||||
buf.WriteLeadingString("OFFSET 0 ROWS")
|
||||
}
|
||||
|
||||
buf.WriteLeadingString("FETCH NEXT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
buf.WriteString(" ROWS ONLY")
|
||||
}
|
||||
|
||||
case Oracle:
|
||||
// It's required to make OFFSET...FETCH work.
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
buf.WriteString(" ROWS")
|
||||
}
|
||||
|
||||
if len(ub.limitVar) > 0 {
|
||||
if len(ub.offsetVar) == 0 {
|
||||
buf.WriteLeadingString("OFFSET 0 ROWS")
|
||||
}
|
||||
|
||||
buf.WriteLeadingString("FETCH NEXT ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
buf.WriteString(" ROWS ONLY")
|
||||
}
|
||||
|
||||
case Informix:
|
||||
// [SKIP N] FIRST M
|
||||
// M must be greater than 0
|
||||
if len(ub.limitVar) > 0 {
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("SKIP ")
|
||||
buf.WriteString(ub.offsetVar)
|
||||
}
|
||||
|
||||
buf.WriteLeadingString("FIRST ")
|
||||
buf.WriteString(ub.limitVar)
|
||||
}
|
||||
|
||||
case Doris:
|
||||
// #192: Doris doesn't support ? in OFFSET and LIMIT.
|
||||
if len(ub.limitVar) > 0 {
|
||||
buf.WriteLeadingString("LIMIT ")
|
||||
buf.WriteString(fmt.Sprint(ub.args.Value(ub.limitVar)))
|
||||
|
||||
if len(ub.offsetVar) > 0 {
|
||||
buf.WriteLeadingString("OFFSET ")
|
||||
buf.WriteString(fmt.Sprint(ub.args.Value(ub.offsetVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ub.limitVar) > 0 {
|
||||
|
||||
+21
@@ -5,6 +5,9 @@ package sqlbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/huandu/go-clone"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,6 +41,24 @@ func newUpdateBuilder() *UpdateBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of UpdateBuilder.
|
||||
// It's useful when you want to create a base builder and clone it to build similar queries.
|
||||
func (ub *UpdateBuilder) Clone() *UpdateBuilder {
|
||||
return clone.Clone(ub).(*UpdateBuilder)
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(UpdateBuilder{})
|
||||
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
||||
cloned := allocator.CloneSlowly(old)
|
||||
new.Set(cloned)
|
||||
|
||||
ub := cloned.Addr().Interface().(*UpdateBuilder)
|
||||
ub.args.Replace(ub.whereClauseExpr, ub.whereClauseProxy)
|
||||
ub.args.Replace(ub.cteBuilderVar, ub.cteBuilder)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBuilder is a builder to build UPDATE.
|
||||
type UpdateBuilder struct {
|
||||
*WhereClause
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Code coverage profiles and other test artifacts
|
||||
*.out
|
||||
coverage.*
|
||||
*.coverprofile
|
||||
profile.cov
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v1.0.0 18 Aug 2025
|
||||
* Initial release
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 lestrrat-go
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dsigsecp256k1
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
const ECDSAWithSecp256k1AndSHA256 = "ECDSA_WITH_SECP256K1_AND_SHA256"
|
||||
|
||||
// init adds secp256k1 support when the dsig_secp256k1 build tag is used.
|
||||
func init() {
|
||||
// Register ES256K (secp256k1 + SHA256) support using the new API
|
||||
err := dsig.RegisterAlgorithm(ECDSAWithSecp256k1AndSHA256, dsig.AlgorithmInfo{
|
||||
Family: dsig.ECDSA,
|
||||
Meta: dsig.ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic("failed to register secp256k1 algorithm: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// secp256k1Curve returns the secp256k1 curve.
|
||||
func Curve() *secp256k1.KoblitzCurve {
|
||||
return secp256k1.S256()
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Code coverage profiles and other test artifacts
|
||||
*.out
|
||||
coverage.*
|
||||
*.coverprofile
|
||||
profile.cov
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v1.0.0 - 18 Aug 2025
|
||||
* Initial release
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 lestrrat-go
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# github.com/lestrrat-go/dsig [](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml) [](https://pkg.go.dev/github.com/lestrrat-go/dsig) [](https://codecov.io/github/lestrrat-go/dsig?branch=v1)
|
||||
|
||||
Go module providing low-level digital signature operations.
|
||||
|
||||
While there are many standards for generating and verifying digital signatures, the core operations are virtually the same. This module implements the core functionality of digital signature generation / verifications in a framework agnostic way.
|
||||
|
||||
# Features
|
||||
|
||||
* RSA signatures (PKCS1v15 and PSS)
|
||||
* ECDSA signatures (P-256, P-384, P-521)
|
||||
* EdDSA signatures (Ed25519, Ed448)
|
||||
* HMAC signatures (SHA-256, SHA-384, SHA-512)
|
||||
* Support for crypto.Signer interface
|
||||
* Allows for dynamic additions of algorithms in limited cases.
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
<!-- INCLUDE(examples/dsig_readme_example_test.go) -->
|
||||
```go
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
payload := []byte("hello world")
|
||||
|
||||
// RSA signing and verification
|
||||
{
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate RSA key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with RSA-PSS SHA256
|
||||
signature, err := dsig.Sign(privKey, dsig.RSAPSSWithSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with RSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with RSA-PSS SHA256
|
||||
err = dsig.Verify(&privKey.PublicKey, dsig.RSAPSSWithSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify RSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ECDSA signing and verification
|
||||
{
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate ECDSA key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with ECDSA P-256 SHA256
|
||||
signature, err := dsig.Sign(privKey, dsig.ECDSAWithP256AndSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with ECDSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with ECDSA P-256 SHA256
|
||||
err = dsig.Verify(&privKey.PublicKey, dsig.ECDSAWithP256AndSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify ECDSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// EdDSA signing and verification
|
||||
{
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate Ed25519 key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with EdDSA
|
||||
signature, err := dsig.Sign(privKey, dsig.EdDSA, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with EdDSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with EdDSA
|
||||
err = dsig.Verify(pubKey, dsig.EdDSA, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify EdDSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HMAC signing and verification
|
||||
{
|
||||
key := []byte("secret-key")
|
||||
|
||||
// Sign with HMAC SHA256
|
||||
signature, err := dsig.Sign(key, dsig.HMACWithSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with HMAC: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with HMAC SHA256
|
||||
err = dsig.Verify(key, dsig.HMACWithSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify HMAC signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
source: [examples/dsig_readme_example_test.go](https://github.com/lestrrat-go/dsig/blob/v1/examples/dsig_readme_example_test.go)
|
||||
<!-- END INCLUDE -->
|
||||
|
||||
# Supported Algorithms
|
||||
|
||||
| Constant | Algorithm | Key Type |
|
||||
|----------|-----------|----------|
|
||||
| `HMACWithSHA256` | HMAC using SHA-256 | []byte |
|
||||
| `HMACWithSHA384` | HMAC using SHA-384 | []byte |
|
||||
| `HMACWithSHA512` | HMAC using SHA-512 | []byte |
|
||||
| `RSAPKCS1v15WithSHA256` | RSA PKCS#1 v1.5 using SHA-256 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPKCS1v15WithSHA384` | RSA PKCS#1 v1.5 using SHA-384 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPKCS1v15WithSHA512` | RSA PKCS#1 v1.5 using SHA-512 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA256` | RSA PSS using SHA-256 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA384` | RSA PSS using SHA-384 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA512` | RSA PSS using SHA-512 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `ECDSAWithP256AndSHA256` | ECDSA using P-256 and SHA-256 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `ECDSAWithP384AndSHA384` | ECDSA using P-384 and SHA-384 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `ECDSAWithP521AndSHA512` | ECDSA using P-521 and SHA-512 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `EdDSA` | EdDSA using Ed25519 or Ed448 | ed25519.PrivateKey / ed25519.PublicKey |
|
||||
|
||||
# Description
|
||||
|
||||
This library provides low-level digital signature operations. It does minimal parameter validation for performance, uses strongly typed APIs, and has minimal dependencies.
|
||||
|
||||
# Contributions
|
||||
|
||||
## Issues
|
||||
|
||||
For bug reports and feature requests, please include failing tests when possible.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Please include tests that exercise your changes.
|
||||
|
||||
# Related Libraries
|
||||
|
||||
* [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) - JOSE (JWA/JWE/JWK/JWS/JWT) implementation
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package dsig
|
||||
|
||||
// This file defines verbose algorithm name constants that can be mapped to by
|
||||
// different standards (RFC7518, FIDO, etc.) for interoperability.
|
||||
//
|
||||
// The algorithm names are intentionally verbose to avoid any ambiguity about
|
||||
// the exact cryptographic operations being performed.
|
||||
|
||||
const (
|
||||
// HMAC signature algorithms
|
||||
// These use Hash-based Message Authentication Code with specified hash functions
|
||||
HMACWithSHA256 = "HMAC_WITH_SHA256"
|
||||
HMACWithSHA384 = "HMAC_WITH_SHA384"
|
||||
HMACWithSHA512 = "HMAC_WITH_SHA512"
|
||||
|
||||
// RSA signature algorithms with PKCS#1 v1.5 padding
|
||||
// These use RSA signatures with PKCS#1 v1.5 padding and specified hash functions
|
||||
RSAPKCS1v15WithSHA256 = "RSA_PKCS1v15_WITH_SHA256"
|
||||
RSAPKCS1v15WithSHA384 = "RSA_PKCS1v15_WITH_SHA384"
|
||||
RSAPKCS1v15WithSHA512 = "RSA_PKCS1v15_WITH_SHA512"
|
||||
|
||||
// RSA signature algorithms with PSS padding
|
||||
// These use RSA signatures with Probabilistic Signature Scheme (PSS) padding
|
||||
RSAPSSWithSHA256 = "RSA_PSS_WITH_SHA256"
|
||||
RSAPSSWithSHA384 = "RSA_PSS_WITH_SHA384"
|
||||
RSAPSSWithSHA512 = "RSA_PSS_WITH_SHA512"
|
||||
|
||||
// ECDSA signature algorithms
|
||||
// These use Elliptic Curve Digital Signature Algorithm with specified curves and hash functions
|
||||
ECDSAWithP256AndSHA256 = "ECDSA_WITH_P256_AND_SHA256"
|
||||
ECDSAWithP384AndSHA384 = "ECDSA_WITH_P384_AND_SHA384"
|
||||
ECDSAWithP521AndSHA512 = "ECDSA_WITH_P521_AND_SHA512"
|
||||
|
||||
// EdDSA signature algorithms
|
||||
// These use Edwards-curve Digital Signature Algorithm (supports Ed25519 and Ed448)
|
||||
EdDSA = "EDDSA"
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// cryptosign is a low-level function that signs a payload using a crypto.Signer.
|
||||
// If hash is crypto.Hash(0), the payload is signed directly without hashing.
|
||||
// Otherwise, the payload is hashed using the specified hash function before signing.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func cryptosign(signer crypto.Signer, payload []byte, hash crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
var digest []byte
|
||||
if hash == crypto.Hash(0) {
|
||||
digest = payload
|
||||
} else {
|
||||
h := hash.New()
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload to hash: %w`, err)
|
||||
}
|
||||
digest = h.Sum(nil)
|
||||
}
|
||||
return signer.Sign(rr, digest, opts)
|
||||
}
|
||||
|
||||
// SignCryptoSigner generates a signature using a crypto.Signer interface.
|
||||
// This function can be used for hardware security modules, smart cards,
|
||||
// and other implementations of the crypto.Signer interface.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// Returns the signature bytes or an error if signing fails.
|
||||
func SignCryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("dsig.SignCryptoSigner: signer is nil")
|
||||
}
|
||||
return cryptosign(signer, raw, h, opts, rr)
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
// Package dsig provides digital signature operations for Go.
|
||||
// It contains low-level signature generation and verification tools that
|
||||
// can be used by other signing libraries
|
||||
//
|
||||
// The package follows these design principles:
|
||||
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
|
||||
// 2. All exported functions are strongly typed (i.e. they do not take `any` types unless they absolutely have to).
|
||||
// 3. Does not rely on other high-level packages (standalone, except for internal packages).
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Family represents the cryptographic algorithm family
|
||||
type Family int
|
||||
|
||||
const (
|
||||
InvalidFamily Family = iota
|
||||
HMAC
|
||||
RSA
|
||||
ECDSA
|
||||
EdDSAFamily
|
||||
maxFamily
|
||||
)
|
||||
|
||||
// String returns the string representation of the Family
|
||||
func (f Family) String() string {
|
||||
switch f {
|
||||
case HMAC:
|
||||
return "HMAC"
|
||||
case RSA:
|
||||
return "RSA"
|
||||
case ECDSA:
|
||||
return "ECDSA"
|
||||
case EdDSAFamily:
|
||||
return "EdDSA"
|
||||
default:
|
||||
return "InvalidFamily"
|
||||
}
|
||||
}
|
||||
|
||||
// AlgorithmInfo contains metadata about a digital signature algorithm
|
||||
type AlgorithmInfo struct {
|
||||
Family Family // The cryptographic family (HMAC, RSA, ECDSA, EdDSA)
|
||||
Meta any // Family-specific metadata
|
||||
}
|
||||
|
||||
// HMACFamilyMeta contains metadata specific to HMAC algorithms
|
||||
type HMACFamilyMeta struct {
|
||||
HashFunc func() hash.Hash // Hash function constructor
|
||||
}
|
||||
|
||||
// RSAFamilyMeta contains metadata specific to RSA algorithms
|
||||
type RSAFamilyMeta struct {
|
||||
Hash crypto.Hash // Hash algorithm
|
||||
PSS bool // Whether to use PSS padding (false = PKCS#1 v1.5)
|
||||
}
|
||||
|
||||
// ECDSAFamilyMeta contains metadata specific to ECDSA algorithms
|
||||
type ECDSAFamilyMeta struct {
|
||||
Hash crypto.Hash // Hash algorithm
|
||||
}
|
||||
|
||||
// EdDSAFamilyMeta contains metadata specific to EdDSA algorithms
|
||||
// Currently EdDSA doesn't need specific metadata, but this provides extensibility
|
||||
type EdDSAFamilyMeta struct {
|
||||
// Reserved for future use
|
||||
}
|
||||
|
||||
var algorithms = make(map[string]AlgorithmInfo)
|
||||
var muAlgorithms sync.RWMutex
|
||||
|
||||
// RegisterAlgorithm registers a new digital signature algorithm with the specified family and metadata.
|
||||
//
|
||||
// info.Meta should contain extra metadata for some algorithms. Currently HMAC, RSA,
|
||||
// and ECDSA family of algorithms need their respective metadata (HMACFamilyMeta,
|
||||
// RSAFamilyMeta, and ECDSAFamilyMeta). Metadata for other families are ignored.
|
||||
func RegisterAlgorithm(name string, info AlgorithmInfo) error {
|
||||
muAlgorithms.Lock()
|
||||
defer muAlgorithms.Unlock()
|
||||
|
||||
// Validate the metadata matches the family
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
if _, ok := info.Meta.(HMACFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid HMAC metadata for algorithm %s", name)
|
||||
}
|
||||
case RSA:
|
||||
if _, ok := info.Meta.(RSAFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid RSA metadata for algorithm %s", name)
|
||||
}
|
||||
case ECDSA:
|
||||
if _, ok := info.Meta.(ECDSAFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid ECDSA metadata for algorithm %s", name)
|
||||
}
|
||||
case EdDSAFamily:
|
||||
// EdDSA metadata is optional for now
|
||||
default:
|
||||
return fmt.Errorf("unsupported algorithm family %s for algorithm %s", info.Family, name)
|
||||
}
|
||||
|
||||
algorithms[name] = info
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlgorithmInfo retrieves the algorithm information for a given algorithm name.
|
||||
// Returns the info and true if found, zero value and false if not found.
|
||||
func GetAlgorithmInfo(name string) (AlgorithmInfo, bool) {
|
||||
muAlgorithms.RLock()
|
||||
defer muAlgorithms.RUnlock()
|
||||
|
||||
info, ok := algorithms[name]
|
||||
return info, ok
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register all standard algorithms with their metadata
|
||||
toRegister := map[string]AlgorithmInfo{
|
||||
// HMAC algorithms
|
||||
HMACWithSHA256: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha256.New,
|
||||
},
|
||||
},
|
||||
HMACWithSHA384: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha512.New384,
|
||||
},
|
||||
},
|
||||
HMACWithSHA512: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha512.New,
|
||||
},
|
||||
},
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
RSAPKCS1v15WithSHA256: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
RSAPKCS1v15WithSHA384: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
RSAPKCS1v15WithSHA512: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
|
||||
// RSA PSS algorithms
|
||||
RSAPSSWithSHA256: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
RSAPSSWithSHA384: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
RSAPSSWithSHA512: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
|
||||
// ECDSA algorithms
|
||||
ECDSAWithP256AndSHA256: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
},
|
||||
ECDSAWithP384AndSHA384: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
},
|
||||
ECDSAWithP521AndSHA512: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
},
|
||||
|
||||
// EdDSA algorithm
|
||||
EdDSA: {
|
||||
Family: EdDSAFamily,
|
||||
Meta: EdDSAFamilyMeta{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, info := range toRegister {
|
||||
if err := RegisterAlgorithm(name, info); err != nil {
|
||||
panic(fmt.Sprintf("failed to register algorithm %s: %v", name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/dsig/internal/ecutil"
|
||||
)
|
||||
|
||||
|
||||
func ecdsaGetSignerKey(key any) (*ecdsa.PrivateKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
if !isValidECDSAKey(key) {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T for ECDSA algorithm`, key)
|
||||
}
|
||||
|
||||
switch key.(type) {
|
||||
case ecdsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
// if it's ecdsa.PrivateKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
privkey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. *ecdsa.PrivateKey is required`, key)
|
||||
}
|
||||
return privkey, nil, false, nil
|
||||
}
|
||||
|
||||
// UnpackASN1ECDSASignature unpacks an ASN.1 encoded ECDSA signature into r and s values.
|
||||
// This is typically used when working with crypto.Signer interfaces that return ASN.1 encoded signatures.
|
||||
func UnpackASN1ECDSASignature(signed []byte, r, s *big.Int) error {
|
||||
// Okay, this is silly, but hear me out. When we use the
|
||||
// crypto.Signer interface, the PrivateKey is hidden.
|
||||
// But we need some information about the key (its bit size).
|
||||
//
|
||||
// So while silly, we're going to have to make another call
|
||||
// here and fetch the Public key.
|
||||
// (This probably means that this information should be cached somewhere)
|
||||
var p struct {
|
||||
R *big.Int // TODO: get this from a pool?
|
||||
S *big.Int
|
||||
}
|
||||
if _, err := asn1.Unmarshal(signed, &p); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
r.Set(p.R)
|
||||
s.Set(p.S)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnpackECDSASignature unpacks a JWS-format ECDSA signature into r and s values.
|
||||
// The signature should be in the format specified by RFC 7515 (r||s as fixed-length byte arrays).
|
||||
func UnpackECDSASignature(signature []byte, pubkey *ecdsa.PublicKey, r, s *big.Int) error {
|
||||
keySize := ecutil.CalculateKeySize(pubkey.Curve)
|
||||
if len(signature) != keySize*2 {
|
||||
return fmt.Errorf(`invalid signature length for curve %q`, pubkey.Curve.Params().Name)
|
||||
}
|
||||
|
||||
r.SetBytes(signature[:keySize])
|
||||
s.SetBytes(signature[keySize:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackECDSASignature packs the r and s values from an ECDSA signature into a JWS-format byte slice.
|
||||
// The output format follows RFC 7515: r||s as fixed-length byte arrays.
|
||||
func PackECDSASignature(r *big.Int, sbig *big.Int, curveBits int) ([]byte, error) {
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes++
|
||||
}
|
||||
|
||||
// Serialize r and s into fixed-length bytes
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
sBytes := sbig.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
// Output as r||s
|
||||
return append(rBytesPadded, sBytesPadded...), nil
|
||||
}
|
||||
|
||||
// SignECDSA generates an ECDSA signature for the given payload using the specified private key and hash.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. if rr is nil, it defaults to rand.Reader.
|
||||
func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
if !isValidECDSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for ECDSA algorithm`, key)
|
||||
}
|
||||
hh := h.New()
|
||||
if _, err := hh.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using ecdsa: %w`, err)
|
||||
}
|
||||
digest := hh.Sum(nil)
|
||||
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
// Sign and get r, s values
|
||||
r, s, err := ecdsa.Sign(rr, key, digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(r, s, key.Curve.Params().BitSize)
|
||||
}
|
||||
|
||||
// SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface.
|
||||
// This function works with hardware security modules and other crypto.Signer implementations.
|
||||
// The signature is converted from ASN.1 format to JWS format (r||s).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func SignECDSACryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
signed, err := SignCryptoSigner(signer, raw, h, h, rr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using crypto.Signer: %w`, err)
|
||||
}
|
||||
|
||||
return signECDSACryptoSigner(signer, signed)
|
||||
}
|
||||
|
||||
func signECDSACryptoSigner(signer crypto.Signer, signed []byte) ([]byte, error) {
|
||||
cpub := signer.Public()
|
||||
pubkey, ok := cpub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`expected *ecdsa.PublicKey, got %T`, pubkey)
|
||||
}
|
||||
curveBits := pubkey.Curve.Params().BitSize
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackASN1ECDSASignature(signed, &r, &s); err != nil {
|
||||
return nil, fmt.Errorf(`failed to unpack ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(&r, &s, curveBits)
|
||||
}
|
||||
|
||||
func ecdsaVerify(key *ecdsa.PublicKey, buf []byte, h crypto.Hash, r, s *big.Int) error {
|
||||
hasher := h.New()
|
||||
hasher.Write(buf)
|
||||
digest := hasher.Sum(nil)
|
||||
if !ecdsa.Verify(key, digest, r, s) {
|
||||
return NewVerificationError("invalid ECDSA signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyECDSA verifies an ECDSA signature for the given payload.
|
||||
// This function verifies the signature using the specified public key and hash algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyECDSA(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error {
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, key, &r, &s); err != nil {
|
||||
return fmt.Errorf("dsig.VerifyECDSA: failed to unpack ECDSA signature: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(key, payload, h, &r, &s)
|
||||
}
|
||||
|
||||
// VerifyECDSACryptoSigner verifies an ECDSA signature for crypto.Signer implementations.
|
||||
// This function is useful for verifying signatures created by hardware security modules
|
||||
// or other implementations of the crypto.Signer interface.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyECDSACryptoSigner(signer crypto.Signer, payload, signature []byte, h crypto.Hash) error {
|
||||
var pubkey *ecdsa.PublicKey
|
||||
switch cpub := signer.Public(); cpub := cpub.(type) {
|
||||
case ecdsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *ecdsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`dsig.VerifyECDSACryptoSigner: expected *ecdsa.PublicKey, got %T`, cpub)
|
||||
}
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, pubkey, &r, &s); err != nil {
|
||||
return fmt.Errorf("dsig.VerifyECDSACryptoSigner: failed to unpack ASN.1 encoded ECDSA signature: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(pubkey, payload, h, &r, &s)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func eddsaGetSigner(key any) (crypto.Signer, error) {
|
||||
// The ed25519.PrivateKey object implements crypto.Signer, so we should
|
||||
// simply accept a crypto.Signer here.
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
if !isValidEDDSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for EdDSA algorithm`, key)
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// This fallback exists for cases when users give us a pointer instead of non-pointer, etc.
|
||||
privkey, ok := key.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`failed to retrieve ed25519.PrivateKey out of %T`, key)
|
||||
}
|
||||
return privkey, nil
|
||||
}
|
||||
|
||||
// SignEdDSA generates an EdDSA (Ed25519) signature for the given payload.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and doesn't require additional hashing of the input.
|
||||
func SignEdDSA(key ed25519.PrivateKey, payload []byte) ([]byte, error) {
|
||||
return ed25519.Sign(key, payload), nil
|
||||
}
|
||||
|
||||
// VerifyEdDSA verifies an EdDSA (Ed25519) signature for the given payload.
|
||||
// This function verifies the signature using Ed25519 verification algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and provides strong security guarantees without requiring hash function selection.
|
||||
func VerifyEdDSA(key ed25519.PublicKey, payload, signature []byte) error {
|
||||
if !ed25519.Verify(key, payload, signature) {
|
||||
return fmt.Errorf("invalid EdDSA signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"fmt"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func toHMACKey(dst *[]byte, key any) error {
|
||||
keyBytes, ok := key.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.toHMACKey: invalid key type %T. []byte is required`, key)
|
||||
}
|
||||
|
||||
if len(keyBytes) == 0 {
|
||||
return fmt.Errorf(`dsig.toHMACKey: missing key while signing payload`)
|
||||
}
|
||||
|
||||
*dst = keyBytes
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignHMAC generates an HMAC signature for the given payload using the specified hash function and key.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
func SignHMAC(key, payload []byte, hfunc func() hash.Hash) ([]byte, error) {
|
||||
h := hmac.New(hfunc, key)
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using hmac: %w`, err)
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// VerifyHMAC verifies an HMAC signature for the given payload.
|
||||
// This function verifies the signature using the specified key and hash function.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyHMAC(key, payload, signature []byte, hfunc func() hash.Hash) error {
|
||||
expected, err := SignHMAC(key, payload, hfunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign payload for verification: %w", err)
|
||||
}
|
||||
if !hmac.Equal(signature, expected) {
|
||||
return NewVerificationError("invalid HMAC signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Package ecutil defines tools that help with elliptic curve related
|
||||
// computation
|
||||
package ecutil
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// size of buffer that needs to be allocated for EC521 curve
|
||||
ec521BufferSize = 66 // (521 / 8) + 1
|
||||
)
|
||||
|
||||
var ecpointBufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
// In most cases the curve bit size will be less than this length
|
||||
// so allocate the maximum, and keep reusing
|
||||
buf := make([]byte, 0, ec521BufferSize)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
func getCrvFixedBuffer(size int) []byte {
|
||||
//nolint:forcetypeassert
|
||||
buf := *(ecpointBufferPool.Get().(*[]byte))
|
||||
if size > ec521BufferSize && cap(buf) < size {
|
||||
buf = append(buf, make([]byte, size-cap(buf))...)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
// ReleaseECPointBuffer releases the []byte buffer allocated.
|
||||
func ReleaseECPointBuffer(buf []byte) {
|
||||
buf = buf[:cap(buf)]
|
||||
buf[0] = 0x0
|
||||
for i := 1; i < len(buf); i *= 2 {
|
||||
copy(buf[i:], buf[:i])
|
||||
}
|
||||
buf = buf[:0]
|
||||
ecpointBufferPool.Put(&buf)
|
||||
}
|
||||
|
||||
func CalculateKeySize(crv elliptic.Curve) int {
|
||||
// We need to create a buffer that fits the entire curve.
|
||||
// If the curve size is 66, that fits in 9 bytes. If the curve
|
||||
// size is 64, it fits in 8 bytes.
|
||||
bits := crv.Params().BitSize
|
||||
|
||||
// For most common cases we know before hand what the byte length
|
||||
// is going to be. optimize
|
||||
var inBytes int
|
||||
switch bits {
|
||||
case 224, 256, 384: // TODO: use constant?
|
||||
inBytes = bits / 8
|
||||
case 521:
|
||||
inBytes = ec521BufferSize
|
||||
default:
|
||||
inBytes = bits / 8
|
||||
if (bits % 8) != 0 {
|
||||
inBytes++
|
||||
}
|
||||
}
|
||||
|
||||
return inBytes
|
||||
}
|
||||
|
||||
// AllocECPointBuffer allocates a buffer for the given point in the given
|
||||
// curve. This buffer should be released using the ReleaseECPointBuffer
|
||||
// function.
|
||||
func AllocECPointBuffer(v *big.Int, crv elliptic.Curve) []byte {
|
||||
buf := getCrvFixedBuffer(CalculateKeySize(crv))
|
||||
v.FillBytes(buf)
|
||||
return buf
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func rsaGetSignerCryptoSignerKey(key any) (crypto.Signer, bool, error) {
|
||||
if !isValidRSAKey(key) {
|
||||
return nil, false, fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
return cs, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// rsaPSSOptions returns the PSS options for RSA-PSS signatures with the specified hash.
|
||||
// The salt length is set to equal the hash length as per RFC 7518.
|
||||
func rsaPSSOptions(h crypto.Hash) rsa.PSSOptions {
|
||||
return rsa.PSSOptions{
|
||||
Hash: h,
|
||||
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||
}
|
||||
}
|
||||
|
||||
// SignRSA generates an RSA signature for the given payload using the specified private key and options.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// If pss is true, RSA-PSS is used; otherwise, PKCS#1 v1.5 is used.
|
||||
//
|
||||
// The rr parameter is an optional io.Reader that can be used to provide randomness for signing.
|
||||
// If rr is nil, it defaults to rand.Reader.
|
||||
func SignRSA(key *rsa.PrivateKey, payload []byte, h crypto.Hash, pss bool, rr io.Reader) ([]byte, error) {
|
||||
if !isValidRSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
var opts crypto.SignerOpts = h
|
||||
if pss {
|
||||
rsaopts := rsaPSSOptions(h)
|
||||
opts = &rsaopts
|
||||
}
|
||||
return cryptosign(key, payload, h, opts, rr)
|
||||
}
|
||||
|
||||
// VerifyRSA verifies an RSA signature for the given payload and header.
|
||||
// This function constructs the signing input by encoding the header and payload according to JWS specification,
|
||||
// then verifies the signature using the specified public key and hash algorithm.
|
||||
// If pss is true, RSA-PSS verification is used; otherwise, PKCS#1 v1.5 verification is used.
|
||||
func VerifyRSA(key *rsa.PublicKey, payload, signature []byte, h crypto.Hash, pss bool) error {
|
||||
if !isValidRSAKey(key) {
|
||||
return fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
hasher := h.New()
|
||||
hasher.Write(payload)
|
||||
digest := hasher.Sum(nil)
|
||||
if pss {
|
||||
return rsa.VerifyPSS(key, h, digest, signature, &rsa.PSSOptions{Hash: h, SaltLength: rsa.PSSSaltLengthEqualsHash})
|
||||
}
|
||||
return rsa.VerifyPKCS1v15(key, h, digest, signature)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sign generates a digital signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the signer registered in the dsig package _ONLY_.
|
||||
// It does not support custom signers that the user might have registered.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
// Not all algorithms require this parameter, but it is included for consistency.
|
||||
// 99% of the time, you can pass nil for rr, and it will work fine.
|
||||
func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
info, ok := GetAlgorithmInfo(alg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
return dispatchHMACSign(key, info, payload)
|
||||
case RSA:
|
||||
return dispatchRSASign(key, info, payload, rr)
|
||||
case ECDSA:
|
||||
return dispatchECDSASign(key, info, payload, rr)
|
||||
case EdDSAFamily:
|
||||
return dispatchEdDSASign(key, info, payload, rr)
|
||||
default:
|
||||
return nil, fmt.Errorf(`dsig.Sign: unsupported signature family %q`, info.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACSign(key any, info AlgorithmInfo, payload []byte) ([]byte, error) {
|
||||
meta, ok := info.Meta.(HMACFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid HMAC metadata`)
|
||||
}
|
||||
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
return SignHMAC(hmackey, payload, meta.HashFunc)
|
||||
}
|
||||
|
||||
func dispatchRSASign(key any, info AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
meta, ok := info.Meta.(RSAFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid RSA metadata`)
|
||||
}
|
||||
|
||||
cs, isCryptoSigner, err := rsaGetSignerCryptoSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
var options crypto.SignerOpts = meta.Hash
|
||||
if meta.PSS {
|
||||
rsaopts := rsaPSSOptions(meta.Hash)
|
||||
options = &rsaopts
|
||||
}
|
||||
return SignCryptoSigner(cs, payload, meta.Hash, options, rr)
|
||||
}
|
||||
|
||||
privkey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid key type %T. *rsa.PrivateKey is required`, key)
|
||||
}
|
||||
return SignRSA(privkey, payload, meta.Hash, meta.PSS, rr)
|
||||
}
|
||||
|
||||
func dispatchEdDSASign(key any, _ AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
signer, err := eddsaGetSigner(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
|
||||
return SignCryptoSigner(signer, payload, crypto.Hash(0), crypto.Hash(0), rr)
|
||||
}
|
||||
|
||||
func dispatchECDSASign(key any, info AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
meta, ok := info.Meta.(ECDSAFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid ECDSA metadata`)
|
||||
}
|
||||
|
||||
privkey, cs, isCryptoSigner, err := ecdsaGetSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return SignECDSACryptoSigner(cs, payload, meta.Hash, rr)
|
||||
}
|
||||
return SignECDSA(privkey, payload, meta.Hash, rr)
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// isValidRSAKey validates that the provided key type is appropriate for RSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., ECDSA or EdDSA keys).
|
||||
func isValidRSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
||||
ed25519.PrivateKey:
|
||||
// these are NOT ok for RSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isValidECDSAKey validates that the provided key type is appropriate for ECDSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., RSA or EdDSA keys).
|
||||
func isValidECDSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ed25519.PrivateKey,
|
||||
rsa.PrivateKey, *rsa.PrivateKey:
|
||||
// these are NOT ok for ECDSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isValidEDDSAKey validates that the provided key type is appropriate for EdDSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., RSA or ECDSA keys).
|
||||
func isValidEDDSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
||||
rsa.PrivateKey, *rsa.PrivateKey:
|
||||
// these are NOT ok for EdDSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// VerificationError represents an error that occurred during signature verification.
|
||||
type VerificationError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *VerificationError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
// NewVerificationError creates a new verification error with the given message.
|
||||
func NewVerificationError(message string) error {
|
||||
return &VerificationError{message: message}
|
||||
}
|
||||
|
||||
// IsVerificationError checks if the given error is a verification error.
|
||||
func IsVerificationError(err error) bool {
|
||||
_, ok := err.(*VerificationError)
|
||||
return ok
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Verify verifies a digital signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the verifier registered in the dsig package _ONLY_.
|
||||
// It does not support custom verifiers that the user might have registered.
|
||||
func Verify(key any, alg string, payload, signature []byte) error {
|
||||
info, ok := GetAlgorithmInfo(alg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
return dispatchHMACVerify(key, info, payload, signature)
|
||||
case RSA:
|
||||
return dispatchRSAVerify(key, info, payload, signature)
|
||||
case ECDSA:
|
||||
return dispatchECDSAVerify(key, info, payload, signature)
|
||||
case EdDSAFamily:
|
||||
return dispatchEdDSAVerify(key, info, payload, signature)
|
||||
default:
|
||||
return fmt.Errorf(`dsig.Verify: unsupported signature family %q`, info.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(HMACFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid HMAC metadata`)
|
||||
}
|
||||
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return fmt.Errorf(`dsig.Verify: %w`, err)
|
||||
}
|
||||
return VerifyHMAC(hmackey, payload, signature, meta.HashFunc)
|
||||
}
|
||||
|
||||
func dispatchRSAVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(RSAFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid RSA metadata`)
|
||||
}
|
||||
|
||||
var pubkey *rsa.PublicKey
|
||||
|
||||
if cs, ok := key.(crypto.Signer); ok {
|
||||
cpub := cs.Public()
|
||||
switch cpub := cpub.(type) {
|
||||
case rsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *rsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve rsa.PublicKey out of crypto.Signer %T`, key)
|
||||
}
|
||||
} else {
|
||||
var ok bool
|
||||
pubkey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve *rsa.PublicKey out of %T`, key)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifyRSA(pubkey, payload, signature, meta.Hash, meta.PSS)
|
||||
}
|
||||
|
||||
func dispatchECDSAVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(ECDSAFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid ECDSA metadata`)
|
||||
}
|
||||
|
||||
pubkey, cs, isCryptoSigner, err := ecdsaGetVerifierKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`dsig.Verify: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return VerifyECDSACryptoSigner(cs, payload, signature, meta.Hash)
|
||||
}
|
||||
return VerifyECDSA(pubkey, payload, signature, meta.Hash)
|
||||
}
|
||||
|
||||
func dispatchEdDSAVerify(key any, _ AlgorithmInfo, payload, signature []byte) error {
|
||||
var pubkey ed25519.PublicKey
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
v := signer.Public()
|
||||
pubkey, ok = v.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: expected crypto.Signer.Public() to return ed25519.PublicKey, but got %T`, v)
|
||||
}
|
||||
} else {
|
||||
var ok bool
|
||||
pubkey, ok = key.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve ed25519.PublicKey out of %T`, key)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifyEdDSA(pubkey, payload, signature)
|
||||
}
|
||||
|
||||
func ecdsaGetVerifierKey(key any) (*ecdsa.PublicKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
switch key.(type) {
|
||||
case ecdsa.PublicKey, *ecdsa.PublicKey:
|
||||
// if it's ecdsa.PublicKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
pubkey, ok := key.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. *ecdsa.PublicKey is required`, key)
|
||||
}
|
||||
|
||||
return pubkey, nil, false, nil
|
||||
}
|
||||
+2
@@ -29,6 +29,7 @@ linters:
|
||||
- nakedret
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- nonamedreturns
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
@@ -37,6 +38,7 @@ linters:
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
- wsl
|
||||
- wsl_v5
|
||||
settings:
|
||||
govet:
|
||||
disable:
|
||||
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v3.0.0 UNRELEASED
|
||||
v3.0.1 18 Aug 2025
|
||||
* Refresh() no longer requires the resource to be ready.
|
||||
|
||||
v3.0.0 5 Jun 2025
|
||||
[Breaking Changes]
|
||||
* The entire API has been re-imagined for Go versions that allow typed parameters
|
||||
|
||||
|
||||
+4
-6
@@ -71,6 +71,7 @@ func (c *ctrlBackend) refreshResource(ctx context.Context, req refreshRequest) {
|
||||
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: [refresh] START %q", req.u))
|
||||
defer c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: [refresh] END %q", req.u))
|
||||
u := req.u
|
||||
|
||||
r, ok := c.items[u]
|
||||
if !ok {
|
||||
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: [refresh] %s is not registered", req.u))
|
||||
@@ -78,12 +79,9 @@ func (c *ctrlBackend) refreshResource(ctx context.Context, req refreshRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure it's ready
|
||||
if err := r.Ready(ctx); err != nil {
|
||||
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: [refresh] %s did not become ready: %v", req.u, err))
|
||||
sendReply(ctx, req.reply, struct{}{}, err)
|
||||
return
|
||||
}
|
||||
// Note: We don't wait for r.Ready() here because refresh should work
|
||||
// regardless of whether the resource has been fetched before. This allows
|
||||
// refresh to work with resources registered using WithWaitReady(false).
|
||||
|
||||
r.SetNext(time.Unix(0, 0))
|
||||
sendWorkerSynchronous(ctx, c.syncoutgoing, synchronousRequest{
|
||||
|
||||
+2
@@ -39,10 +39,12 @@ func (w worker) Run(ctx context.Context, readywg *sync.WaitGroup, donewg *sync.W
|
||||
case sr := <-w.nextsync:
|
||||
w.traceSink.Put(ctx, fmt.Sprintf("httprc worker: syncing %q (synchronous)", sr.resource.URL()))
|
||||
if err := sr.resource.Sync(ctx); err != nil {
|
||||
w.traceSink.Put(ctx, fmt.Sprintf("httprc worker: FAILED to sync %q (synchronous): %s", sr.resource.URL(), err))
|
||||
sendReply(ctx, sr.reply, struct{}{}, err)
|
||||
sr.resource.SetBusy(false)
|
||||
return
|
||||
}
|
||||
w.traceSink.Put(ctx, fmt.Sprintf("httprc worker: SUCCESS syncing %q (synchronous)", sr.resource.URL()))
|
||||
sr.resource.SetBusy(false)
|
||||
sendReply(ctx, sr.reply, struct{}{}, nil)
|
||||
w.sendAdjustIntervalRequest(ctx, sr.resource)
|
||||
|
||||
+14
@@ -4,6 +4,20 @@ Changes
|
||||
v3 has many incompatibilities with v2. To see the full list of differences between
|
||||
v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md)
|
||||
|
||||
v3.0.11 14 Sep 2025
|
||||
* [jwk] Add `(jwk.Cache).Shutdown()` method that delegates to the httprc controller
|
||||
object, to shutdown the cache.
|
||||
* [jwk] Change timing of `res.Body.Close()` call
|
||||
* [jwe] Previously, ecdh.PrivateKey/ecdh.PublicKey were not properly handled
|
||||
when used for encryption, which has been fixed.
|
||||
* [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Convert most functions into
|
||||
thin wrappers around functions from github.com/lestrrat-go/dsig package.
|
||||
As a related change, HAMCHashFuncFor/RSAHashFuncFor/ECDSAHashFuncFor/RSAPSSOptions
|
||||
have been removed or unexported.
|
||||
Users of this module should be using jwsbb.Sign() and jwsbb.Verify() instead of
|
||||
algorithm specific jwsbb.SignRSA()/jwsbb.VerifyRSA() and such. If you feel the
|
||||
need to use these functions, you should use github.com/lestrrat-go/dsig directly.
|
||||
|
||||
v3.0.10 04 Aug 2025
|
||||
* [jws/jwsbb] Add `jwsbb.ErrHeaderNotFound()` to return the same error type as when
|
||||
a non-existent header is requested. via `HeaderGetXXX()` functions. Previously, this
|
||||
|
||||
+4
-2
@@ -9,9 +9,9 @@ bazel_dep(name = "rules_go", version = "0.55.1")
|
||||
bazel_dep(name = "gazelle", version = "0.44.0")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.11.0")
|
||||
|
||||
# Go SDK setup - using Go 1.23.6 to match the toolchain in go.mod
|
||||
# Go SDK setup - using Go 1.24.4 to match the toolchain in go.mod
|
||||
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.23.6")
|
||||
go_sdk.download(version = "1.24.4")
|
||||
|
||||
# Go dependencies from go.mod
|
||||
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
|
||||
@@ -23,6 +23,8 @@ use_repo(
|
||||
"com_github_decred_dcrd_dcrec_secp256k1_v4",
|
||||
"com_github_goccy_go_json",
|
||||
"com_github_lestrrat_go_blackmagic",
|
||||
"com_github_lestrrat_go_dsig",
|
||||
"com_github_lestrrat_go_dsig_secp256k1",
|
||||
"com_github_lestrrat_go_httprc_v3",
|
||||
"com_github_lestrrat_go_option_v2",
|
||||
"com_github_segmentio_asm",
|
||||
|
||||
+89
@@ -5,8 +5,10 @@ import (
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/blackmagic"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
@@ -263,3 +265,90 @@ func ECDHPublicKey(dst, src any) error {
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, pubECDH)
|
||||
}
|
||||
|
||||
// ecdhCurveToElliptic maps ECDH curves to elliptic curves
|
||||
func ecdhCurveToElliptic(ecdhCurve ecdh.Curve) (elliptic.Curve, error) {
|
||||
switch ecdhCurve {
|
||||
case ecdh.P256():
|
||||
return elliptic.P256(), nil
|
||||
case ecdh.P384():
|
||||
return elliptic.P384(), nil
|
||||
case ecdh.P521():
|
||||
return elliptic.P521(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`keyconv: unsupported ECDH curve: %v`, ecdhCurve)
|
||||
}
|
||||
}
|
||||
|
||||
// ecdhPublicKeyToECDSA converts an ECDH public key to an ECDSA public key
|
||||
func ecdhPublicKeyToECDSA(ecdhPubKey *ecdh.PublicKey) (*ecdsa.PublicKey, error) {
|
||||
curve, err := ecdhCurveToElliptic(ecdhPubKey.Curve())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubBytes := ecdhPubKey.Bytes()
|
||||
|
||||
// Parse the uncompressed point format (0x04 prefix + X + Y coordinates)
|
||||
if len(pubBytes) == 0 || pubBytes[0] != 0x04 {
|
||||
return nil, fmt.Errorf(`keyconv: invalid ECDH public key format`)
|
||||
}
|
||||
|
||||
keyLen := (len(pubBytes) - 1) / 2
|
||||
if len(pubBytes) != 1+2*keyLen {
|
||||
return nil, fmt.Errorf(`keyconv: invalid ECDH public key length`)
|
||||
}
|
||||
|
||||
x := new(big.Int).SetBytes(pubBytes[1 : 1+keyLen])
|
||||
y := new(big.Int).SetBytes(pubBytes[1+keyLen:])
|
||||
|
||||
return &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ECDHToECDSA(dst, src any) error {
|
||||
// convert ecdh.PublicKey to ecdsa.PublicKey, ecdh.PrivateKey to ecdsa.PrivateKey
|
||||
|
||||
// First, handle value types by converting to pointers
|
||||
switch s := src.(type) {
|
||||
case ecdh.PrivateKey:
|
||||
src = &s
|
||||
case ecdh.PublicKey:
|
||||
src = &s
|
||||
}
|
||||
|
||||
var privBytes []byte
|
||||
var pubkey *ecdh.PublicKey
|
||||
// Now handle the actual conversion with pointer types
|
||||
switch src := src.(type) {
|
||||
case *ecdh.PrivateKey:
|
||||
pubkey = src.PublicKey()
|
||||
privBytes = src.Bytes()
|
||||
case *ecdh.PublicKey:
|
||||
pubkey = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected ecdh.PrivateKey, *ecdh.PrivateKey, ecdh.PublicKey, or *ecdh.PublicKey, got %T`, src)
|
||||
}
|
||||
|
||||
// convert the public key
|
||||
ecdsaPubKey, err := ecdhPublicKeyToECDSA(pubkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv.ECDHToECDSA: failed to convert ECDH public key to ECDSA public key: %w`, err)
|
||||
}
|
||||
|
||||
// return if we were being asked to convert *ecdh.PublicKey
|
||||
if privBytes == nil {
|
||||
return blackmagic.AssignIfCompatible(dst, ecdsaPubKey)
|
||||
}
|
||||
|
||||
// Then create the private key with the public key embedded
|
||||
ecdsaPrivKey := &ecdsa.PrivateKey{
|
||||
D: new(big.Int).SetBytes(privBytes),
|
||||
PublicKey: *ecdsaPubKey,
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, ecdsaPrivKey)
|
||||
}
|
||||
|
||||
+33
-8
@@ -96,23 +96,48 @@ func (e *encrypter) EncryptKey(cek []byte) (keygen.ByteSource, error) {
|
||||
keyToUse = e.pubkey
|
||||
}
|
||||
|
||||
// Handle ecdsa.PublicKey by value - convert to pointer
|
||||
if pk, ok := keyToUse.(ecdsa.PublicKey); ok {
|
||||
keyToUse = &pk
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
// no op
|
||||
case ecdsa.PublicKey:
|
||||
keyToUse = &key
|
||||
case *ecdsa.PrivateKey:
|
||||
keyToUse = &key.PublicKey
|
||||
case ecdsa.PrivateKey:
|
||||
keyToUse = &key.PublicKey
|
||||
case *ecdh.PublicKey:
|
||||
// no op
|
||||
case ecdh.PublicKey:
|
||||
keyToUse = &key
|
||||
case ecdh.PrivateKey:
|
||||
keyToUse = key.PublicKey()
|
||||
case *ecdh.PrivateKey:
|
||||
keyToUse = key.PublicKey()
|
||||
}
|
||||
|
||||
// Determine key type and call appropriate function
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdh.PublicKey:
|
||||
if key.Curve() == ecdh.X25519() {
|
||||
if !keywrap {
|
||||
return jwebb.KeyEncryptECDHESX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptECDHESKeyWrapX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
|
||||
var ecdsaKey *ecdsa.PublicKey
|
||||
if err := keyconv.ECDHToECDSA(&ecdsaKey, key); err != nil {
|
||||
return nil, fmt.Errorf(`encrypt: failed to convert ECDH public key to ECDSA: %w`, err)
|
||||
}
|
||||
keyToUse = ecdsaKey
|
||||
}
|
||||
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
if !keywrap {
|
||||
return jwebb.KeyEncryptECDHESECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptECDHESKeyWrapECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
case *ecdh.PublicKey:
|
||||
if !keywrap {
|
||||
return jwebb.KeyEncryptECDHESX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptECDHESKeyWrapX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
default:
|
||||
return nil, fmt.Errorf(`encrypt: unsupported key type for ECDH-ES: %T`, keyToUse)
|
||||
}
|
||||
|
||||
+4
@@ -223,6 +223,10 @@ func (c *Cache) Unregister(ctx context.Context, u string) error {
|
||||
return c.ctrl.Remove(ctx, u)
|
||||
}
|
||||
|
||||
func (c *Cache) Shutdown(ctx context.Context) error {
|
||||
return c.ctrl.ShutdownContext(ctx)
|
||||
}
|
||||
|
||||
// CachedSet is a thin shim over jwk.Cache that allows the user to cloak
|
||||
// jwk.Cache as if it's a `jwk.Set`. Behind the scenes, the `jwk.Set` is
|
||||
// retrieved from the `jwk.Cache` for every operation.
|
||||
|
||||
+1
-1
@@ -102,13 +102,13 @@ func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwk.Fetch: request failed: %w`, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf(`jwk.Fetch: request returned status %d, expected 200`, res.StatusCode)
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwk.Fetch: failed to read response body for %q: %w`, u, err)
|
||||
}
|
||||
|
||||
+1
@@ -8,5 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register ES256K to EC algorithm family
|
||||
addAlgorithmForKeyType(jwa.EC(), jwa.ES256K())
|
||||
}
|
||||
|
||||
+4
@@ -19,6 +19,10 @@
|
||||
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
|
||||
// and verify the result using `algorithm` and `key`. Upon successful
|
||||
// verification, the original payload is returned, so you can work on it.
|
||||
//
|
||||
// As a sidenote, consider using github.com/lestrrat-go/htmsig if you
|
||||
// looking for HTTP Message Signatures (RFC9421) -- it uses the same
|
||||
// underlying signing/verification mechanisms as this module.
|
||||
package jws
|
||||
|
||||
import (
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ go_library(
|
||||
"//internal/pool",
|
||||
"//internal/tokens",
|
||||
"//jws/internal/keytype",
|
||||
"@com_github_lestrrat_go_dsig//:dsig",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+25
-62
@@ -3,59 +3,27 @@ package jwsbb
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
"github.com/lestrrat-go/jwx/v3/jws/internal/keytype"
|
||||
)
|
||||
|
||||
var ecdsaHashFuncs = map[string]crypto.Hash{
|
||||
"ES256": crypto.SHA256,
|
||||
"ES256K": crypto.SHA256,
|
||||
"ES384": crypto.SHA384,
|
||||
"ES512": crypto.SHA512,
|
||||
}
|
||||
|
||||
func isSuppotedECDSAAlgorithm(alg string) bool {
|
||||
_, ok := ecdsaHashFuncs[alg]
|
||||
return ok
|
||||
}
|
||||
|
||||
func ECDSAHashFuncFor(alg string) (crypto.Hash, error) {
|
||||
if h, ok := ecdsaHashFuncs[alg]; ok {
|
||||
return h, nil
|
||||
// ecdsaHashToDsigAlgorithm maps ECDSA hash functions to dsig algorithm constants
|
||||
func ecdsaHashToDsigAlgorithm(h crypto.Hash) (string, error) {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.ECDSAWithP256AndSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.ECDSAWithP384AndSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.ECDSAWithP521AndSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported ECDSA hash function: %v", h)
|
||||
}
|
||||
return 0, fmt.Errorf(`unsupported ECDSA algorithm %s`, alg)
|
||||
}
|
||||
|
||||
func ecdsaGetSignerKey(key any) (*ecdsa.PrivateKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
if !keytype.IsValidECDSAKey(key) {
|
||||
return nil, nil, false, fmt.Errorf(`cannot use key of type %T`, key)
|
||||
}
|
||||
switch key.(type) {
|
||||
case ecdsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
// if it's ecdsa.PrivateKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
var privkey *ecdsa.PrivateKey
|
||||
if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. ecdsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
return privkey, nil, false, nil
|
||||
}
|
||||
|
||||
// UnpackASN1ECDSASignature unpacks an ASN.1 encoded ECDSA signature into r and s values.
|
||||
@@ -120,24 +88,16 @@ func PackECDSASignature(r *big.Int, sbig *big.Int, curveBits int) ([]byte, error
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. if rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignECDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
hh := h.New()
|
||||
if _, err := hh.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using ecdsa: %w`, err)
|
||||
}
|
||||
digest := hh.Sum(nil)
|
||||
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
// Sign and get r, s values
|
||||
r, s, err := ecdsa.Sign(rr, key, digest)
|
||||
dsigAlg, err := ecdsaHashToDsigAlgorithm(h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err)
|
||||
return nil, fmt.Errorf("jwsbb.SignECDSA: %w", err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(r, s, key.Curve.Params().BitSize)
|
||||
return dsig.Sign(key, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
// SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface.
|
||||
@@ -183,13 +143,16 @@ func ecdsaVerify(key *ecdsa.PublicKey, buf []byte, h crypto.Hash, r, s *big.Int)
|
||||
// VerifyECDSA verifies an ECDSA signature for the given payload.
|
||||
// This function verifies the signature using the specified public key and hash algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyECDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyECDSA(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error {
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, key, &r, &s); err != nil {
|
||||
return fmt.Errorf("jwsbb.ECDSAVerifier: failed to unpack ECDSA signature: %w", err)
|
||||
dsigAlg, err := ecdsaHashToDsigAlgorithm(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("jwsbb.VerifyECDSA: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(key, payload, h, &r, &s)
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
// VerifyECDSACryptoSigner verifies an ECDSA signature for crypto.Signer implementations.
|
||||
|
||||
+11
-33
@@ -1,52 +1,30 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
"github.com/lestrrat-go/jwx/v3/jws/internal/keytype"
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
func isSupportedEdDSAAlgorithm(alg string) bool {
|
||||
return alg == "EdDSA"
|
||||
}
|
||||
|
||||
func eddsaGetSigner(key any) (crypto.Signer, error) {
|
||||
// The ed25519.PrivateKey object implements crypto.Signer, so we should
|
||||
// simply accept a crypto.Signer here.
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
if !keytype.IsValidEDDSAKey(key) {
|
||||
return nil, fmt.Errorf(`cannot use key of type %T to generate EdDSA based signatures`, key)
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// This fallback exists for cases when jwk.Key was passed, or
|
||||
// users gave us a pointer instead of non-pointer, etc.
|
||||
var privkey ed25519.PrivateKey
|
||||
if err := keyconv.Ed25519PrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`failed to retrieve ed25519.PrivateKey out of %T: %w`, key, err)
|
||||
}
|
||||
return privkey, nil
|
||||
}
|
||||
|
||||
// SignEdDSA generates an EdDSA (Ed25519) signature for the given payload.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and doesn't require additional hashing of the input.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignEdDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignEdDSA(key ed25519.PrivateKey, payload []byte) ([]byte, error) {
|
||||
return ed25519.Sign(key, payload), nil
|
||||
// Use dsig.Sign with EdDSA algorithm constant
|
||||
return dsig.Sign(key, dsig.EdDSA, payload, nil)
|
||||
}
|
||||
|
||||
// VerifyEdDSA verifies an EdDSA (Ed25519) signature for the given payload.
|
||||
// This function verifies the signature using Ed25519 verification algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and provides strong security guarantees without requiring hash function selection.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyEdDSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyEdDSA(key ed25519.PublicKey, payload, signature []byte) error {
|
||||
if !ed25519.Verify(key, payload, signature) {
|
||||
return fmt.Errorf("invalid EdDSA signature")
|
||||
}
|
||||
return nil
|
||||
// Use dsig.Verify with EdDSA algorithm constant
|
||||
return dsig.Verify(key, dsig.EdDSA, payload, signature)
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build jwx_es256k
|
||||
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
dsigsecp256k1 "github.com/lestrrat-go/dsig-secp256k1"
|
||||
)
|
||||
|
||||
const es256k = "ES256K"
|
||||
|
||||
func init() {
|
||||
// Add ES256K mapping when this build tag is enabled
|
||||
jwsToDsigAlgorithm[es256k] = dsigsecp256k1.ECDSAWithSecp256k1AndSHA256
|
||||
}
|
||||
+28
-43
@@ -1,67 +1,52 @@
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
var hmacHashFuncs = map[string]func() hash.Hash{
|
||||
"HS256": sha256.New,
|
||||
"HS384": sha512.New384,
|
||||
"HS512": sha512.New,
|
||||
}
|
||||
|
||||
func isSupportedHMACAlgorithm(alg string) bool {
|
||||
_, ok := hmacHashFuncs[alg]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HMACHashFuncFor returns the appropriate hash function for the given HMAC algorithm.
|
||||
// Supported algorithms: HS256 (SHA-256), HS384 (SHA-384), HS512 (SHA-512).
|
||||
// Returns the hash function constructor and an error if the algorithm is unsupported.
|
||||
func HMACHashFuncFor(alg string) (func() hash.Hash, error) {
|
||||
if h, ok := hmacHashFuncs[alg]; ok {
|
||||
return h, nil
|
||||
// hmacHashToDsigAlgorithm maps HMAC hash function sizes to dsig algorithm constants
|
||||
func hmacHashToDsigAlgorithm(hfunc func() hash.Hash) (string, error) {
|
||||
h := hfunc()
|
||||
switch h.Size() {
|
||||
case 32: // SHA256
|
||||
return dsig.HMACWithSHA256, nil
|
||||
case 48: // SHA384
|
||||
return dsig.HMACWithSHA384, nil
|
||||
case 64: // SHA512
|
||||
return dsig.HMACWithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported HMAC hash function: size=%d", h.Size())
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported HMAC algorithm %s", alg)
|
||||
}
|
||||
|
||||
func toHMACKey(dst *[]byte, key any) error {
|
||||
if err := keyconv.ByteSliceKey(dst, key); err != nil {
|
||||
return fmt.Errorf(`jws.toHMACKey: invalid key type %T. []byte is required: %w`, key, err)
|
||||
}
|
||||
|
||||
if len(*dst) == 0 {
|
||||
return fmt.Errorf(`jws.toHMACKey: missing key while signing payload`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignHMAC generates an HMAC signature for the given payload using the specified hash function and key.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignHMAC. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignHMAC(key, payload []byte, hfunc func() hash.Hash) ([]byte, error) {
|
||||
h := hmac.New(hfunc, key)
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using hmac: %w`, err)
|
||||
dsigAlg, err := hmacHashToDsigAlgorithm(hfunc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignHMAC: %w", err)
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
|
||||
return dsig.Sign(key, dsigAlg, payload, nil)
|
||||
}
|
||||
|
||||
// VerifyHMAC verifies an HMAC signature for the given payload.
|
||||
// This function verifies the signature using the specified key and hash function.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyHMAC. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyHMAC(key, payload, signature []byte, hfunc func() hash.Hash) error {
|
||||
expected, err := SignHMAC(key, payload, hfunc)
|
||||
dsigAlg, err := hmacHashToDsigAlgorithm(hfunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign payload for verification: %w", err)
|
||||
return fmt.Errorf("jwsbb.VerifyHMAC: %w", err)
|
||||
}
|
||||
if !hmac.Equal(signature, expected) {
|
||||
return fmt.Errorf("invalid HMAC signature")
|
||||
}
|
||||
return nil
|
||||
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
+65
@@ -12,8 +12,40 @@
|
||||
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
|
||||
// 2. All exported functions are strongly typed (i.e. they do not take `any` types unless they absolutely have to).
|
||||
// 3. Does not rely on other public jwx packages (they are standalone, except for internal packages).
|
||||
//
|
||||
// This implementation uses github.com/lestrrat-go/dsig as the underlying signature provider.
|
||||
package jwsbb
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
// JWS algorithm name constants
|
||||
const (
|
||||
// HMAC algorithms
|
||||
hs256 = "HS256"
|
||||
hs384 = "HS384"
|
||||
hs512 = "HS512"
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
rs256 = "RS256"
|
||||
rs384 = "RS384"
|
||||
rs512 = "RS512"
|
||||
|
||||
// RSA PSS algorithms
|
||||
ps256 = "PS256"
|
||||
ps384 = "PS384"
|
||||
ps512 = "PS512"
|
||||
|
||||
// ECDSA algorithms
|
||||
es256 = "ES256"
|
||||
es384 = "ES384"
|
||||
es512 = "ES512"
|
||||
|
||||
// EdDSA algorithm
|
||||
edDSA = "EdDSA"
|
||||
)
|
||||
|
||||
// Signer is a generic interface that defines the method for signing payloads.
|
||||
// The type parameter K represents the key type (e.g., []byte for HMAC keys,
|
||||
// *rsa.PrivateKey for RSA keys, *ecdsa.PrivateKey for ECDSA keys).
|
||||
@@ -27,3 +59,36 @@ type Signer[K any] interface {
|
||||
type Verifier[K any] interface {
|
||||
Verify(key K, buf []byte, signature []byte) error
|
||||
}
|
||||
|
||||
// JWS to dsig algorithm mapping
|
||||
var jwsToDsigAlgorithm = map[string]string{
|
||||
// HMAC algorithms
|
||||
hs256: dsig.HMACWithSHA256,
|
||||
hs384: dsig.HMACWithSHA384,
|
||||
hs512: dsig.HMACWithSHA512,
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
rs256: dsig.RSAPKCS1v15WithSHA256,
|
||||
rs384: dsig.RSAPKCS1v15WithSHA384,
|
||||
rs512: dsig.RSAPKCS1v15WithSHA512,
|
||||
|
||||
// RSA PSS algorithms
|
||||
ps256: dsig.RSAPSSWithSHA256,
|
||||
ps384: dsig.RSAPSSWithSHA384,
|
||||
ps512: dsig.RSAPSSWithSHA512,
|
||||
|
||||
// ECDSA algorithms
|
||||
es256: dsig.ECDSAWithP256AndSHA256,
|
||||
es384: dsig.ECDSAWithP384AndSHA384,
|
||||
es512: dsig.ECDSAWithP521AndSHA512,
|
||||
// Note: ES256K requires external dependency and is handled separately
|
||||
|
||||
// EdDSA algorithm
|
||||
edDSA: dsig.EdDSA,
|
||||
}
|
||||
|
||||
// getDsigAlgorithm returns the dsig algorithm name for a JWS algorithm
|
||||
func getDsigAlgorithm(jwsAlg string) (string, bool) {
|
||||
dsigAlg, ok := jwsToDsigAlgorithm[jwsAlg]
|
||||
return dsigAlg, ok
|
||||
}
|
||||
|
||||
+40
-55
@@ -6,53 +6,33 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jws/internal/keytype"
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
func rsaGetSignerCryptoSignerKey(key any) (crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
if !keytype.IsValidRSAKey(key) {
|
||||
return nil, false, fmt.Errorf(`cannot use key of type %T`, key)
|
||||
// rsaHashToDsigAlgorithm maps RSA hash functions to dsig algorithm constants
|
||||
func rsaHashToDsigAlgorithm(h crypto.Hash, pss bool) (string, error) {
|
||||
if pss {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.RSAPSSWithSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.RSAPSSWithSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.RSAPSSWithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported hash algorithm for RSA-PSS: %v", h)
|
||||
}
|
||||
} else {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return dsig.RSAPKCS1v15WithSHA256, nil
|
||||
case crypto.SHA384:
|
||||
return dsig.RSAPKCS1v15WithSHA384, nil
|
||||
case crypto.SHA512:
|
||||
return dsig.RSAPKCS1v15WithSHA512, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported hash algorithm for RSA PKCS#1 v1.5: %v", h)
|
||||
}
|
||||
return cs, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
var rsaHashFuncs = map[string]struct {
|
||||
Hash crypto.Hash
|
||||
PSS bool // whether to use PSS padding
|
||||
}{
|
||||
"RS256": {Hash: crypto.SHA256, PSS: false},
|
||||
"RS384": {Hash: crypto.SHA384, PSS: false},
|
||||
"RS512": {Hash: crypto.SHA512, PSS: false},
|
||||
"PS256": {Hash: crypto.SHA256, PSS: true},
|
||||
"PS384": {Hash: crypto.SHA384, PSS: true},
|
||||
"PS512": {Hash: crypto.SHA512, PSS: true},
|
||||
}
|
||||
|
||||
func isSuppotedRSAAlgorithm(alg string) bool {
|
||||
_, ok := rsaHashFuncs[alg]
|
||||
return ok
|
||||
}
|
||||
|
||||
// RSAHashFuncFor returns the appropriate hash function and PSS flag for the given RSA algorithm.
|
||||
// Supported algorithms: RS256, RS384, RS512 (PKCS#1 v1.5) and PS256, PS384, PS512 (PSS).
|
||||
// Returns the hash function, PSS flag, and an error if the algorithm is unsupported.
|
||||
func RSAHashFuncFor(alg string) (crypto.Hash, bool, error) {
|
||||
if h, ok := rsaHashFuncs[alg]; ok {
|
||||
return h.Hash, h.PSS, nil
|
||||
}
|
||||
return 0, false, fmt.Errorf("unsupported RSA algorithm %s", alg)
|
||||
}
|
||||
|
||||
// RSAPSSOptions returns the PSS options for RSA-PSS signatures with the specified hash.
|
||||
// The salt length is set to equal the hash length as per RFC 7518.
|
||||
func RSAPSSOptions(h crypto.Hash) rsa.PSSOptions {
|
||||
return rsa.PSSOptions{
|
||||
Hash: h,
|
||||
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,25 +42,30 @@ func RSAPSSOptions(h crypto.Hash) rsa.PSSOptions {
|
||||
//
|
||||
// The rr parameter is an optional io.Reader that can be used to provide randomness for signing.
|
||||
// If rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.SignRSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func SignRSA(key *rsa.PrivateKey, payload []byte, h crypto.Hash, pss bool, rr io.Reader) ([]byte, error) {
|
||||
var opts crypto.SignerOpts = h
|
||||
if pss {
|
||||
rsaopts := RSAPSSOptions(h)
|
||||
opts = &rsaopts
|
||||
dsigAlg, err := rsaHashToDsigAlgorithm(h, pss)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwsbb.SignRSA: %w", err)
|
||||
}
|
||||
return cryptosign(key, payload, h, opts, rr)
|
||||
|
||||
return dsig.Sign(key, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
// VerifyRSA verifies an RSA signature for the given payload and header.
|
||||
// This function constructs the signing input by encoding the header and payload according to JWS specification,
|
||||
// then verifies the signature using the specified public key and hash algorithm.
|
||||
// If pss is true, RSA-PSS verification is used; otherwise, PKCS#1 v1.5 verification is used.
|
||||
//
|
||||
// This function is now a thin wrapper around dsig.VerifyRSA. For new projects, you should
|
||||
// consider using dsig instead of this function.
|
||||
func VerifyRSA(key *rsa.PublicKey, payload, signature []byte, h crypto.Hash, pss bool) error {
|
||||
hasher := h.New()
|
||||
hasher.Write(payload)
|
||||
digest := hasher.Sum(nil)
|
||||
if pss {
|
||||
return rsa.VerifyPSS(key, h, digest, signature, &rsa.PSSOptions{Hash: h, SaltLength: rsa.PSSSaltLengthEqualsHash})
|
||||
dsigAlg, err := rsaHashToDsigAlgorithm(h, pss)
|
||||
if err != nil {
|
||||
return fmt.Errorf("jwsbb.VerifyRSA: %w", err)
|
||||
}
|
||||
return rsa.VerifyPKCS1v15(key, h, digest, signature)
|
||||
|
||||
return dsig.Verify(key, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
+71
-56
@@ -2,94 +2,109 @@ package jwsbb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
)
|
||||
|
||||
// Sign generates a JWS signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the signer registered in the hwsbb package _ONLY_.
|
||||
// This function loads the signer registered in the jwsbb package _ONLY_.
|
||||
// It does not support custom signers that the user might have registered.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
// Not all algorithms require this parameter, but it is included for consistency.
|
||||
// 99% of the time, you can pass nil for rr, and it will work fine.
|
||||
func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
switch {
|
||||
case isSupportedHMACAlgorithm(alg):
|
||||
return dispatchHMACSign(key, alg, payload)
|
||||
case isSuppotedRSAAlgorithm(alg):
|
||||
return dispatchRSASign(key, alg, payload, rr)
|
||||
case isSuppotedECDSAAlgorithm(alg):
|
||||
return dispatchECDSASign(key, alg, payload, rr)
|
||||
case isSupportedEdDSAAlgorithm(alg):
|
||||
return dispatchEdDSASign(key, alg, payload, rr)
|
||||
dsigAlg, ok := getDsigAlgorithm(alg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: unsupported signature algorithm %q`, alg)
|
||||
// Get dsig algorithm info to determine key conversion strategy
|
||||
dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: dsig algorithm %q not registered`, dsigAlg)
|
||||
}
|
||||
|
||||
switch dsigInfo.Family {
|
||||
case dsig.HMAC:
|
||||
return dispatchHMACSign(key, dsigAlg, payload)
|
||||
case dsig.RSA:
|
||||
return dispatchRSASign(key, dsigAlg, payload, rr)
|
||||
case dsig.ECDSA:
|
||||
return dispatchECDSASign(key, dsigAlg, payload, rr)
|
||||
case dsig.EdDSAFamily:
|
||||
return dispatchEdDSASign(key, dsigAlg, payload, rr)
|
||||
default:
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: unsupported dsig algorithm family %q`, dsigInfo.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACSign(key any, alg string, payload []byte) ([]byte, error) {
|
||||
h, err := HMACHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: failed to get hash function for %s: %w`, alg, err)
|
||||
}
|
||||
|
||||
func dispatchHMACSign(key any, dsigAlg string, payload []byte) ([]byte, error) {
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: %w`, err)
|
||||
if err := keyconv.ByteSliceKey(&hmackey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. []byte is required: %w`, key, err)
|
||||
}
|
||||
return SignHMAC(hmackey, payload, h)
|
||||
|
||||
return dsig.Sign(hmackey, dsigAlg, payload, nil)
|
||||
}
|
||||
|
||||
func dispatchRSASign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
h, pss, err := RSAHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: failed to get hash function for %s: %w`, alg, err)
|
||||
}
|
||||
cs, isCryptoSigner, err := rsaGetSignerCryptoSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
var options crypto.SignerOpts = h
|
||||
if pss {
|
||||
rsaopts := RSAPSSOptions(h)
|
||||
options = &rsaopts
|
||||
func dispatchRSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an RSA key
|
||||
if _, ok := signer.Public().(*rsa.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
return SignCryptoSigner(cs, payload, h, options, rr)
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey *rsa.PrivateKey
|
||||
if err := keyconv.RSAPrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jws.RSASigner: invalid key type %T. rsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
return SignRSA(privkey, payload, h, pss, rr)
|
||||
}
|
||||
|
||||
func dispatchEdDSASign(key any, _ string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
signer, err := eddsaGetSigner(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jws.EdDSASigner: %w`, err)
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *rsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return SignCryptoSigner(signer, payload, crypto.Hash(0), crypto.Hash(0), rr)
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
func dispatchECDSASign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
h, err := ECDSAHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: failed to get hash function for %s: %w`, alg, err)
|
||||
func dispatchECDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an ECDSA key
|
||||
if _, ok := signer.Public().(*ecdsa.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
}
|
||||
privkey, cs, isCryptoSigner, err := ecdsaGetSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jws.ECDSASigner: %w`, err)
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey *ecdsa.PrivateKey
|
||||
if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *ecdsa.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return SignECDSACryptoSigner(cs, payload, h, rr)
|
||||
}
|
||||
return SignECDSA(privkey, payload, h, rr)
|
||||
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
func dispatchEdDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an EdDSA key
|
||||
if _, ok := signer.Public().(ed25519.PublicKey); ok {
|
||||
return dsig.Sign(signer, dsigAlg, payload, rr)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var privkey ed25519.PrivateKey
|
||||
if err := keyconv.Ed25519PrivateKey(&privkey, key); err != nil {
|
||||
return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. ed25519.PrivateKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Sign(privkey, dsigAlg, payload, rr)
|
||||
}
|
||||
|
||||
+65
-87
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
)
|
||||
|
||||
@@ -15,113 +16,90 @@ import (
|
||||
// This function loads the verifier registered in the jwsbb package _ONLY_.
|
||||
// It does not support custom verifiers that the user might have registered.
|
||||
func Verify(key any, alg string, payload, signature []byte) error {
|
||||
switch {
|
||||
case isSupportedHMACAlgorithm(alg):
|
||||
return dispatchHMACVerify(key, alg, payload, signature)
|
||||
case isSuppotedRSAAlgorithm(alg):
|
||||
return dispatchRSAVerify(key, alg, payload, signature)
|
||||
case isSuppotedECDSAAlgorithm(alg):
|
||||
return dispatchECDSAVerify(key, alg, payload, signature)
|
||||
case isSupportedEdDSAAlgorithm(alg):
|
||||
return dispatchEdDSAVerify(key, alg, payload, signature)
|
||||
dsigAlg, ok := getDsigAlgorithm(alg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`jwsbb.Verify: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
return fmt.Errorf(`jwsbb.Verify: unsupported signature algorithm %q`, alg)
|
||||
// Get dsig algorithm info to determine key conversion strategy
|
||||
dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`jwsbb.Verify: dsig algorithm %q not registered`, dsigAlg)
|
||||
}
|
||||
|
||||
switch dsigInfo.Family {
|
||||
case dsig.HMAC:
|
||||
return dispatchHMACVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.RSA:
|
||||
return dispatchRSAVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.ECDSA:
|
||||
return dispatchECDSAVerify(key, dsigAlg, payload, signature)
|
||||
case dsig.EdDSAFamily:
|
||||
return dispatchEdDSAVerify(key, dsigAlg, payload, signature)
|
||||
default:
|
||||
return fmt.Errorf(`jwsbb.Verify: unsupported dsig algorithm family %q`, dsigInfo.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACVerify(key any, alg string, payload, signature []byte) error {
|
||||
h, err := HMACHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to get hash function for %s: %w`, alg, err)
|
||||
}
|
||||
|
||||
func dispatchHMACVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: %w`, err)
|
||||
if err := keyconv.ByteSliceKey(&hmackey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. []byte is required: %w`, key, err)
|
||||
}
|
||||
return VerifyHMAC(hmackey, payload, signature, h)
|
||||
|
||||
return dsig.Verify(hmackey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchRSAVerify(key any, alg string, payload, signature []byte) error {
|
||||
h, pss, err := RSAHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to get hash function for %s: %w`, alg, err)
|
||||
func dispatchRSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an RSA key
|
||||
if _, ok := signer.Public().(*rsa.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey *rsa.PublicKey
|
||||
|
||||
if cs, ok := key.(crypto.Signer); ok {
|
||||
cpub := cs.Public()
|
||||
switch cpub := cpub.(type) {
|
||||
case rsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *rsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to retrieve rsa.PublicKey out of crypto.Signer %T`, key)
|
||||
}
|
||||
} else {
|
||||
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to retrieve rsa.PublicKey out of %T: %w`, key, err)
|
||||
}
|
||||
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *rsa.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return VerifyRSA(pubkey, payload, signature, h, pss)
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchECDSAVerify(key any, alg string, payload, signature []byte) error {
|
||||
h, err := ECDSAHashFuncFor(alg)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to get hash function for %s: %w`, alg, err)
|
||||
}
|
||||
|
||||
pubkey, cs, isCryptoSigner, err := ecdsaGetVerifierKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return VerifyECDSACryptoSigner(cs, payload, signature, h)
|
||||
}
|
||||
return VerifyECDSA(pubkey, payload, signature, h)
|
||||
}
|
||||
|
||||
func dispatchEdDSAVerify(key any, _ string, payload, signature []byte) error {
|
||||
var pubkey ed25519.PublicKey
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
v := signer.Public()
|
||||
pubkey, ok = v.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`jwsbb.Verify: expected crypto.Signer.Public() to return ed25519.PublicKey, but got %T`, v)
|
||||
}
|
||||
} else {
|
||||
if err := keyconv.Ed25519PublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: failed to retrieve ed25519.PublicKey out of %T: %w`, key, err)
|
||||
func dispatchECDSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an ECDSA key
|
||||
if _, ok := signer.Public().(*ecdsa.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifyEdDSA(pubkey, payload, signature)
|
||||
}
|
||||
|
||||
func ecdsaGetVerifierKey(key any) (*ecdsa.PublicKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
switch key.(type) {
|
||||
case ecdsa.PublicKey, *ecdsa.PublicKey:
|
||||
// if it's ecdsa.PublicKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey *ecdsa.PublicKey
|
||||
if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. ecdsa.PublicKey is required: %w`, key, err)
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *ecdsa.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return pubkey, nil, false, nil
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
func dispatchEdDSAVerify(key any, dsigAlg string, payload, signature []byte) error {
|
||||
// Try crypto.Signer first (dsig can handle it directly)
|
||||
if signer, ok := key.(crypto.Signer); ok {
|
||||
// Verify it's an EdDSA key
|
||||
if _, ok := signer.Public().(ed25519.PublicKey); ok {
|
||||
return dsig.Verify(signer, dsigAlg, payload, signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to concrete key types
|
||||
var pubkey ed25519.PublicKey
|
||||
if err := keyconv.Ed25519PublicKey(&pubkey, key); err != nil {
|
||||
return fmt.Errorf(`jwsbb.Verify: invalid key type %T. ed25519.PublicKey is required: %w`, key, err)
|
||||
}
|
||||
|
||||
return dsig.Verify(pubkey, dsigAlg, payload, signature)
|
||||
}
|
||||
|
||||
+2
-1
@@ -95,7 +95,8 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
|
||||
help: help,
|
||||
variableLabels: variableLabels.compile(),
|
||||
}
|
||||
if !model.IsValidMetricName(model.LabelValue(fqName)) {
|
||||
//nolint:staticcheck // TODO: Don't use deprecated model.NameValidationScheme.
|
||||
if !model.NameValidationScheme.IsValidMetricName(fqName) {
|
||||
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
||||
return d
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -67,7 +67,7 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
|
||||
}
|
||||
|
||||
// Our current conversion moves to legacy naming, so use legacy validation.
|
||||
valid := model.IsValidLegacyMetricName(namespace + "_" + subsystem + "_" + name)
|
||||
valid := model.LegacyValidation.IsValidMetricName(namespace + "_" + subsystem + "_" + name)
|
||||
switch d.Kind {
|
||||
case metrics.KindUint64:
|
||||
case metrics.KindFloat64:
|
||||
|
||||
+2
-1
@@ -184,5 +184,6 @@ func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
||||
}
|
||||
|
||||
func checkLabelName(l string) bool {
|
||||
return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
|
||||
//nolint:staticcheck // TODO: Don't use deprecated model.NameValidationScheme.
|
||||
return model.NameValidationScheme.IsValidLabelName(l) && !strings.HasPrefix(l, reservedLabelPrefix)
|
||||
}
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ func CollectAndFormat(c prometheus.Collector, format expfmt.FormatType, metricNa
|
||||
// convertReaderToMetricFamily would read from a io.Reader object and convert it to a slice of
|
||||
// dto.MetricFamily.
|
||||
func convertReaderToMetricFamily(reader io.Reader) ([]*dto.MetricFamily, error) {
|
||||
var tp expfmt.TextParser
|
||||
tp := expfmt.NewTextParser(model.UTF8Validation)
|
||||
notNormalized, err := tp.TextToMetricFamilies(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting reader to metric families failed: %w", err)
|
||||
|
||||
+32
-7
@@ -70,19 +70,34 @@ func ResponseFormat(h http.Header) Format {
|
||||
return FmtUnknown
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder based on the given input format.
|
||||
// If the input format does not imply otherwise, a text format decoder is returned.
|
||||
// NewDecoder returns a new decoder based on the given input format. Metric
|
||||
// names are validated based on the provided Format -- if the format requires
|
||||
// escaping, raditional Prometheues validity checking is used. Otherwise, names
|
||||
// are checked for UTF-8 validity. Supported formats include delimited protobuf
|
||||
// and Prometheus text format. For historical reasons, this decoder fallbacks
|
||||
// to classic text decoding for any other format. This decoder does not fully
|
||||
// support OpenMetrics although it may often succeed due to the similarities
|
||||
// between the formats. This decoder may not support the latest features of
|
||||
// Prometheus text format and is not intended for high-performance applications.
|
||||
// See: https://github.com/prometheus/common/issues/812
|
||||
func NewDecoder(r io.Reader, format Format) Decoder {
|
||||
scheme := model.LegacyValidation
|
||||
if format.ToEscapingScheme() == model.NoEscaping {
|
||||
scheme = model.UTF8Validation
|
||||
}
|
||||
switch format.FormatType() {
|
||||
case TypeProtoDelim:
|
||||
return &protoDecoder{r: bufio.NewReader(r)}
|
||||
return &protoDecoder{r: bufio.NewReader(r), s: scheme}
|
||||
case TypeProtoText, TypeProtoCompact:
|
||||
return &errDecoder{err: fmt.Errorf("format %s not supported for decoding", format)}
|
||||
}
|
||||
return &textDecoder{r: r}
|
||||
return &textDecoder{r: r, s: scheme}
|
||||
}
|
||||
|
||||
// protoDecoder implements the Decoder interface for protocol buffers.
|
||||
type protoDecoder struct {
|
||||
r protodelim.Reader
|
||||
s model.ValidationScheme
|
||||
}
|
||||
|
||||
// Decode implements the Decoder interface.
|
||||
@@ -93,7 +108,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
|
||||
if err := opts.UnmarshalFrom(d.r, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
|
||||
if !d.s.IsValidMetricName(v.GetName()) {
|
||||
return fmt.Errorf("invalid metric name %q", v.GetName())
|
||||
}
|
||||
for _, m := range v.GetMetric() {
|
||||
@@ -107,7 +122,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
|
||||
if !model.LabelValue(l.GetValue()).IsValid() {
|
||||
return fmt.Errorf("invalid label value %q", l.GetValue())
|
||||
}
|
||||
if !model.LabelName(l.GetName()).IsValid() {
|
||||
if !d.s.IsValidLabelName(l.GetName()) {
|
||||
return fmt.Errorf("invalid label name %q", l.GetName())
|
||||
}
|
||||
}
|
||||
@@ -115,10 +130,20 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// errDecoder is an error-state decoder that always returns the same error.
|
||||
type errDecoder struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *errDecoder) Decode(*dto.MetricFamily) error {
|
||||
return d.err
|
||||
}
|
||||
|
||||
// textDecoder implements the Decoder interface for the text protocol.
|
||||
type textDecoder struct {
|
||||
r io.Reader
|
||||
fams map[string]*dto.MetricFamily
|
||||
s model.ValidationScheme
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -126,7 +151,7 @@ type textDecoder struct {
|
||||
func (d *textDecoder) Decode(v *dto.MetricFamily) error {
|
||||
if d.err == nil {
|
||||
// Read all metrics in one shot.
|
||||
var p TextParser
|
||||
p := NewTextParser(d.s)
|
||||
d.fams, d.err = p.TextToMetricFamilies(d.r)
|
||||
// If we don't get an error, store io.EOF for the end.
|
||||
if d.err == nil {
|
||||
|
||||
+4
-6
@@ -18,14 +18,12 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/munnerz/goautoneg"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"google.golang.org/protobuf/encoding/protodelim"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/munnerz/goautoneg"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// Encoder types encode metric families into an underlying wire protocol.
|
||||
@@ -61,7 +59,7 @@ func (ec encoderCloser) Close() error {
|
||||
// appropriate accepted type is found, FmtText is returned (which is the
|
||||
// Prometheus text format). This function will never negotiate FmtOpenMetrics,
|
||||
// as the support is still experimental. To include the option to negotiate
|
||||
// FmtOpenMetrics, use NegotiateOpenMetrics.
|
||||
// FmtOpenMetrics, use NegotiateIncludingOpenMetrics.
|
||||
func Negotiate(h http.Header) Format {
|
||||
escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
|
||||
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
|
||||
@@ -153,7 +151,7 @@ func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder {
|
||||
case TypeProtoDelim:
|
||||
return encoderCloser{
|
||||
encode: func(v *dto.MetricFamily) error {
|
||||
_, err := protodelim.MarshalTo(w, v)
|
||||
_, err := protodelim.MarshalTo(w, model.EscapeMetricFamily(v, escapingScheme))
|
||||
return err
|
||||
},
|
||||
close: func() error { return nil },
|
||||
|
||||
+8
-4
@@ -36,9 +36,11 @@ const (
|
||||
ProtoType = `application/vnd.google.protobuf`
|
||||
ProtoProtocol = `io.prometheus.client.MetricFamily`
|
||||
// Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
|
||||
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
|
||||
OpenMetricsType = `application/openmetrics-text`
|
||||
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
|
||||
OpenMetricsType = `application/openmetrics-text`
|
||||
//nolint:revive // Allow for underscores.
|
||||
OpenMetricsVersion_0_0_1 = "0.0.1"
|
||||
//nolint:revive // Allow for underscores.
|
||||
OpenMetricsVersion_1_0_0 = "1.0.0"
|
||||
|
||||
// The Content-Type values for the different wire protocols. Do not do direct
|
||||
@@ -54,8 +56,10 @@ const (
|
||||
// Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
|
||||
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
|
||||
// Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
|
||||
//nolint:revive // Allow for underscores.
|
||||
FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
|
||||
// Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
|
||||
//nolint:revive // Allow for underscores.
|
||||
FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
|
||||
)
|
||||
|
||||
@@ -188,8 +192,8 @@ func (f Format) FormatType() FormatType {
|
||||
// Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid
|
||||
// "escaping" term exists, that will be used. Otherwise, the global default will
|
||||
// be returned.
|
||||
func (format Format) ToEscapingScheme() model.EscapingScheme {
|
||||
for _, p := range strings.Split(string(format), ";") {
|
||||
func (f Format) ToEscapingScheme() model.EscapingScheme {
|
||||
for _, p := range strings.Split(string(f), ";") {
|
||||
toks := strings.Split(p, "=")
|
||||
if len(toks) != 2 {
|
||||
continue
|
||||
|
||||
+6
-3
@@ -17,7 +17,11 @@
|
||||
|
||||
package expfmt
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
|
||||
//
|
||||
@@ -26,9 +30,8 @@ import "bytes"
|
||||
//
|
||||
// Further input samples should go in the folder fuzz/corpus.
|
||||
func Fuzz(in []byte) int {
|
||||
parser := TextParser{}
|
||||
parser := NewTextParser(model.UTF8Validation)
|
||||
_, err := parser.TextToMetricFamilies(bytes.NewReader(in))
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
+5
-6
@@ -22,11 +22,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
type encoderOption struct {
|
||||
@@ -249,7 +248,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
|
||||
// Finally the samples, one line for each.
|
||||
if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") {
|
||||
compliantName = compliantName + "_total"
|
||||
compliantName += "_total"
|
||||
}
|
||||
for _, metric := range in.Metric {
|
||||
switch metricType {
|
||||
@@ -477,7 +476,7 @@ func writeOpenMetricsNameAndLabelPairs(
|
||||
if name != "" {
|
||||
// If the name does not pass the legacy validity check, we must put the
|
||||
// metric name inside the braces, quoted.
|
||||
if !model.IsValidLegacyMetricName(name) {
|
||||
if !model.LegacyValidation.IsValidMetricName(name) {
|
||||
metricInsideBraces = true
|
||||
err := w.WriteByte(separator)
|
||||
written++
|
||||
@@ -641,11 +640,11 @@ func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
err = (*e).Timestamp.CheckValid()
|
||||
err = e.Timestamp.CheckValid()
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
ts := (*e).Timestamp.AsTime()
|
||||
ts := e.Timestamp.AsTime()
|
||||
// TODO(beorn7): Format this directly from components of ts to
|
||||
// avoid overflow/underflow and precision issues of the float
|
||||
// conversion.
|
||||
|
||||
+4
-4
@@ -22,9 +22,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
|
||||
@@ -354,7 +354,7 @@ func writeNameAndLabelPairs(
|
||||
if name != "" {
|
||||
// If the name does not pass the legacy validity check, we must put the
|
||||
// metric name inside the braces.
|
||||
if !model.IsValidLegacyMetricName(name) {
|
||||
if !model.LegacyValidation.IsValidMetricName(name) {
|
||||
metricInsideBraces = true
|
||||
err := w.WriteByte(separator)
|
||||
written++
|
||||
@@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) {
|
||||
// writeName writes a string as-is if it complies with the legacy naming
|
||||
// scheme, or escapes it in double quotes if not.
|
||||
func writeName(w enhancedWriter, name string) (int, error) {
|
||||
if model.IsValidLegacyMetricName(name) {
|
||||
if model.LegacyValidation.IsValidMetricName(name) {
|
||||
return w.WriteString(name)
|
||||
}
|
||||
var written int
|
||||
|
||||
+38
-6
@@ -78,6 +78,14 @@ type TextParser struct {
|
||||
// These indicate if the metric name from the current line being parsed is inside
|
||||
// braces and if that metric name was found respectively.
|
||||
currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool
|
||||
// scheme sets the desired ValidationScheme for names. Defaults to the invalid
|
||||
// UnsetValidation.
|
||||
scheme model.ValidationScheme
|
||||
}
|
||||
|
||||
// NewTextParser returns a new TextParser with the provided nameValidationScheme.
|
||||
func NewTextParser(nameValidationScheme model.ValidationScheme) TextParser {
|
||||
return TextParser{scheme: nameValidationScheme}
|
||||
}
|
||||
|
||||
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
|
||||
@@ -126,6 +134,7 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF
|
||||
|
||||
func (p *TextParser) reset(in io.Reader) {
|
||||
p.metricFamiliesByName = map[string]*dto.MetricFamily{}
|
||||
p.currentLabelPairs = nil
|
||||
if p.buf == nil {
|
||||
p.buf = bufio.NewReader(in)
|
||||
} else {
|
||||
@@ -216,6 +225,9 @@ func (p *TextParser) startComment() stateFn {
|
||||
return nil
|
||||
}
|
||||
p.setOrCreateCurrentMF()
|
||||
if p.err != nil {
|
||||
return nil
|
||||
}
|
||||
if p.skipBlankTab(); p.err != nil {
|
||||
return nil // Unexpected end of input.
|
||||
}
|
||||
@@ -244,6 +256,9 @@ func (p *TextParser) readingMetricName() stateFn {
|
||||
return nil
|
||||
}
|
||||
p.setOrCreateCurrentMF()
|
||||
if p.err != nil {
|
||||
return nil
|
||||
}
|
||||
// Now is the time to fix the type if it hasn't happened yet.
|
||||
if p.currentMF.Type == nil {
|
||||
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
|
||||
@@ -311,6 +326,9 @@ func (p *TextParser) startLabelName() stateFn {
|
||||
switch p.currentByte {
|
||||
case ',':
|
||||
p.setOrCreateCurrentMF()
|
||||
if p.err != nil {
|
||||
return nil
|
||||
}
|
||||
if p.currentMF.Type == nil {
|
||||
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
|
||||
}
|
||||
@@ -319,6 +337,10 @@ func (p *TextParser) startLabelName() stateFn {
|
||||
return p.startLabelName
|
||||
case '}':
|
||||
p.setOrCreateCurrentMF()
|
||||
if p.err != nil {
|
||||
p.currentLabelPairs = nil
|
||||
return nil
|
||||
}
|
||||
if p.currentMF.Type == nil {
|
||||
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
|
||||
}
|
||||
@@ -341,6 +363,12 @@ func (p *TextParser) startLabelName() stateFn {
|
||||
p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
|
||||
if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
|
||||
p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
|
||||
p.currentLabelPairs = nil
|
||||
return nil
|
||||
}
|
||||
if !p.scheme.IsValidLabelName(p.currentLabelPair.GetName()) {
|
||||
p.parseError(fmt.Sprintf("invalid label name %q", p.currentLabelPair.GetName()))
|
||||
p.currentLabelPairs = nil
|
||||
return nil
|
||||
}
|
||||
// Special summary/histogram treatment. Don't add 'quantile' and 'le'
|
||||
@@ -353,13 +381,12 @@ func (p *TextParser) startLabelName() stateFn {
|
||||
labels := make(map[string]struct{})
|
||||
for _, l := range p.currentLabelPairs {
|
||||
lName := l.GetName()
|
||||
if _, exists := labels[lName]; !exists {
|
||||
labels[lName] = struct{}{}
|
||||
} else {
|
||||
if _, exists := labels[lName]; exists {
|
||||
p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName()))
|
||||
p.currentLabelPairs = nil
|
||||
return nil
|
||||
}
|
||||
labels[lName] = struct{}{}
|
||||
}
|
||||
return p.startLabelValue
|
||||
}
|
||||
@@ -440,7 +467,8 @@ func (p *TextParser) readingValue() stateFn {
|
||||
// When we are here, we have read all the labels, so for the
|
||||
// special case of a summary/histogram, we can finally find out
|
||||
// if the metric already exists.
|
||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
||||
switch p.currentMF.GetType() {
|
||||
case dto.MetricType_SUMMARY:
|
||||
signature := model.LabelsToSignature(p.currentLabels)
|
||||
if summary := p.summaries[signature]; summary != nil {
|
||||
p.currentMetric = summary
|
||||
@@ -448,7 +476,7 @@ func (p *TextParser) readingValue() stateFn {
|
||||
p.summaries[signature] = p.currentMetric
|
||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
||||
}
|
||||
} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
signature := model.LabelsToSignature(p.currentLabels)
|
||||
if histogram := p.histograms[signature]; histogram != nil {
|
||||
p.currentMetric = histogram
|
||||
@@ -456,7 +484,7 @@ func (p *TextParser) readingValue() stateFn {
|
||||
p.histograms[signature] = p.currentMetric
|
||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
||||
}
|
||||
if p.readTokenUntilWhitespace(); p.err != nil {
|
||||
@@ -805,6 +833,10 @@ func (p *TextParser) setOrCreateCurrentMF() {
|
||||
p.currentIsHistogramCount = false
|
||||
p.currentIsHistogramSum = false
|
||||
name := p.currentToken.String()
|
||||
if !p.scheme.IsValidMetricName(name) {
|
||||
p.parseError(fmt.Sprintf("invalid metric name %q", name))
|
||||
return
|
||||
}
|
||||
if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+14
-21
@@ -32,6 +32,12 @@ const (
|
||||
// MetricNameLabel is the label name indicating the metric name of a
|
||||
// timeseries.
|
||||
MetricNameLabel = "__name__"
|
||||
// MetricTypeLabel is the label name indicating the metric type of
|
||||
// timeseries as per the PROM-39 proposal.
|
||||
MetricTypeLabel = "__type__"
|
||||
// MetricUnitLabel is the label name indicating the metric unit of
|
||||
// timeseries as per the PROM-39 proposal.
|
||||
MetricUnitLabel = "__unit__"
|
||||
|
||||
// SchemeLabel is the name of the label that holds the scheme on which to
|
||||
// scrape a target.
|
||||
@@ -100,34 +106,21 @@ type LabelName string
|
||||
// IsValid returns true iff the name matches the pattern of LabelNameRE when
|
||||
// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if
|
||||
// NameValidationScheme is set to UTF8Validation.
|
||||
//
|
||||
// Deprecated: This method should not be used and may be removed in the future.
|
||||
// Use [ValidationScheme.IsValidLabelName] instead.
|
||||
func (ln LabelName) IsValid() bool {
|
||||
if len(ln) == 0 {
|
||||
return false
|
||||
}
|
||||
switch NameValidationScheme {
|
||||
case LegacyValidation:
|
||||
return ln.IsValidLegacy()
|
||||
case UTF8Validation:
|
||||
return utf8.ValidString(string(ln))
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
|
||||
}
|
||||
return NameValidationScheme.IsValidLabelName(string(ln))
|
||||
}
|
||||
|
||||
// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for
|
||||
// legacy names. It does not use LabelNameRE for the check but a much faster
|
||||
// hardcoded implementation.
|
||||
//
|
||||
// Deprecated: This method should not be used and may be removed in the future.
|
||||
// Use [LegacyValidation.IsValidLabelName] instead.
|
||||
func (ln LabelName) IsValidLegacy() bool {
|
||||
if len(ln) == 0 {
|
||||
return false
|
||||
}
|
||||
for i, b := range ln {
|
||||
// TODO: Apply De Morgan's law. Make sure there are tests for this.
|
||||
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return LegacyValidation.IsValidLabelName(string(ln))
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
|
||||
+5
-5
@@ -114,10 +114,10 @@ func (ls LabelSet) Clone() LabelSet {
|
||||
}
|
||||
|
||||
// Merge is a helper function to non-destructively merge two label sets.
|
||||
func (l LabelSet) Merge(other LabelSet) LabelSet {
|
||||
result := make(LabelSet, len(l))
|
||||
func (ls LabelSet) Merge(other LabelSet) LabelSet {
|
||||
result := make(LabelSet, len(ls))
|
||||
|
||||
for k, v := range l {
|
||||
for k, v := range ls {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func (ls LabelSet) FastFingerprint() Fingerprint {
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (l *LabelSet) UnmarshalJSON(b []byte) error {
|
||||
func (ls *LabelSet) UnmarshalJSON(b []byte) error {
|
||||
var m map[LabelName]LabelValue
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
@@ -153,6 +153,6 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error {
|
||||
return fmt.Errorf("%q is not a valid label name", ln)
|
||||
}
|
||||
}
|
||||
*l = LabelSet(m)
|
||||
*ls = LabelSet(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
+162
-34
@@ -14,6 +14,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"go.yaml.in/yaml/v2"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -62,16 +64,151 @@ var (
|
||||
type ValidationScheme int
|
||||
|
||||
const (
|
||||
// UnsetValidation represents an undefined ValidationScheme.
|
||||
// Should not be used in practice.
|
||||
UnsetValidation ValidationScheme = iota
|
||||
|
||||
// LegacyValidation is a setting that requires that all metric and label names
|
||||
// conform to the original Prometheus character requirements described by
|
||||
// MetricNameRE and LabelNameRE.
|
||||
LegacyValidation ValidationScheme = iota
|
||||
LegacyValidation
|
||||
|
||||
// UTF8Validation only requires that metric and label names be valid UTF-8
|
||||
// strings.
|
||||
UTF8Validation
|
||||
)
|
||||
|
||||
var _ interface {
|
||||
yaml.Marshaler
|
||||
yaml.Unmarshaler
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
fmt.Stringer
|
||||
} = new(ValidationScheme)
|
||||
|
||||
// String returns the string representation of s.
|
||||
func (s ValidationScheme) String() string {
|
||||
switch s {
|
||||
case UnsetValidation:
|
||||
return "unset"
|
||||
case LegacyValidation:
|
||||
return "legacy"
|
||||
case UTF8Validation:
|
||||
return "utf8"
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled ValidationScheme: %d", s))
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (s ValidationScheme) MarshalYAML() (any, error) {
|
||||
switch s {
|
||||
case UnsetValidation:
|
||||
return "", nil
|
||||
case LegacyValidation, UTF8Validation:
|
||||
return s.String(), nil
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled ValidationScheme: %d", s))
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var scheme string
|
||||
if err := unmarshal(&scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Set(scheme)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s ValidationScheme) MarshalJSON() ([]byte, error) {
|
||||
switch s {
|
||||
case UnsetValidation:
|
||||
return json.Marshal("")
|
||||
case UTF8Validation, LegacyValidation:
|
||||
return json.Marshal(s.String())
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled ValidationScheme: %d", s)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (s *ValidationScheme) UnmarshalJSON(bytes []byte) error {
|
||||
var repr string
|
||||
if err := json.Unmarshal(bytes, &repr); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Set(repr)
|
||||
}
|
||||
|
||||
// Set implements the pflag.Value interface.
|
||||
func (s *ValidationScheme) Set(text string) error {
|
||||
switch text {
|
||||
case "":
|
||||
// Don't change the value.
|
||||
case LegacyValidation.String():
|
||||
*s = LegacyValidation
|
||||
case UTF8Validation.String():
|
||||
*s = UTF8Validation
|
||||
default:
|
||||
return fmt.Errorf("unrecognized ValidationScheme: %q", text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValidMetricName returns whether metricName is valid according to s.
|
||||
func (s ValidationScheme) IsValidMetricName(metricName string) bool {
|
||||
switch s {
|
||||
case LegacyValidation:
|
||||
if len(metricName) == 0 {
|
||||
return false
|
||||
}
|
||||
for i, b := range metricName {
|
||||
if !isValidLegacyRune(b, i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case UTF8Validation:
|
||||
if len(metricName) == 0 {
|
||||
return false
|
||||
}
|
||||
return utf8.ValidString(metricName)
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidLabelName returns whether labelName is valid according to s.
|
||||
func (s ValidationScheme) IsValidLabelName(labelName string) bool {
|
||||
switch s {
|
||||
case LegacyValidation:
|
||||
if len(labelName) == 0 {
|
||||
return false
|
||||
}
|
||||
for i, b := range labelName {
|
||||
// TODO: Apply De Morgan's law. Make sure there are tests for this.
|
||||
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case UTF8Validation:
|
||||
if len(labelName) == 0 {
|
||||
return false
|
||||
}
|
||||
return utf8.ValidString(labelName)
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s))
|
||||
}
|
||||
}
|
||||
|
||||
// Type implements the pflag.Value interface.
|
||||
func (ValidationScheme) Type() string {
|
||||
return "validationScheme"
|
||||
}
|
||||
|
||||
type EscapingScheme int
|
||||
|
||||
const (
|
||||
@@ -101,7 +238,7 @@ const (
|
||||
// Accept header, the default NameEscapingScheme will be used.
|
||||
EscapingKey = "escaping"
|
||||
|
||||
// Possible values for Escaping Key:
|
||||
// Possible values for Escaping Key.
|
||||
AllowUTF8 = "allow-utf-8" // No escaping required.
|
||||
EscapeUnderscores = "underscores"
|
||||
EscapeDots = "dots"
|
||||
@@ -175,34 +312,22 @@ func (m Metric) FastFingerprint() Fingerprint {
|
||||
// IsValidMetricName returns true iff name matches the pattern of MetricNameRE
|
||||
// for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is
|
||||
// selected.
|
||||
//
|
||||
// Deprecated: This function should not be used and might be removed in the future.
|
||||
// Use [ValidationScheme.IsValidMetricName] instead.
|
||||
func IsValidMetricName(n LabelValue) bool {
|
||||
switch NameValidationScheme {
|
||||
case LegacyValidation:
|
||||
return IsValidLegacyMetricName(string(n))
|
||||
case UTF8Validation:
|
||||
if len(n) == 0 {
|
||||
return false
|
||||
}
|
||||
return utf8.ValidString(string(n))
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
|
||||
}
|
||||
return NameValidationScheme.IsValidMetricName(string(n))
|
||||
}
|
||||
|
||||
// IsValidLegacyMetricName is similar to IsValidMetricName but always uses the
|
||||
// legacy validation scheme regardless of the value of NameValidationScheme.
|
||||
// This function, however, does not use MetricNameRE for the check but a much
|
||||
// faster hardcoded implementation.
|
||||
//
|
||||
// Deprecated: This function should not be used and might be removed in the future.
|
||||
// Use [LegacyValidation.IsValidMetricName] instead.
|
||||
func IsValidLegacyMetricName(n string) bool {
|
||||
if len(n) == 0 {
|
||||
return false
|
||||
}
|
||||
for i, b := range n {
|
||||
if !isValidLegacyRune(b, i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return LegacyValidation.IsValidMetricName(n)
|
||||
}
|
||||
|
||||
// EscapeMetricFamily escapes the given metric names and labels with the given
|
||||
@@ -310,13 +435,14 @@ func EscapeName(name string, scheme EscapingScheme) string {
|
||||
case DotsEscaping:
|
||||
// Do not early return for legacy valid names, we still escape underscores.
|
||||
for i, b := range name {
|
||||
if b == '_' {
|
||||
switch {
|
||||
case b == '_':
|
||||
escaped.WriteString("__")
|
||||
} else if b == '.' {
|
||||
case b == '.':
|
||||
escaped.WriteString("_dot_")
|
||||
} else if isValidLegacyRune(b, i) {
|
||||
case isValidLegacyRune(b, i):
|
||||
escaped.WriteRune(b)
|
||||
} else {
|
||||
default:
|
||||
escaped.WriteString("__")
|
||||
}
|
||||
}
|
||||
@@ -327,13 +453,14 @@ func EscapeName(name string, scheme EscapingScheme) string {
|
||||
}
|
||||
escaped.WriteString("U__")
|
||||
for i, b := range name {
|
||||
if b == '_' {
|
||||
switch {
|
||||
case b == '_':
|
||||
escaped.WriteString("__")
|
||||
} else if isValidLegacyRune(b, i) {
|
||||
case isValidLegacyRune(b, i):
|
||||
escaped.WriteRune(b)
|
||||
} else if !utf8.ValidRune(b) {
|
||||
case !utf8.ValidRune(b):
|
||||
escaped.WriteString("_FFFD_")
|
||||
} else {
|
||||
default:
|
||||
escaped.WriteRune('_')
|
||||
escaped.WriteString(strconv.FormatInt(int64(b), 16))
|
||||
escaped.WriteRune('_')
|
||||
@@ -345,7 +472,7 @@ func EscapeName(name string, scheme EscapingScheme) string {
|
||||
}
|
||||
}
|
||||
|
||||
// lower function taken from strconv.atoi
|
||||
// lower function taken from strconv.atoi.
|
||||
func lower(c byte) byte {
|
||||
return c | ('x' - 'X')
|
||||
}
|
||||
@@ -409,11 +536,12 @@ func UnescapeName(name string, scheme EscapingScheme) string {
|
||||
}
|
||||
r := lower(escapedName[i])
|
||||
utf8Val *= 16
|
||||
if r >= '0' && r <= '9' {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
utf8Val += uint(r) - '0'
|
||||
} else if r >= 'a' && r <= 'f' {
|
||||
case r >= 'a' && r <= 'f':
|
||||
utf8Val += uint(r) - 'a' + 10
|
||||
} else {
|
||||
default:
|
||||
return name
|
||||
}
|
||||
i++
|
||||
|
||||
+6
-6
@@ -126,14 +126,14 @@ func (t *Time) UnmarshalJSON(b []byte) error {
|
||||
p := strings.Split(string(b), ".")
|
||||
switch len(p) {
|
||||
case 1:
|
||||
v, err := strconv.ParseInt(string(p[0]), 10, 64)
|
||||
v, err := strconv.ParseInt(p[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = Time(v * second)
|
||||
|
||||
case 2:
|
||||
v, err := strconv.ParseInt(string(p[0]), 10, 64)
|
||||
v, err := strconv.ParseInt(p[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (t *Time) UnmarshalJSON(b []byte) error {
|
||||
if prec < 0 {
|
||||
p[1] = p[1][:dotPrecision]
|
||||
} else if prec > 0 {
|
||||
p[1] = p[1] + strings.Repeat("0", prec)
|
||||
p[1] += strings.Repeat("0", prec)
|
||||
}
|
||||
|
||||
va, err := strconv.ParseInt(p[1], 10, 32)
|
||||
@@ -170,15 +170,15 @@ func (t *Time) UnmarshalJSON(b []byte) error {
|
||||
// This type should not propagate beyond the scope of input/output processing.
|
||||
type Duration time.Duration
|
||||
|
||||
// Set implements pflag/flag.Value
|
||||
// Set implements pflag/flag.Value.
|
||||
func (d *Duration) Set(s string) error {
|
||||
var err error
|
||||
*d, err = ParseDuration(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type implements pflag.Value
|
||||
func (d *Duration) Type() string {
|
||||
// Type implements pflag.Value.
|
||||
func (*Duration) Type() string {
|
||||
return "duration"
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -191,7 +191,8 @@ func (ss SampleStream) String() string {
|
||||
}
|
||||
|
||||
func (ss SampleStream) MarshalJSON() ([]byte, error) {
|
||||
if len(ss.Histograms) > 0 && len(ss.Values) > 0 {
|
||||
switch {
|
||||
case len(ss.Histograms) > 0 && len(ss.Values) > 0:
|
||||
v := struct {
|
||||
Metric Metric `json:"metric"`
|
||||
Values []SamplePair `json:"values"`
|
||||
@@ -202,7 +203,7 @@ func (ss SampleStream) MarshalJSON() ([]byte, error) {
|
||||
Histograms: ss.Histograms,
|
||||
}
|
||||
return json.Marshal(&v)
|
||||
} else if len(ss.Histograms) > 0 {
|
||||
case len(ss.Histograms) > 0:
|
||||
v := struct {
|
||||
Metric Metric `json:"metric"`
|
||||
Histograms []SampleHistogramPair `json:"histograms"`
|
||||
@@ -211,7 +212,7 @@ func (ss SampleStream) MarshalJSON() ([]byte, error) {
|
||||
Histograms: ss.Histograms,
|
||||
}
|
||||
return json.Marshal(&v)
|
||||
} else {
|
||||
default:
|
||||
v := struct {
|
||||
Metric Metric `json:"metric"`
|
||||
Values []SamplePair `json:"values"`
|
||||
@@ -258,7 +259,7 @@ func (s Scalar) String() string {
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (s Scalar) MarshalJSON() ([]byte, error) {
|
||||
v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64)
|
||||
return json.Marshal([...]interface{}{s.Timestamp, string(v)})
|
||||
return json.Marshal([...]interface{}{s.Timestamp, v})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
@@ -349,9 +350,9 @@ func (m Matrix) Len() int { return len(m) }
|
||||
func (m Matrix) Less(i, j int) bool { return m[i].Metric.Before(m[j].Metric) }
|
||||
func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
||||
|
||||
func (mat Matrix) String() string {
|
||||
matCp := make(Matrix, len(mat))
|
||||
copy(matCp, mat)
|
||||
func (m Matrix) String() string {
|
||||
matCp := make(Matrix, len(m))
|
||||
copy(matCp, m)
|
||||
sort.Sort(matCp)
|
||||
|
||||
strs := make([]string, len(matCp))
|
||||
|
||||
+5
-5
@@ -86,22 +86,22 @@ func (s *HistogramBucket) Equal(o *HistogramBucket) bool {
|
||||
return s == o || (s.Boundaries == o.Boundaries && s.Lower == o.Lower && s.Upper == o.Upper && s.Count == o.Count)
|
||||
}
|
||||
|
||||
func (b HistogramBucket) String() string {
|
||||
func (s HistogramBucket) String() string {
|
||||
var sb strings.Builder
|
||||
lowerInclusive := b.Boundaries == 1 || b.Boundaries == 3
|
||||
upperInclusive := b.Boundaries == 0 || b.Boundaries == 3
|
||||
lowerInclusive := s.Boundaries == 1 || s.Boundaries == 3
|
||||
upperInclusive := s.Boundaries == 0 || s.Boundaries == 3
|
||||
if lowerInclusive {
|
||||
sb.WriteRune('[')
|
||||
} else {
|
||||
sb.WriteRune('(')
|
||||
}
|
||||
fmt.Fprintf(&sb, "%g,%g", b.Lower, b.Upper)
|
||||
fmt.Fprintf(&sb, "%g,%g", s.Lower, s.Upper)
|
||||
if upperInclusive {
|
||||
sb.WriteRune(']')
|
||||
} else {
|
||||
sb.WriteRune(')')
|
||||
}
|
||||
fmt.Fprintf(&sb, ":%v", b.Count)
|
||||
fmt.Fprintf(&sb, ":%v", s.Count)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -66,8 +66,8 @@ func (et *ValueType) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ValueType) String() string {
|
||||
switch e {
|
||||
func (et ValueType) String() string {
|
||||
switch et {
|
||||
case ValNone:
|
||||
return "<ValNone>"
|
||||
case ValScalar:
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-yMJ2OVMzrFaDPn7q8nCBZFRYpL/f0RcHzhmw/i6btJM="
|
||||
fi
|
||||
use flake . --impure
|
||||
|
||||
+29
-19
@@ -1,27 +1,37 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
timeout: 10m
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/sagikazarmark/locafero)
|
||||
goimports:
|
||||
local-prefixes: github.com/sagikazarmark/locafero
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-leading-space: false # require machine-readable nolint directives (with no leading space)
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
revive:
|
||||
confidence: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gci
|
||||
- goimports
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- unused
|
||||
|
||||
settings:
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- golines
|
||||
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- localmodule
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
[](https://github.com/sagikazarmark/locafero/actions/workflows/ci.yaml)
|
||||
[](https://pkg.go.dev/mod/github.com/sagikazarmark/locafero)
|
||||

|
||||
[](https://builtwithnix.org)
|
||||

|
||||
[](https://deps.dev/go/github.com%252Fsagikazarmark%252Flocafero)
|
||||
|
||||
**Finder library for [Afero](https://github.com/spf13/afero) ported from [go-finder](https://github.com/sagikazarmark/go-finder).**
|
||||
|
||||
|
||||
+8
-4
@@ -5,19 +5,23 @@ import "io/fs"
|
||||
// FileType represents the kind of entries [Finder] can return.
|
||||
type FileType int
|
||||
|
||||
// FileType represents the kind of entries [Finder] can return.
|
||||
const (
|
||||
FileTypeAll FileType = iota
|
||||
FileTypeAny FileType = iota
|
||||
FileTypeFile
|
||||
FileTypeDir
|
||||
|
||||
// Deprecated: Use [FileTypeAny] instead.
|
||||
FileTypeAll = FileTypeAny
|
||||
)
|
||||
|
||||
func (ft FileType) matchFileInfo(info fs.FileInfo) bool {
|
||||
func (ft FileType) match(info fs.FileInfo) bool {
|
||||
switch ft {
|
||||
case FileTypeAll:
|
||||
case FileTypeAny:
|
||||
return true
|
||||
|
||||
case FileTypeFile:
|
||||
return !info.IsDir()
|
||||
return info.Mode().IsRegular()
|
||||
|
||||
case FileTypeDir:
|
||||
return info.IsDir()
|
||||
|
||||
+55
-49
@@ -1,4 +1,4 @@
|
||||
// Package finder looks for files and directories in an {fs.Fs} filesystem.
|
||||
// Package locafero looks for files and directories in an {fs.Fs} filesystem.
|
||||
package locafero
|
||||
|
||||
import (
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sourcegraph/conc/iter"
|
||||
"github.com/sourcegraph/conc/pool"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -44,65 +44,66 @@ type Finder struct {
|
||||
// Find looks for files and directories in an [afero.Fs] filesystem.
|
||||
func (f Finder) Find(fsys afero.Fs) ([]string, error) {
|
||||
// Arbitrary go routine limit (TODO: make this a parameter)
|
||||
// pool := pool.NewWithResults[[]string]().WithMaxGoroutines(5).WithErrors().WithFirstError()
|
||||
|
||||
type searchItem struct {
|
||||
path string
|
||||
name string
|
||||
}
|
||||
|
||||
var searchItems []searchItem
|
||||
p := pool.NewWithResults[[]searchResult]().WithMaxGoroutines(5).WithErrors().WithFirstError()
|
||||
|
||||
for _, searchPath := range f.Paths {
|
||||
searchPath := searchPath
|
||||
|
||||
for _, searchName := range f.Names {
|
||||
searchName := searchName
|
||||
p.Go(func() ([]searchResult, error) {
|
||||
// If the name contains any glob character, perform a glob match
|
||||
if strings.ContainsAny(searchName, globMatch) {
|
||||
return globWalkSearch(fsys, searchPath, searchName, f.Type)
|
||||
}
|
||||
|
||||
searchItems = append(searchItems, searchItem{searchPath, searchName})
|
||||
|
||||
// pool.Go(func() ([]string, error) {
|
||||
// // If the name contains any glob character, perform a glob match
|
||||
// if strings.ContainsAny(searchName, globMatch) {
|
||||
// return globWalkSearch(fsys, searchPath, searchName, f.Type)
|
||||
// }
|
||||
//
|
||||
// return statSearch(fsys, searchPath, searchName, f.Type)
|
||||
// })
|
||||
return statSearch(fsys, searchPath, searchName, f.Type)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// allResults, err := pool.Wait()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
allResults, err := iter.MapErr(searchItems, func(item *searchItem) ([]string, error) {
|
||||
// If the name contains any glob character, perform a glob match
|
||||
if strings.ContainsAny(item.name, globMatch) {
|
||||
return globWalkSearch(fsys, item.path, item.name, f.Type)
|
||||
}
|
||||
|
||||
return statSearch(fsys, item.path, item.name, f.Type)
|
||||
})
|
||||
searchResults, err := flatten(p.Wait())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
for _, r := range allResults {
|
||||
results = append(results, r...)
|
||||
// Return early if no results were found
|
||||
if len(searchResults) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Sort results in alphabetical order for now
|
||||
// sort.Strings(results)
|
||||
results := make([]string, 0, len(searchResults))
|
||||
|
||||
for _, searchResult := range searchResults {
|
||||
results = append(results, searchResult.path)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||||
var results []string
|
||||
type searchResult struct {
|
||||
path string
|
||||
info fs.FileInfo
|
||||
}
|
||||
|
||||
func flatten[T any](results [][]T, err error) ([]T, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var flattened []T
|
||||
|
||||
for _, r := range results {
|
||||
flattened = append(flattened, r...)
|
||||
}
|
||||
|
||||
return flattened, nil
|
||||
}
|
||||
|
||||
func globWalkSearch(
|
||||
fsys afero.Fs,
|
||||
searchPath string,
|
||||
searchName string,
|
||||
searchType FileType,
|
||||
) ([]searchResult, error) {
|
||||
var results []searchResult
|
||||
|
||||
err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -123,7 +124,7 @@ func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchT
|
||||
}
|
||||
|
||||
// Skip unmatching type
|
||||
if !searchType.matchFileInfo(fileInfo) {
|
||||
if !searchType.match(fileInfo) {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchT
|
||||
}
|
||||
|
||||
if match {
|
||||
results = append(results, p)
|
||||
results = append(results, searchResult{p, fileInfo})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -145,7 +146,12 @@ func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchT
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func statSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||||
func statSearch(
|
||||
fsys afero.Fs,
|
||||
searchPath string,
|
||||
searchName string,
|
||||
searchType FileType,
|
||||
) ([]searchResult, error) {
|
||||
filePath := filepath.Join(searchPath, searchName)
|
||||
|
||||
fileInfo, err := fsys.Stat(filePath)
|
||||
@@ -157,9 +163,9 @@ func statSearch(fsys afero.Fs, searchPath string, searchName string, searchType
|
||||
}
|
||||
|
||||
// Skip unmatching type
|
||||
if !searchType.matchFileInfo(fileInfo) {
|
||||
if !searchType.match(fileInfo) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []string{filePath}, nil
|
||||
return []searchResult{{filePath, fileInfo}}, nil
|
||||
}
|
||||
|
||||
+100
-317
@@ -2,30 +2,32 @@
|
||||
"nodes": {
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_2",
|
||||
"devenv": [
|
||||
"devenv"
|
||||
],
|
||||
"flake-compat": [
|
||||
"devenv"
|
||||
],
|
||||
"git-hooks": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712055811,
|
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||
"lastModified": 1748883665,
|
||||
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "latest",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -33,52 +35,21 @@
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nix": "nix_2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1725907707,
|
||||
"narHash": "sha256-s3pbtzZmVPHzc86WQjK7MGZMNvvw6hWnFMljEkllAfM=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2bbbbc468fc02257265a79652a8350651cca495a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-compat": "flake-compat",
|
||||
"git-hooks": "git-hooks",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708704632,
|
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
||||
"lastModified": 1753981111,
|
||||
"narHash": "sha256-uBJOyMxOkGRmxhD2M5rbN2aV6oP1T2AKq5oBaHHC4mw=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
||||
"rev": "d4d70df706b153b601a87ab8e81c88a0b1a373b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "python-rewrite",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -86,27 +57,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"lastModified": 1747046372,
|
||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,15 +71,37 @@
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733312601,
|
||||
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1725234343,
|
||||
"narHash": "sha256-+ebgonl3NbiKD2UD0x4BszCZQ6sTfL4xioaM49o5B3Y=",
|
||||
"lastModified": 1753121425,
|
||||
"narHash": "sha256-TVcTNvOeWWk1DXljFxVRp+E0tzG1LhrVjOGGoMHuXio=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "567b938d64d4b4112ee253b9274472dc3a346eb6",
|
||||
"rev": "644e0fc48951a860279da645ba77fe4a6e814c5e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -133,39 +110,29 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"lastModified": 1750779888,
|
||||
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -173,7 +140,7 @@
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"pre-commit-hooks",
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
@@ -192,165 +159,49 @@
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": "flake-parts",
|
||||
"git-hooks-nix": [
|
||||
"devenv",
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
||||
"nixpkgs-23-11": [
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs-regression": [
|
||||
"devenv"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"lastModified": 1752773918,
|
||||
"narHash": "sha256-dOi/M6yNeuJlj88exI+7k154z+hAhFcuB8tZktiW7rg=",
|
||||
"owner": "cachix",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"rev": "031c3cf42d2e9391eee373507d8c12e0f9606779",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"owner": "cachix",
|
||||
"ref": "devenv-2.30",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1692808169,
|
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1725233747,
|
||||
"narHash": "sha256-Ss8QWLXdr2JCBPcYChJhz4xJm+h/xjl4G0c0XlP6a74=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_2": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1710695816,
|
||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1713361204,
|
||||
"narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=",
|
||||
"lastModified": 1750441195,
|
||||
"narHash": "sha256-yke+pm+MdgRb6c0dPt8MgDhv7fcBbdjmv1ZceNTyzKg=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6",
|
||||
"rev": "0ceffe312871b443929ff3006960d29b120dc627",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -360,110 +211,42 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1725910328,
|
||||
"narHash": "sha256-n9pCtzGZ0httmTwMuEbi5E78UQ4ZbQMr1pzi5N0LAG8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5775c2583f1801df7b790bf7f7d710a19bac66f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692876271,
|
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
||||
"lastModified": 1751159883,
|
||||
"narHash": "sha256-urW/Ylk9FIfvXfliA1ywh75yszAbiTEVgpPeinFyVZo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "14a40a1d7fb9afa4739275ac642ed7301a9ba1ab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": "flake-utils_2",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1713775815,
|
||||
"narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4",
|
||||
"lastModified": 1753939845,
|
||||
"narHash": "sha256-K2ViRJfdVGE8tpJejs8Qpvvejks1+A4GQej/lBk5y7I=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "94def634a20494ee057c76998843c015909d6311",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+22
-44
@@ -1,64 +1,42 @@
|
||||
{
|
||||
description = "Finder library for Afero";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
outputs =
|
||||
inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devenv.flakeModule
|
||||
];
|
||||
|
||||
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: rec {
|
||||
devenv.shells = {
|
||||
default = {
|
||||
languages = {
|
||||
go.enable = true;
|
||||
go.package = pkgs.lib.mkDefault pkgs.go_1_23;
|
||||
};
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
devenv.shells = {
|
||||
default = {
|
||||
languages = {
|
||||
go.enable = true;
|
||||
go.package = pkgs.lib.mkDefault pkgs.go_1_24;
|
||||
};
|
||||
|
||||
packages = with pkgs; [
|
||||
just
|
||||
packages = with pkgs; [
|
||||
just
|
||||
|
||||
golangci-lint
|
||||
];
|
||||
golangci-lint
|
||||
];
|
||||
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||
containers = pkgs.lib.mkForce { };
|
||||
};
|
||||
|
||||
ci = devenv.shells.default;
|
||||
|
||||
ci_1_21 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_21;
|
||||
};
|
||||
};
|
||||
|
||||
ci_1_22 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_22;
|
||||
};
|
||||
};
|
||||
|
||||
ci_1_23 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_23;
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||
containers = pkgs.lib.mkForce { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+5
-2
@@ -2,10 +2,13 @@ default:
|
||||
just --list
|
||||
|
||||
test:
|
||||
go test -race -v ./...
|
||||
go test -count 10 -shuffle on -race -v ./...
|
||||
|
||||
fuzz:
|
||||
go test -race -v -fuzz=Fuzz -fuzztime=60s ./...
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
fmt:
|
||||
golangci-lint run --fix
|
||||
golangci-lint fmt
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
GO_BIN ?= $(shell go env GOPATH)/bin
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
$(GO_BIN)/golangci-lint:
|
||||
@echo "==> Installing golangci-lint within "${GO_BIN}""
|
||||
@go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GO_BIN)/golangci-lint ## Run linting on Go files
|
||||
@echo "==> Linting Go source files"
|
||||
@golangci-lint run -v --fix -c .golangci.yml ./...
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run tests
|
||||
go test -race -v ./... -coverprofile ./coverage.txt
|
||||
|
||||
.PHONY: bench
|
||||
bench: ## Run benchmarks. See https://pkg.go.dev/cmd/go#hdr-Testing_flags
|
||||
go test ./... -bench . -benchtime 5s -timeout 0 -run=XXX -cpu 1 -benchmem
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
//go:build !go1.20
|
||||
// +build !go1.20
|
||||
|
||||
package multierror
|
||||
|
||||
import "go.uber.org/multierr"
|
||||
|
||||
var (
|
||||
Join = multierr.Combine
|
||||
)
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package multierror
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
Join = errors.Join
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user