update deps
This commit is contained in:
parent
301201c8d2
commit
3ef819de2d
311 changed files with 143330 additions and 69305 deletions
14
.github/workflows/workflow.yml
vendored
14
.github/workflows/workflow.yml
vendored
|
|
@ -18,9 +18,9 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
- uses: extractions/setup-just@v1
|
- uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3
|
||||||
- name: lint
|
- name: lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
- name: test
|
- name: test
|
||||||
|
|
@ -31,16 +31,16 @@ jobs:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
|
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
|
||||||
type=semver,pattern={{raw}}
|
type=semver,pattern={{raw}}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
|
|
@ -66,7 +66,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean
|
args: release --clean
|
||||||
|
|
|
||||||
168
.golangci.yml
168
.golangci.yml
|
|
@ -1,19 +1,52 @@
|
||||||
|
version: "2"
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
timeout: 30m
|
modules-download-mode: readonly
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: true
|
tests: true
|
||||||
build-tags: []
|
|
||||||
modules-download-mode: readonly
|
|
||||||
|
|
||||||
output:
|
output:
|
||||||
formats:
|
formats:
|
||||||
- format: colored-line-number
|
text:
|
||||||
print-issued-lines: true
|
path: stdout
|
||||||
print-linter-name: true
|
print-linter-name: true
|
||||||
sort-results: true
|
print-issued-lines: true
|
||||||
|
linters:
|
||||||
linters-settings:
|
enable:
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- forcetypeassert
|
||||||
|
- gocognit
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
|
- gosmopolitan
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- nestif
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- revive
|
||||||
|
- sqlclosecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- wastedassign
|
||||||
|
settings:
|
||||||
decorder:
|
decorder:
|
||||||
dec-order:
|
dec-order:
|
||||||
- const
|
- const
|
||||||
|
|
@ -34,28 +67,21 @@ linters-settings:
|
||||||
default-signifies-exhaustive: true
|
default-signifies-exhaustive: true
|
||||||
gocognit:
|
gocognit:
|
||||||
min-complexity: 15
|
min-complexity: 15
|
||||||
nestif:
|
|
||||||
min-complexity: 5
|
|
||||||
gocritic:
|
gocritic:
|
||||||
enabled-tags:
|
enabled-tags:
|
||||||
- diagnostic
|
- diagnostic
|
||||||
- style
|
- style
|
||||||
- performance
|
- performance
|
||||||
gofmt:
|
|
||||||
simplify: true
|
|
||||||
rewrite-rules:
|
|
||||||
- pattern: 'interface{}'
|
|
||||||
replacement: 'any'
|
|
||||||
- pattern: 'a[b:len(a)]'
|
|
||||||
replacement: 'a[b:]'
|
|
||||||
gofumpt:
|
|
||||||
extra-rules: true
|
|
||||||
grouper:
|
grouper:
|
||||||
const-require-single-const: true
|
const-require-single-const: true
|
||||||
import-require-single-import: true
|
import-require-single-import: true
|
||||||
var-require-single-var: true
|
var-require-single-var: true
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
nestif:
|
||||||
|
min-complexity: 5
|
||||||
|
unparam:
|
||||||
|
check-exported: true
|
||||||
usestdlibvars:
|
usestdlibvars:
|
||||||
time-month: true
|
time-month: true
|
||||||
time-layout: true
|
time-layout: true
|
||||||
|
|
@ -64,78 +90,58 @@ linters-settings:
|
||||||
sql-isolation-level: true
|
sql-isolation-level: true
|
||||||
tls-signature-scheme: true
|
tls-signature-scheme: true
|
||||||
constant-kind: true
|
constant-kind: true
|
||||||
unparam:
|
exclusions:
|
||||||
check-exported: true
|
generated: lax
|
||||||
linters:
|
presets:
|
||||||
disable-all: false
|
- comments
|
||||||
enable:
|
- common-false-positives
|
||||||
- asasalint
|
- legacy
|
||||||
- asciicheck
|
- std-error-handling
|
||||||
- bidichk
|
rules:
|
||||||
- bodyclose
|
- linters:
|
||||||
- containedctx
|
|
||||||
- copyloopvar
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
- dupl
|
||||||
- dupword
|
|
||||||
- durationcheck
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
|
||||||
- errname
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- forcetypeassert
|
|
||||||
- gocognit
|
- gocognit
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- nestif
|
|
||||||
- nolintlint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- revive
|
|
||||||
- sqlclosecheck
|
|
||||||
- staticcheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- wastedassign
|
|
||||||
fast: false
|
|
||||||
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-dirs-use-default: true
|
|
||||||
exclude-dirs:
|
|
||||||
- mocks
|
|
||||||
exclude-rules:
|
|
||||||
- path: _test\.go
|
|
||||||
linters:
|
|
||||||
- gocyclo
|
|
||||||
- gocognit
|
|
||||||
- errcheck
|
|
||||||
- dupl
|
|
||||||
- gosec
|
- gosec
|
||||||
|
path: _test\.go
|
||||||
- linters:
|
- linters:
|
||||||
- staticcheck
|
- staticcheck
|
||||||
text: "SA9003:"
|
text: 'SA9003:'
|
||||||
- linters:
|
- linters:
|
||||||
- lll
|
- lll
|
||||||
source: "^//go:generate "
|
source: '^//go:generate '
|
||||||
- linters:
|
- linters:
|
||||||
- revive
|
- revive
|
||||||
text: "returns unexported type"
|
text: returns unexported type
|
||||||
|
paths:
|
||||||
|
- mocks
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
issues:
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
new: false
|
new: false
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
rewrite-rules:
|
||||||
|
- pattern: interface{}
|
||||||
|
replacement: any
|
||||||
|
- pattern: a[b:len(a)]
|
||||||
|
replacement: a[b:]
|
||||||
|
gofumpt:
|
||||||
|
extra-rules: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- mocks
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
|
||||||
47
go.mod
47
go.mod
|
|
@ -1,26 +1,24 @@
|
||||||
module github.com/etkecc/postmoogle
|
module github.com/etkecc/postmoogle
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24.3
|
||||||
|
|
||||||
toolchain go1.23.5
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/archdx/zerolog-sentry v1.8.5
|
github.com/archdx/zerolog-sentry v1.8.5
|
||||||
github.com/emersion/go-msgauth v0.7.0
|
github.com/emersion/go-msgauth v0.7.0
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
|
||||||
github.com/emersion/go-smtp v0.21.3
|
github.com/emersion/go-smtp v0.22.0
|
||||||
github.com/etkecc/go-env v1.2.1
|
github.com/etkecc/go-env v1.2.1
|
||||||
github.com/etkecc/go-fswatcher v1.0.1
|
github.com/etkecc/go-fswatcher v1.0.1
|
||||||
github.com/etkecc/go-healthchecks/v2 v2.2.2
|
github.com/etkecc/go-healthchecks/v2 v2.2.2
|
||||||
github.com/etkecc/go-kit v1.6.0
|
github.com/etkecc/go-kit v1.7.0
|
||||||
github.com/etkecc/go-linkpearl v0.0.0-20250416104026-a65bda9f5f37
|
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39
|
||||||
github.com/etkecc/go-mxidwc v1.0.1
|
github.com/etkecc/go-mxidwc v1.0.1
|
||||||
github.com/etkecc/go-secgen v1.4.0
|
github.com/etkecc/go-secgen v1.4.0
|
||||||
github.com/etkecc/go-validator/v2 v2.2.4
|
github.com/etkecc/go-validator/v2 v2.2.4
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9
|
github.com/gabriel-vasile/mimetype v1.4.9
|
||||||
github.com/getsentry/sentry-go v0.32.0
|
github.com/getsentry/sentry-go v0.34.0
|
||||||
github.com/jhillyerd/enmime v1.3.0
|
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||||
github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e
|
github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/mcnijman/go-emailaddress v1.1.1
|
github.com/mcnijman/go-emailaddress v1.1.1
|
||||||
|
|
@ -28,9 +26,9 @@ require (
|
||||||
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/swaggo/swag v1.16.3
|
github.com/swaggo/swag v1.16.3
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||||
maunium.net/go/mautrix v0.23.3
|
maunium.net/go/mautrix v0.24.1
|
||||||
modernc.org/sqlite v1.37.0
|
modernc.org/sqlite v1.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -43,6 +41,7 @@ require (
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/etkecc/go-trysmtp v1.1.5 // indirect
|
github.com/etkecc/go-trysmtp v1.1.5 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||||
github.com/go-openapi/spec v0.20.4 // indirect
|
github.com/go-openapi/spec v0.20.4 // indirect
|
||||||
|
|
@ -50,7 +49,7 @@ require (
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
|
github.com/inbucket/html2text v1.0.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
|
@ -59,8 +58,10 @@ require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/errors v1.1.0 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect
|
github.com/olekukonko/ll v0.0.9 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||||
|
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
|
@ -69,15 +70,15 @@ require (
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/yuin/goldmark v1.7.10 // indirect
|
github.com/yuin/goldmark v1.7.12 // indirect
|
||||||
go.mau.fi/util v0.8.6 // indirect
|
go.mau.fi/util v0.8.8 // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
golang.org/x/tools v0.32.0 // indirect
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
modernc.org/libc v1.63.0 // indirect
|
modernc.org/libc v1.66.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.10.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
118
go.sum
118
go.sum
|
|
@ -25,21 +25,20 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=
|
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=
|
||||||
github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck=
|
github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
|
github.com/emersion/go-smtp v0.22.0 h1:/d3HWxkZZ4riB+0kzfoODh9X+xyCrLEezMnAAa1LEMU=
|
||||||
github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/emersion/go-smtp v0.22.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||||
github.com/etkecc/go-env v1.2.1 h1:b/mIa8D1d9hc3rI8h5bEtBHsnKkBZ6UbmYPog3QIPTU=
|
github.com/etkecc/go-env v1.2.1 h1:b/mIa8D1d9hc3rI8h5bEtBHsnKkBZ6UbmYPog3QIPTU=
|
||||||
github.com/etkecc/go-env v1.2.1/go.mod h1:yTs1DWEsllAZYA417r4V+OmMuYinGXtBzKkLGNvCv5c=
|
github.com/etkecc/go-env v1.2.1/go.mod h1:yTs1DWEsllAZYA417r4V+OmMuYinGXtBzKkLGNvCv5c=
|
||||||
github.com/etkecc/go-fswatcher v1.0.1 h1:n9hqtjzTS3ETb9hcZe1EqYA1lkkhlzZlztu3hXwFDQc=
|
github.com/etkecc/go-fswatcher v1.0.1 h1:n9hqtjzTS3ETb9hcZe1EqYA1lkkhlzZlztu3hXwFDQc=
|
||||||
github.com/etkecc/go-fswatcher v1.0.1/go.mod h1:O5TODJ9z6Qb7X+snqHbB+F0Pah6G497Wdg0SFE/UYpE=
|
github.com/etkecc/go-fswatcher v1.0.1/go.mod h1:O5TODJ9z6Qb7X+snqHbB+F0Pah6G497Wdg0SFE/UYpE=
|
||||||
github.com/etkecc/go-healthchecks/v2 v2.2.2 h1:YV7e+Ga8JY3aZX4Qf6Q1Ca+DnqbT5Drjli3PufB4J1g=
|
github.com/etkecc/go-healthchecks/v2 v2.2.2 h1:YV7e+Ga8JY3aZX4Qf6Q1Ca+DnqbT5Drjli3PufB4J1g=
|
||||||
github.com/etkecc/go-healthchecks/v2 v2.2.2/go.mod h1:IowWGN4F6By6z0eh63+639OscylgtdvT9ITjfN6hnZA=
|
github.com/etkecc/go-healthchecks/v2 v2.2.2/go.mod h1:IowWGN4F6By6z0eh63+639OscylgtdvT9ITjfN6hnZA=
|
||||||
github.com/etkecc/go-kit v1.6.0 h1:pNbIONvOuRaWuP0F4JLq2ASBxnsCEq5CFY3KeKdhaVY=
|
github.com/etkecc/go-kit v1.7.0 h1:jxJ+tWfWwYqgNgfdbZ2va/M0AHyDdKwc8lYGXrFuWsc=
|
||||||
github.com/etkecc/go-kit v1.6.0/go.mod h1:yikghi8YaYbTjRXNtx82g0LFv90YqZi2vLf5Chw0ysg=
|
github.com/etkecc/go-kit v1.7.0/go.mod h1:yikghi8YaYbTjRXNtx82g0LFv90YqZi2vLf5Chw0ysg=
|
||||||
github.com/etkecc/go-linkpearl v0.0.0-20250416104026-a65bda9f5f37 h1:ZWUy35kMjyTXTU7cwHyyV1ttrOIBZlg/+z8zmbevj98=
|
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39 h1:wZ+Iv47qoBy3jzzVgzS52KkQWcddUuDsO5s59J2q+1g=
|
||||||
github.com/etkecc/go-linkpearl v0.0.0-20250416104026-a65bda9f5f37/go.mod h1:iXvKPwSFgTG9DyEMN9UMuUNeZrZFYlGoBsZZNbiTMdQ=
|
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39/go.mod h1:v4QqBXgDZ4Hz235SqyaHde1FL+dNpdnjX4tTR4kxcaI=
|
||||||
github.com/etkecc/go-mxidwc v1.0.1 h1:t4Kq3FxSlQjt1i7RpzE5q3cOWjJ0vrTzzGZRSpgh8mg=
|
github.com/etkecc/go-mxidwc v1.0.1 h1:t4Kq3FxSlQjt1i7RpzE5q3cOWjJ0vrTzzGZRSpgh8mg=
|
||||||
github.com/etkecc/go-mxidwc v1.0.1/go.mod h1:WFlntcH4mdual/gNi6X7a6rSJERNuLjdrwM3K/tucQA=
|
github.com/etkecc/go-mxidwc v1.0.1/go.mod h1:WFlntcH4mdual/gNi6X7a6rSJERNuLjdrwM3K/tucQA=
|
||||||
github.com/etkecc/go-secgen v1.4.0 h1:PapLpen3aIqG2LDu+U6KWDb4SMW2UGuvetpJjd86yUc=
|
github.com/etkecc/go-secgen v1.4.0 h1:PapLpen3aIqG2LDu+U6KWDb4SMW2UGuvetpJjd86yUc=
|
||||||
|
|
@ -48,12 +47,14 @@ github.com/etkecc/go-trysmtp v1.1.5 h1:MQ4l4bE9nWUqRKoJOalGHGLkb/hRmzbcBMlClufEO
|
||||||
github.com/etkecc/go-trysmtp v1.1.5/go.mod h1:J4zQu+kgM37xQY148wUz0Dm6y+YhtzMghAv/AaXt0sQ=
|
github.com/etkecc/go-trysmtp v1.1.5/go.mod h1:J4zQu+kgM37xQY148wUz0Dm6y+YhtzMghAv/AaXt0sQ=
|
||||||
github.com/etkecc/go-validator/v2 v2.2.4 h1:E4sE4FpYPVP6VRvnndni+8qUXbeSuHxkniFsi6uCRFQ=
|
github.com/etkecc/go-validator/v2 v2.2.4 h1:E4sE4FpYPVP6VRvnndni+8qUXbeSuHxkniFsi6uCRFQ=
|
||||||
github.com/etkecc/go-validator/v2 v2.2.4/go.mod h1:5VX2VICgMALoFBiGc02p/6RjQ8ifRxZ0KOuLAH5fTe0=
|
github.com/etkecc/go-validator/v2 v2.2.4/go.mod h1:5VX2VICgMALoFBiGc02p/6RjQ8ifRxZ0KOuLAH5fTe0=
|
||||||
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
|
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
|
||||||
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
|
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
|
@ -66,8 +67,8 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
|
|
@ -79,10 +80,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
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/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
|
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||||
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
|
github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8=
|
||||||
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
|
github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
|
@ -108,7 +109,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||||
|
|
@ -122,10 +122,14 @@ github.com/mileusna/crontab v1.2.0/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||||
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q=
|
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||||
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||||
|
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||||
|
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||||
|
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4=
|
||||||
|
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
|
@ -163,41 +167,41 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
|
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
|
||||||
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54=
|
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
||||||
go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE=
|
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
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/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
@ -210,28 +214,30 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mautrix v0.23.3 h1:U+fzdcLhFKLUm5gf2+Q0hEUqWkwDMRfvE+paUH9ogSk=
|
maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU=
|
||||||
maunium.net/go/mautrix v0.23.3/go.mod h1:LX+3evXVKSvh/b43BVC3rkvN2qV7b0bkIV4fY7Snn/4=
|
maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI=
|
||||||
modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA=
|
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||||
modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc=
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY=
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.63.0 h1:wKzb61wOGCzgahQBORb1b0dZonh8Ufzl/7r4Yf1D5YA=
|
modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI=
|
||||||
modernc.org/libc v1.63.0/go.mod h1:wDzH1mgz1wUIEwottFt++POjGRO9sgyQKrpXaz3x89E=
|
modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.66.0 h1:eoFuDb1ozurUY5WSWlgvxHp0FuL+AncMwNjFqGYMJPQ=
|
||||||
|
modernc.org/libc v1.66.0/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
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 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/emersion/go-msgauth/dkim"
|
"github.com/emersion/go-msgauth/dkim"
|
||||||
"github.com/etkecc/go-linkpearl"
|
"github.com/etkecc/go-linkpearl"
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime/v2"
|
||||||
"github.com/kvannotten/mailstrip"
|
"github.com/kvannotten/mailstrip"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/etkecc/go-validator/v2"
|
"github.com/etkecc/go-validator/v2"
|
||||||
"github.com/etkecc/postmoogle/internal/email"
|
"github.com/etkecc/postmoogle/internal/email"
|
||||||
"github.com/etkecc/postmoogle/internal/utils"
|
"github.com/etkecc/postmoogle/internal/utils"
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime/v2"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
1
vendor/github.com/emersion/go-smtp/README.md
generated
vendored
1
vendor/github.com/emersion/go-smtp/README.md
generated
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
# go-smtp
|
# go-smtp
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/emersion/go-smtp)
|
[](https://pkg.go.dev/github.com/emersion/go-smtp)
|
||||||
[](https://builds.sr.ht/~emersion/go-smtp/commits?)
|
|
||||||
|
|
||||||
An ESMTP client and server library written in Go.
|
An ESMTP client and server library written in Go.
|
||||||
|
|
||||||
|
|
|
||||||
188
vendor/github.com/emersion/go-smtp/client.go
generated
vendored
188
vendor/github.com/emersion/go-smtp/client.go
generated
vendored
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -81,7 +82,7 @@ func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialStartTLS retruns a new Client connected to an SMTP server via STARTTLS
|
// DialStartTLS returns a new Client connected to an SMTP server via STARTTLS
|
||||||
// at addr. The addr must include a port, as in "mail.example.com:smtp".
|
// at addr. The addr must include a port, as in "mail.example.com:smtp".
|
||||||
//
|
//
|
||||||
// A nil tlsConfig is equivalent to a zero tls.Config.
|
// A nil tlsConfig is equivalent to a zero tls.Config.
|
||||||
|
|
@ -530,6 +531,9 @@ func (c *Client) Rcpt(to string, opts *RcptOptions) error {
|
||||||
fmt.Fprintf(&sb, " ORCPT=%s;%s", string(opts.OriginalRecipientType), enc)
|
fmt.Fprintf(&sb, " ORCPT=%s;%s", string(opts.OriginalRecipientType), enc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := c.ext["RRVS"]; ok && opts != nil && !opts.RequireRecipientValidSince.IsZero() {
|
||||||
|
sb.WriteString(fmt.Sprintf(" RRVS=%s", opts.RequireRecipientValidSince.Format(time.RFC3339)))
|
||||||
|
}
|
||||||
if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
|
if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -537,85 +541,152 @@ func (c *Client) Rcpt(to string, opts *RcptOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type dataCloser struct {
|
// DataCommand is a pending DATA command. DataCommand is an io.WriteCloser.
|
||||||
c *Client
|
// See Client.Data.
|
||||||
io.WriteCloser
|
type DataCommand struct {
|
||||||
statusCb func(rcpt string, status *SMTPError)
|
client *Client
|
||||||
closed bool
|
wc io.WriteCloser
|
||||||
|
|
||||||
|
closeErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataCloser) Close() error {
|
var _ io.WriteCloser = (*DataCommand)(nil)
|
||||||
if d.closed {
|
|
||||||
return fmt.Errorf("smtp: data writer closed twice")
|
// Write implements io.Writer.
|
||||||
|
func (cmd *DataCommand) Write(b []byte) (int, error) {
|
||||||
|
return cmd.wc.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.WriteCloser.Close(); err != nil {
|
// Close implements io.Closer.
|
||||||
|
func (cmd *DataCommand) Close() error {
|
||||||
|
var err error
|
||||||
|
if cmd.client.lmtp {
|
||||||
|
_, err = cmd.CloseWithLMTPResponse()
|
||||||
|
} else {
|
||||||
|
_, err = cmd.CloseWithResponse()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.c.conn.SetDeadline(time.Now().Add(d.c.SubmissionTimeout))
|
// CloseWithResponse is equivalent to Close, but also returns the server
|
||||||
defer d.c.conn.SetDeadline(time.Time{})
|
// response. It cannot be called when the LMTP protocol is used.
|
||||||
|
//
|
||||||
|
// If server returns an error, it will be of type *SMTPError.
|
||||||
|
func (cmd *DataCommand) CloseWithResponse() (*DataResponse, error) {
|
||||||
|
if cmd.client.lmtp {
|
||||||
|
return nil, errors.New("smtp: CloseWithResponse used with an LMTP client")
|
||||||
|
}
|
||||||
|
|
||||||
expectedResponses := len(d.c.rcpts)
|
if err := cmd.close(); err != nil {
|
||||||
if d.c.lmtp {
|
return nil, err
|
||||||
for expectedResponses > 0 {
|
|
||||||
rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses]
|
|
||||||
if _, _, err := d.c.readResponse(250); err != nil {
|
|
||||||
if smtpErr, ok := err.(*SMTPError); ok {
|
|
||||||
if d.statusCb != nil {
|
|
||||||
d.statusCb(rcpt, smtpErr)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return err
|
cmd.client.conn.SetDeadline(time.Now().Add(cmd.client.SubmissionTimeout))
|
||||||
}
|
defer cmd.client.conn.SetDeadline(time.Time{})
|
||||||
} else if d.statusCb != nil {
|
|
||||||
d.statusCb(rcpt, nil)
|
_, msg, err := cmd.client.readResponse(250)
|
||||||
}
|
|
||||||
expectedResponses--
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, _, err := d.c.readResponse(250)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
cmd.closeErr = err
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DataResponse{StatusText: msg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithLMTPResponse is equivalent to Close, but also returns per-recipient
|
||||||
|
// server responses. It can only be called when the LMTP protocol is used.
|
||||||
|
//
|
||||||
|
// If server returns an error, it will be of type LMTPDataError.
|
||||||
|
func (cmd *DataCommand) CloseWithLMTPResponse() (map[string]*DataResponse, error) {
|
||||||
|
if !cmd.client.lmtp {
|
||||||
|
return nil, errors.New("smtp: CloseWithLMTPResponse used without an LMTP client")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.client.conn.SetDeadline(time.Now().Add(cmd.client.SubmissionTimeout))
|
||||||
|
defer cmd.client.conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
resp := make(map[string]*DataResponse, len(cmd.client.rcpts))
|
||||||
|
lmtpErr := make(LMTPDataError, len(cmd.client.rcpts))
|
||||||
|
for i := 0; i < len(cmd.client.rcpts); i++ {
|
||||||
|
rcpt := cmd.client.rcpts[i]
|
||||||
|
_, msg, err := cmd.client.readResponse(250)
|
||||||
|
if err != nil {
|
||||||
|
if smtpErr, ok := err.(*SMTPError); ok {
|
||||||
|
lmtpErr[rcpt] = smtpErr
|
||||||
|
} else {
|
||||||
|
if len(lmtpErr) > 0 {
|
||||||
|
return resp, errors.Join(err, lmtpErr)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp[rcpt] = &DataResponse{StatusText: msg}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.closed = true
|
if len(lmtpErr) > 0 {
|
||||||
|
return resp, lmtpErr
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *DataCommand) close() error {
|
||||||
|
if cmd.closeErr != nil {
|
||||||
|
return cmd.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.wc.Close(); err != nil {
|
||||||
|
cmd.closeErr = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.closeErr = errors.New("smtp: data writer closed twice")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataResponse is the response returned by a DATA command. See
|
||||||
|
// DataCommand.CloseWithResponse.
|
||||||
|
type DataResponse struct {
|
||||||
|
// StatusText is the status text returned by the server. It may contain
|
||||||
|
// tracking information.
|
||||||
|
StatusText string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LMTPDataError is a collection of errors returned by an LMTP server for a
|
||||||
|
// DATA command. It holds per-recipient errors.
|
||||||
|
type LMTPDataError map[string]*SMTPError
|
||||||
|
|
||||||
|
// Error implements error.
|
||||||
|
func (lmtpErr LMTPDataError) Error() string {
|
||||||
|
return errors.Join(lmtpErr.Unwrap()...).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns all per-recipient errors returned by the server.
|
||||||
|
func (lmtpErr LMTPDataError) Unwrap() []error {
|
||||||
|
l := make([]error, 0, len(lmtpErr))
|
||||||
|
for rcpt, smtpErr := range lmtpErr {
|
||||||
|
l = append(l, fmt.Errorf("<%v>: %w", rcpt, smtpErr))
|
||||||
|
}
|
||||||
|
sort.Slice(l, func(i, j int) bool {
|
||||||
|
return l[i].Error() < l[j].Error()
|
||||||
|
})
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
// Data issues a DATA command to the server and returns a writer that
|
// Data issues a DATA command to the server and returns a writer that
|
||||||
// can be used to write the mail headers and body. The caller should
|
// can be used to write the mail headers and body. The caller should
|
||||||
// close the writer before calling any more methods on c. A call to
|
// close the writer before calling any more methods on c. A call to
|
||||||
// Data must be preceded by one or more calls to Rcpt.
|
// Data must be preceded by one or more calls to Rcpt.
|
||||||
//
|
func (c *Client) Data() (*DataCommand, error) {
|
||||||
// If server returns an error, it will be of type *SMTPError.
|
|
||||||
func (c *Client) Data() (io.WriteCloser, error) {
|
|
||||||
_, _, err := c.cmd(354, "DATA")
|
_, _, err := c.cmd(354, "DATA")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &dataCloser{c: c, WriteCloser: c.text.DotWriter()}, nil
|
return &DataCommand{client: c, wc: c.text.DotWriter()}, nil
|
||||||
}
|
|
||||||
|
|
||||||
// LMTPData is the LMTP-specific version of the Data method. It accepts a callback
|
|
||||||
// that will be called for each status response received from the server.
|
|
||||||
//
|
|
||||||
// Status callback will receive a SMTPError argument for each negative server
|
|
||||||
// reply and nil for each positive reply. I/O errors will not be reported using
|
|
||||||
// callback and instead will be returned by the Close method of io.WriteCloser.
|
|
||||||
// Callback will be called for each successfull Rcpt call done before in the
|
|
||||||
// same order.
|
|
||||||
func (c *Client) LMTPData(statusCb func(rcpt string, status *SMTPError)) (io.WriteCloser, error) {
|
|
||||||
if !c.lmtp {
|
|
||||||
return nil, errors.New("smtp: not a LMTP client")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := c.cmd(354, "DATA")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &dataCloser{c: c, WriteCloser: c.text.DotWriter(), statusCb: statusCb}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMail will use an existing connection to send an email from
|
// SendMail will use an existing connection to send an email from
|
||||||
|
|
@ -784,6 +855,11 @@ func (c *Client) Reset() error {
|
||||||
if _, _, err := c.cmd(250, "RSET"); err != nil {
|
if _, _, err := c.cmd(250, "RSET"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allow custom HELLO again
|
||||||
|
c.didHello = false
|
||||||
|
c.helloError = nil
|
||||||
|
|
||||||
c.rcpts = nil
|
c.rcpts = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
vendor/github.com/emersion/go-smtp/conn.go
generated
vendored
32
vendor/github.com/emersion/go-smtp/conn.go
generated
vendored
|
|
@ -229,6 +229,12 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
|
||||||
// NewSession can access it via Conn.Hostname.
|
// NewSession can access it via Conn.Hostname.
|
||||||
c.helo = domain
|
c.helo = domain
|
||||||
|
|
||||||
|
// RFC 5321: "An EHLO command MAY be issued by a client later in the session"
|
||||||
|
if c.session != nil {
|
||||||
|
// RFC 5321: "... the SMTP server MUST clear all buffers
|
||||||
|
// and reset the state exactly as if a RSET command has been issued."
|
||||||
|
c.reset()
|
||||||
|
} else {
|
||||||
sess, err := c.server.Backend.NewSession(c)
|
sess, err := c.server.Backend.NewSession(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.helo = ""
|
c.helo = ""
|
||||||
|
|
@ -237,6 +243,7 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setSession(sess)
|
c.setSession(sess)
|
||||||
|
}
|
||||||
|
|
||||||
if !enhanced {
|
if !enhanced {
|
||||||
c.writeResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
|
c.writeResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
|
||||||
|
|
@ -284,6 +291,9 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
|
||||||
if c.server.MaxRecipients > 0 {
|
if c.server.MaxRecipients > 0 {
|
||||||
caps = append(caps, fmt.Sprintf("LIMITS RCPTMAX=%v", c.server.MaxRecipients))
|
caps = append(caps, fmt.Sprintf("LIMITS RCPTMAX=%v", c.server.MaxRecipients))
|
||||||
}
|
}
|
||||||
|
if c.server.EnableRRVS {
|
||||||
|
caps = append(caps, "RRVS")
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{"Hello " + domain}
|
args := []string{"Hello " + domain}
|
||||||
args = append(args, caps...)
|
args = append(args, caps...)
|
||||||
|
|
@ -709,6 +719,18 @@ func (c *Conn) handleRcpt(arg string) {
|
||||||
}
|
}
|
||||||
opts.OriginalRecipientType = aType
|
opts.OriginalRecipientType = aType
|
||||||
opts.OriginalRecipient = aAddr
|
opts.OriginalRecipient = aAddr
|
||||||
|
case "RRVS":
|
||||||
|
if !c.server.EnableRRVS {
|
||||||
|
c.writeResponse(504, EnhancedCode{5, 5, 4}, "RRVS is not implemented")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value, _, _ = strings.Cut(value, ";") // discard the no-support action
|
||||||
|
rrvsTime, err := time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
c.writeResponse(501, EnhancedCode{5, 5, 4}, "Malformed RRVS parameter value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.RequireRecipientValidSince = rrvsTime
|
||||||
default:
|
default:
|
||||||
c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown RCPT TO argument")
|
c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown RCPT TO argument")
|
||||||
return
|
return
|
||||||
|
|
@ -1229,13 +1251,17 @@ func (c *Conn) writeResponse(code int, enhCode EnhancedCode, text ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(text)-1; i++ {
|
// transform each single line with \n, into separate lines
|
||||||
|
text = strings.Split(strings.Join(text, "\n"), "\n")
|
||||||
|
|
||||||
|
lastLineIndex := len(text) - 1
|
||||||
|
for i := 0; i < lastLineIndex; i++ {
|
||||||
c.text.PrintfLine("%d-%v", code, text[i])
|
c.text.PrintfLine("%d-%v", code, text[i])
|
||||||
}
|
}
|
||||||
if enhCode == NoEnhancedCode {
|
if enhCode == NoEnhancedCode {
|
||||||
c.text.PrintfLine("%d %v", code, text[len(text)-1])
|
c.text.PrintfLine("%d %v", code, text[lastLineIndex])
|
||||||
} else {
|
} else {
|
||||||
c.text.PrintfLine("%d %v.%v.%v %v", code, enhCode[0], enhCode[1], enhCode[2], text[len(text)-1])
|
c.text.PrintfLine("%d %v.%v.%v %v", code, enhCode[0], enhCode[1], enhCode[2], text[lastLineIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4
vendor/github.com/emersion/go-smtp/server.go
generated
vendored
4
vendor/github.com/emersion/go-smtp/server.go
generated
vendored
|
|
@ -57,6 +57,10 @@ type Server struct {
|
||||||
// Should be used only if backend supports it.
|
// Should be used only if backend supports it.
|
||||||
EnableDSN bool
|
EnableDSN bool
|
||||||
|
|
||||||
|
// Advertise RRVS (RFC 7293) capability.
|
||||||
|
// Should be used only if backend supports it.
|
||||||
|
EnableRRVS bool
|
||||||
|
|
||||||
// The server backend.
|
// The server backend.
|
||||||
Backend Backend
|
Backend Backend
|
||||||
|
|
||||||
|
|
|
||||||
6
vendor/github.com/emersion/go-smtp/smtp.go
generated
vendored
6
vendor/github.com/emersion/go-smtp/smtp.go
generated
vendored
|
|
@ -17,6 +17,8 @@
|
||||||
// Additional extensions may be handled by other packages.
|
// Additional extensions may be handled by other packages.
|
||||||
package smtp
|
package smtp
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type BodyType string
|
type BodyType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -91,4 +93,8 @@ type RcptOptions struct {
|
||||||
// Original recipient set by client.
|
// Original recipient set by client.
|
||||||
OriginalRecipientType DSNAddressType
|
OriginalRecipientType DSNAddressType
|
||||||
OriginalRecipient string
|
OriginalRecipient string
|
||||||
|
|
||||||
|
// Time value of the RRVS= argument
|
||||||
|
// Left as the zero time if unset.
|
||||||
|
RequireRecipientValidSince time.Time
|
||||||
}
|
}
|
||||||
|
|
|
||||||
168
vendor/github.com/etkecc/go-kit/.golangci.yml
generated
vendored
168
vendor/github.com/etkecc/go-kit/.golangci.yml
generated
vendored
|
|
@ -1,19 +1,52 @@
|
||||||
|
version: "2"
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
timeout: 30m
|
modules-download-mode: readonly
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: true
|
tests: true
|
||||||
build-tags: []
|
|
||||||
modules-download-mode: readonly
|
|
||||||
|
|
||||||
output:
|
output:
|
||||||
formats:
|
formats:
|
||||||
- format: colored-line-number
|
text:
|
||||||
print-issued-lines: true
|
path: stdout
|
||||||
print-linter-name: true
|
print-linter-name: true
|
||||||
sort-results: true
|
print-issued-lines: true
|
||||||
|
linters:
|
||||||
linters-settings:
|
enable:
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- forcetypeassert
|
||||||
|
- gocognit
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
|
- gosmopolitan
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- nestif
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- revive
|
||||||
|
- sqlclosecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- wastedassign
|
||||||
|
settings:
|
||||||
decorder:
|
decorder:
|
||||||
dec-order:
|
dec-order:
|
||||||
- const
|
- const
|
||||||
|
|
@ -34,28 +67,21 @@ linters-settings:
|
||||||
default-signifies-exhaustive: true
|
default-signifies-exhaustive: true
|
||||||
gocognit:
|
gocognit:
|
||||||
min-complexity: 15
|
min-complexity: 15
|
||||||
nestif:
|
|
||||||
min-complexity: 5
|
|
||||||
gocritic:
|
gocritic:
|
||||||
enabled-tags:
|
enabled-tags:
|
||||||
- diagnostic
|
- diagnostic
|
||||||
- style
|
- style
|
||||||
- performance
|
- performance
|
||||||
gofmt:
|
|
||||||
simplify: true
|
|
||||||
rewrite-rules:
|
|
||||||
- pattern: 'interface{}'
|
|
||||||
replacement: 'any'
|
|
||||||
- pattern: 'a[b:len(a)]'
|
|
||||||
replacement: 'a[b:]'
|
|
||||||
gofumpt:
|
|
||||||
extra-rules: true
|
|
||||||
grouper:
|
grouper:
|
||||||
const-require-single-const: true
|
const-require-single-const: true
|
||||||
import-require-single-import: true
|
import-require-single-import: true
|
||||||
var-require-single-var: true
|
var-require-single-var: true
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
nestif:
|
||||||
|
min-complexity: 5
|
||||||
|
unparam:
|
||||||
|
check-exported: true
|
||||||
usestdlibvars:
|
usestdlibvars:
|
||||||
time-month: true
|
time-month: true
|
||||||
time-layout: true
|
time-layout: true
|
||||||
|
|
@ -64,78 +90,58 @@ linters-settings:
|
||||||
sql-isolation-level: true
|
sql-isolation-level: true
|
||||||
tls-signature-scheme: true
|
tls-signature-scheme: true
|
||||||
constant-kind: true
|
constant-kind: true
|
||||||
unparam:
|
exclusions:
|
||||||
check-exported: true
|
generated: lax
|
||||||
linters:
|
presets:
|
||||||
disable-all: false
|
- comments
|
||||||
enable:
|
- common-false-positives
|
||||||
- asasalint
|
- legacy
|
||||||
- asciicheck
|
- std-error-handling
|
||||||
- bidichk
|
rules:
|
||||||
- bodyclose
|
- linters:
|
||||||
- containedctx
|
|
||||||
- copyloopvar
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
- dupl
|
||||||
- dupword
|
|
||||||
- durationcheck
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
|
||||||
- errname
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- forcetypeassert
|
|
||||||
- gocognit
|
- gocognit
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- nestif
|
|
||||||
- nolintlint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- revive
|
|
||||||
- sqlclosecheck
|
|
||||||
- staticcheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- wastedassign
|
|
||||||
fast: false
|
|
||||||
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-dirs-use-default: true
|
|
||||||
exclude-dirs:
|
|
||||||
- mocks
|
|
||||||
exclude-rules:
|
|
||||||
- path: _test\.go
|
|
||||||
linters:
|
|
||||||
- gocyclo
|
|
||||||
- gocognit
|
|
||||||
- errcheck
|
|
||||||
- dupl
|
|
||||||
- gosec
|
- gosec
|
||||||
|
path: _test\.go
|
||||||
- linters:
|
- linters:
|
||||||
- staticcheck
|
- staticcheck
|
||||||
text: "SA9003:"
|
text: 'SA9003:'
|
||||||
- linters:
|
- linters:
|
||||||
- lll
|
- lll
|
||||||
source: "^//go:generate "
|
source: '^//go:generate '
|
||||||
- linters:
|
- linters:
|
||||||
- revive
|
- revive
|
||||||
text: "returns unexported type"
|
text: returns unexported type
|
||||||
|
paths:
|
||||||
|
- mocks
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
issues:
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
new: false
|
new: false
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
rewrite-rules:
|
||||||
|
- pattern: interface{}
|
||||||
|
replacement: any
|
||||||
|
- pattern: a[b:len(a)]
|
||||||
|
replacement: a[b:]
|
||||||
|
gofumpt:
|
||||||
|
extra-rules: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- mocks
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
|
||||||
34
vendor/github.com/etkecc/go-kit/ip.go
generated
vendored
Normal file
34
vendor/github.com/etkecc/go-kit/ip.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package kit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnonymizeIP drops the last octet of the IPv4 and IPv6 address to anonymize it
|
||||||
|
func AnonymizeIP(ip string) string {
|
||||||
|
if ip == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parsedIP := net.ParseIP(ip)
|
||||||
|
if parsedIP == nil {
|
||||||
|
return ip // not an ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4
|
||||||
|
if parsedIP.To4() != nil {
|
||||||
|
ipParts := strings.Split(parsedIP.String(), ".")
|
||||||
|
if len(ipParts) == 4 {
|
||||||
|
ipParts[3] = "0"
|
||||||
|
return strings.Join(ipParts, ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6
|
||||||
|
ipParts := strings.Split(parsedIP.String(), ":")
|
||||||
|
if len(ipParts) > 0 {
|
||||||
|
ipParts[len(ipParts)-1] = "0"
|
||||||
|
return strings.Join(ipParts, ":")
|
||||||
|
}
|
||||||
|
return ip // not an ip
|
||||||
|
}
|
||||||
11
vendor/github.com/etkecc/go-kit/strings.go
generated
vendored
11
vendor/github.com/etkecc/go-kit/strings.go
generated
vendored
|
|
@ -63,6 +63,11 @@ func StringToInt(value string, optionalDefaultValue ...int) int {
|
||||||
defaultValue = optionalDefaultValue[0]
|
defaultValue = optionalDefaultValue[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
vInt, err := strconv.Atoi(value)
|
vInt, err := strconv.Atoi(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
|
|
@ -80,7 +85,7 @@ func StringToSlice(value string, optionalDefaultValue ...string) []string {
|
||||||
|
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
if idx := strings.Index(value, ","); idx == -1 {
|
if idx := strings.Index(value, ","); idx == -1 {
|
||||||
value = defaultValue
|
return []string{defaultValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(value, ",")
|
parts := strings.Split(value, ",")
|
||||||
|
|
@ -93,6 +98,10 @@ func StringToSlice(value string, optionalDefaultValue ...string) []string {
|
||||||
|
|
||||||
// SliceToString converts slice of strings into single string (using strings.Join) with optional hook
|
// SliceToString converts slice of strings into single string (using strings.Join) with optional hook
|
||||||
func SliceToString(slice []string, delimiter string, hook func(string) string) string {
|
func SliceToString(slice []string, delimiter string, hook func(string) string) string {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
adjusted := make([]string, 0, len(slice))
|
adjusted := make([]string, 0, len(slice))
|
||||||
for _, item := range slice {
|
for _, item := range slice {
|
||||||
if hook != nil {
|
if hook != nil {
|
||||||
|
|
|
||||||
4
vendor/github.com/etkecc/go-linkpearl/linkpearl.go
generated
vendored
4
vendor/github.com/etkecc/go-linkpearl/linkpearl.go
generated
vendored
|
|
@ -159,7 +159,7 @@ func (l *Linkpearl) Start(ctx context.Context, optionalStatusMsg ...string) erro
|
||||||
|
|
||||||
err := l.api.SetPresence(ctx, mautrix.ReqPresence{Presence: event.PresenceOnline, StatusMsg: statusMsg})
|
err := l.api.SetPresence(ctx, mautrix.ReqPresence{Presence: event.PresenceOnline, StatusMsg: statusMsg})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.log.Error().Err(err).Msg("cannot set presence")
|
l.log.Warn().Err(err).Msg("cannot set presence on start")
|
||||||
}
|
}
|
||||||
defer l.Stop(ctx)
|
defer l.Stop(ctx)
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ func (l *Linkpearl) Start(ctx context.Context, optionalStatusMsg ...string) erro
|
||||||
func (l *Linkpearl) Stop(ctx context.Context) {
|
func (l *Linkpearl) Stop(ctx context.Context) {
|
||||||
l.log.Debug().Msg("stopping the client")
|
l.log.Debug().Msg("stopping the client")
|
||||||
if err := l.api.SetPresence(ctx, mautrix.ReqPresence{Presence: event.PresenceOffline}); err != nil {
|
if err := l.api.SetPresence(ctx, mautrix.ReqPresence{Presence: event.PresenceOffline}); err != nil {
|
||||||
l.log.Error().Err(err).Msg("cannot set presence")
|
l.log.Warn().Err(err).Msg("cannot set presence")
|
||||||
}
|
}
|
||||||
l.api.StopSync()
|
l.api.StopSync()
|
||||||
if err := l.ch.Close(); err != nil {
|
if err := l.ch.Close(); err != nil {
|
||||||
|
|
|
||||||
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
Normal file
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Fatih Arslan
|
||||||
|
|
||||||
|
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.
|
||||||
189
vendor/github.com/fatih/color/README.md
generated
vendored
Normal file
189
vendor/github.com/fatih/color/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# color [](https://github.com/fatih/color/actions) [](https://pkg.go.dev/github.com/fatih/color)
|
||||||
|
|
||||||
|
Color lets you use colorized outputs in terms of [ANSI Escape
|
||||||
|
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
|
||||||
|
has support for Windows too! The API can be used in several ways, pick one that
|
||||||
|
suits you.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/fatih/color
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Standard colors
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Print with default helper functions
|
||||||
|
color.Cyan("Prints text in cyan.")
|
||||||
|
|
||||||
|
// A newline will be appended automatically
|
||||||
|
color.Blue("Prints %s in blue.", "text")
|
||||||
|
|
||||||
|
// These are using the default foreground colors
|
||||||
|
color.Red("We have red")
|
||||||
|
color.Magenta("And many others ..")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### RGB colors
|
||||||
|
|
||||||
|
If your terminal supports 24-bit colors, you can use RGB color codes.
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.RGB(255, 128, 0).Println("foreground orange")
|
||||||
|
color.RGB(230, 42, 42).Println("foreground red")
|
||||||
|
|
||||||
|
color.BgRGB(255, 128, 0).Println("background orange")
|
||||||
|
color.BgRGB(230, 42, 42).Println("background red")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mix and reuse colors
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a new color object
|
||||||
|
c := color.New(color.FgCyan).Add(color.Underline)
|
||||||
|
c.Println("Prints cyan text with an underline.")
|
||||||
|
|
||||||
|
// Or just add them to New()
|
||||||
|
d := color.New(color.FgCyan, color.Bold)
|
||||||
|
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||||
|
|
||||||
|
// Mix up foreground and background colors, create new mixes!
|
||||||
|
red := color.New(color.FgRed)
|
||||||
|
|
||||||
|
boldRed := red.Add(color.Bold)
|
||||||
|
boldRed.Println("This will print text in bold red.")
|
||||||
|
|
||||||
|
whiteBackground := red.Add(color.BgWhite)
|
||||||
|
whiteBackground.Println("Red text with white background.")
|
||||||
|
|
||||||
|
// Mix with RGB color codes
|
||||||
|
color.RGB(255, 128, 0).AddBgRGB(0, 0, 0).Println("orange with black background")
|
||||||
|
|
||||||
|
color.BgRGB(255, 128, 0).AddRGB(255, 255, 255).Println("orange background with white foreground")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use your own output (io.Writer)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Use your own io.Writer output
|
||||||
|
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||||
|
|
||||||
|
blue := color.New(color.FgBlue)
|
||||||
|
blue.Fprint(writer, "This will print text in blue.")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom print functions (PrintFunc)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a custom print function for convenience
|
||||||
|
red := color.New(color.FgRed).PrintfFunc()
|
||||||
|
red("Warning")
|
||||||
|
red("Error: %s", err)
|
||||||
|
|
||||||
|
// Mix up multiple attributes
|
||||||
|
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||||
|
notice("Don't forget this...")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom fprint functions (FprintFunc)
|
||||||
|
|
||||||
|
```go
|
||||||
|
blue := color.New(color.FgBlue).FprintfFunc()
|
||||||
|
blue(myWriter, "important notice: %s", stars)
|
||||||
|
|
||||||
|
// Mix up with multiple attributes
|
||||||
|
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||||
|
success(myWriter, "Don't forget this...")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Insert into noncolor strings (SprintFunc)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create SprintXxx functions to mix strings with other non-colorized strings:
|
||||||
|
yellow := color.New(color.FgYellow).SprintFunc()
|
||||||
|
red := color.New(color.FgRed).SprintFunc()
|
||||||
|
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||||
|
|
||||||
|
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
|
||||||
|
fmt.Printf("This %s rocks!\n", info("package"))
|
||||||
|
|
||||||
|
// Use helper functions
|
||||||
|
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
|
||||||
|
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
|
||||||
|
|
||||||
|
// Windows supported too! Just don't forget to change the output to color.Output
|
||||||
|
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plug into existing code
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Use handy standard colors
|
||||||
|
color.Set(color.FgYellow)
|
||||||
|
|
||||||
|
fmt.Println("Existing text will now be in yellow")
|
||||||
|
fmt.Printf("This one %s\n", "too")
|
||||||
|
|
||||||
|
color.Unset() // Don't forget to unset
|
||||||
|
|
||||||
|
// You can mix up parameters
|
||||||
|
color.Set(color.FgMagenta, color.Bold)
|
||||||
|
defer color.Unset() // Use it in your function
|
||||||
|
|
||||||
|
fmt.Println("All text will now be bold magenta.")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable/Enable color
|
||||||
|
|
||||||
|
There might be a case where you want to explicitly disable/enable color output. the
|
||||||
|
`go-isatty` package will automatically disable color output for non-tty output streams
|
||||||
|
(for example if the output were piped directly to `less`).
|
||||||
|
|
||||||
|
The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
|
||||||
|
variable is set to a non-empty string.
|
||||||
|
|
||||||
|
`Color` has support to disable/enable colors programmatically both globally and
|
||||||
|
for single color definitions. For example suppose you have a CLI app and a
|
||||||
|
`-no-color` bool flag. You can easily disable the color output with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||||
|
|
||||||
|
if *flagNoColor {
|
||||||
|
color.NoColor = true // disables colorized output
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It also has support for single color definitions (local). You can
|
||||||
|
disable/enable color output on the fly:
|
||||||
|
|
||||||
|
```go
|
||||||
|
c := color.New(color.FgCyan)
|
||||||
|
c.Println("Prints cyan text")
|
||||||
|
|
||||||
|
c.DisableColor()
|
||||||
|
c.Println("This is printed without any color")
|
||||||
|
|
||||||
|
c.EnableColor()
|
||||||
|
c.Println("This prints again cyan...")
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Actions
|
||||||
|
|
||||||
|
To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Fatih Arslan](https://github.com/fatih)
|
||||||
|
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
|
||||||
685
vendor/github.com/fatih/color/color.go
generated
vendored
Normal file
685
vendor/github.com/fatih/color/color.go
generated
vendored
Normal file
|
|
@ -0,0 +1,685 @@
|
||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||||
|
// false or true based on the stdout's file descriptor referring to a terminal
|
||||||
|
// or not. It's also set to true if the NO_COLOR environment variable is
|
||||||
|
// set (regardless of its value). This is a global option and affects all
|
||||||
|
// colors. For more control over each color block use the methods
|
||||||
|
// DisableColor() individually.
|
||||||
|
NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" ||
|
||||||
|
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||||
|
|
||||||
|
// Output defines the standard output of the print functions. By default,
|
||||||
|
// os.Stdout is used.
|
||||||
|
Output = colorable.NewColorableStdout()
|
||||||
|
|
||||||
|
// Error defines a color supporting writer for os.Stderr.
|
||||||
|
Error = colorable.NewColorableStderr()
|
||||||
|
|
||||||
|
// colorsCache is used to reduce the count of created Color objects and
|
||||||
|
// allows to reuse already created objects with required Attribute.
|
||||||
|
colorsCache = make(map[Attribute]*Color)
|
||||||
|
colorsCacheMu sync.Mutex // protects colorsCache
|
||||||
|
)
|
||||||
|
|
||||||
|
// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string.
|
||||||
|
func noColorIsSet() bool {
|
||||||
|
return os.Getenv("NO_COLOR") != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color defines a custom color object which is defined by SGR parameters.
|
||||||
|
type Color struct {
|
||||||
|
params []Attribute
|
||||||
|
noColor *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute defines a single SGR Code
|
||||||
|
type Attribute int
|
||||||
|
|
||||||
|
const escape = "\x1b"
|
||||||
|
|
||||||
|
// Base attributes
|
||||||
|
const (
|
||||||
|
Reset Attribute = iota
|
||||||
|
Bold
|
||||||
|
Faint
|
||||||
|
Italic
|
||||||
|
Underline
|
||||||
|
BlinkSlow
|
||||||
|
BlinkRapid
|
||||||
|
ReverseVideo
|
||||||
|
Concealed
|
||||||
|
CrossedOut
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResetBold Attribute = iota + 22
|
||||||
|
ResetItalic
|
||||||
|
ResetUnderline
|
||||||
|
ResetBlinking
|
||||||
|
_
|
||||||
|
ResetReversed
|
||||||
|
ResetConcealed
|
||||||
|
ResetCrossedOut
|
||||||
|
)
|
||||||
|
|
||||||
|
var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{
|
||||||
|
Bold: ResetBold,
|
||||||
|
Faint: ResetBold,
|
||||||
|
Italic: ResetItalic,
|
||||||
|
Underline: ResetUnderline,
|
||||||
|
BlinkSlow: ResetBlinking,
|
||||||
|
BlinkRapid: ResetBlinking,
|
||||||
|
ReverseVideo: ResetReversed,
|
||||||
|
Concealed: ResetConcealed,
|
||||||
|
CrossedOut: ResetCrossedOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreground text colors
|
||||||
|
const (
|
||||||
|
FgBlack Attribute = iota + 30
|
||||||
|
FgRed
|
||||||
|
FgGreen
|
||||||
|
FgYellow
|
||||||
|
FgBlue
|
||||||
|
FgMagenta
|
||||||
|
FgCyan
|
||||||
|
FgWhite
|
||||||
|
|
||||||
|
// used internally for 256 and 24-bit coloring
|
||||||
|
foreground
|
||||||
|
)
|
||||||
|
|
||||||
|
// Foreground Hi-Intensity text colors
|
||||||
|
const (
|
||||||
|
FgHiBlack Attribute = iota + 90
|
||||||
|
FgHiRed
|
||||||
|
FgHiGreen
|
||||||
|
FgHiYellow
|
||||||
|
FgHiBlue
|
||||||
|
FgHiMagenta
|
||||||
|
FgHiCyan
|
||||||
|
FgHiWhite
|
||||||
|
)
|
||||||
|
|
||||||
|
// Background text colors
|
||||||
|
const (
|
||||||
|
BgBlack Attribute = iota + 40
|
||||||
|
BgRed
|
||||||
|
BgGreen
|
||||||
|
BgYellow
|
||||||
|
BgBlue
|
||||||
|
BgMagenta
|
||||||
|
BgCyan
|
||||||
|
BgWhite
|
||||||
|
|
||||||
|
// used internally for 256 and 24-bit coloring
|
||||||
|
background
|
||||||
|
)
|
||||||
|
|
||||||
|
// Background Hi-Intensity text colors
|
||||||
|
const (
|
||||||
|
BgHiBlack Attribute = iota + 100
|
||||||
|
BgHiRed
|
||||||
|
BgHiGreen
|
||||||
|
BgHiYellow
|
||||||
|
BgHiBlue
|
||||||
|
BgHiMagenta
|
||||||
|
BgHiCyan
|
||||||
|
BgHiWhite
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a newly created color object.
|
||||||
|
func New(value ...Attribute) *Color {
|
||||||
|
c := &Color{
|
||||||
|
params: make([]Attribute, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if noColorIsSet() {
|
||||||
|
c.noColor = boolPtr(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Add(value...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB returns a new foreground color in 24-bit RGB.
|
||||||
|
func RGB(r, g, b int) *Color {
|
||||||
|
return New(foreground, 2, Attribute(r), Attribute(g), Attribute(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BgRGB returns a new background color in 24-bit RGB.
|
||||||
|
func BgRGB(r, g, b int) *Color {
|
||||||
|
return New(background, 2, Attribute(r), Attribute(g), Attribute(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRGB is used to chain foreground RGB SGR parameters. Use as many as parameters to combine
|
||||||
|
// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
|
||||||
|
func (c *Color) AddRGB(r, g, b int) *Color {
|
||||||
|
c.params = append(c.params, foreground, 2, Attribute(r), Attribute(g), Attribute(b))
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRGB is used to chain background RGB SGR parameters. Use as many as parameters to combine
|
||||||
|
// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
|
||||||
|
func (c *Color) AddBgRGB(r, g, b int) *Color {
|
||||||
|
c.params = append(c.params, background, 2, Attribute(r), Attribute(g), Attribute(b))
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the given parameters immediately. It will change the color of
|
||||||
|
// output with the given SGR parameters until color.Unset() is called.
|
||||||
|
func Set(p ...Attribute) *Color {
|
||||||
|
c := New(p...)
|
||||||
|
c.Set()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unset resets all escape attributes and clears the output. Usually should
|
||||||
|
// be called after Set().
|
||||||
|
func Unset() {
|
||||||
|
if NoColor {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the SGR sequence.
|
||||||
|
func (c *Color) Set() *Color {
|
||||||
|
if c.isNoColorSet() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(Output, c.format())
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) unset() {
|
||||||
|
if c.isNoColorSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Unset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriter is used to set the SGR sequence with the given io.Writer. This is
|
||||||
|
// a low-level function, and users should use the higher-level functions, such
|
||||||
|
// as color.Fprint, color.Print, etc.
|
||||||
|
func (c *Color) SetWriter(w io.Writer) *Color {
|
||||||
|
if c.isNoColorSet() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, c.format())
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsetWriter resets all escape attributes and clears the output with the give
|
||||||
|
// io.Writer. Usually should be called after SetWriter().
|
||||||
|
func (c *Color) UnsetWriter(w io.Writer) {
|
||||||
|
if c.isNoColorSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if NoColor {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||||
|
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||||
|
func (c *Color) Add(value ...Attribute) *Color {
|
||||||
|
c.params = append(c.params, value...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint formats using the default formats for its operands and writes to w.
|
||||||
|
// Spaces are added between operands when neither is a string.
|
||||||
|
// It returns the number of bytes written and any write error encountered.
|
||||||
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||||
|
// type *os.File.
|
||||||
|
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
c.SetWriter(w)
|
||||||
|
defer c.UnsetWriter(w)
|
||||||
|
|
||||||
|
return fmt.Fprint(w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print formats using the default formats for its operands and writes to
|
||||||
|
// standard output. Spaces are added between operands when neither is a
|
||||||
|
// string. It returns the number of bytes written and any write error
|
||||||
|
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||||
|
// color.
|
||||||
|
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||||
|
c.Set()
|
||||||
|
defer c.unset()
|
||||||
|
|
||||||
|
return fmt.Fprint(Output, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf formats according to a format specifier and writes to w.
|
||||||
|
// It returns the number of bytes written and any write error encountered.
|
||||||
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||||
|
// type *os.File.
|
||||||
|
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
c.SetWriter(w)
|
||||||
|
defer c.UnsetWriter(w)
|
||||||
|
|
||||||
|
return fmt.Fprintf(w, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf formats according to a format specifier and writes to standard output.
|
||||||
|
// It returns the number of bytes written and any write error encountered.
|
||||||
|
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||||
|
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
c.Set()
|
||||||
|
defer c.unset()
|
||||||
|
|
||||||
|
return fmt.Fprintf(Output, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln formats using the default formats for its operands and writes to w.
|
||||||
|
// Spaces are always added between operands and a newline is appended.
|
||||||
|
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||||
|
// type *os.File.
|
||||||
|
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.wrap(sprintln(a...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println formats using the default formats for its operands and writes to
|
||||||
|
// standard output. Spaces are always added between operands and a newline is
|
||||||
|
// appended. It returns the number of bytes written and any write error
|
||||||
|
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||||
|
// color.
|
||||||
|
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(Output, c.wrap(sprintln(a...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is just like Print, but returns a string instead of printing it.
|
||||||
|
func (c *Color) Sprint(a ...interface{}) string {
|
||||||
|
return c.wrap(fmt.Sprint(a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||||
|
func (c *Color) Sprintln(a ...interface{}) string {
|
||||||
|
return c.wrap(sprintln(a...)) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||||
|
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return c.wrap(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FprintFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Fprint().
|
||||||
|
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||||
|
return func(w io.Writer, a ...interface{}) {
|
||||||
|
c.Fprint(w, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Print().
|
||||||
|
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||||
|
return func(a ...interface{}) {
|
||||||
|
c.Print(a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FprintfFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Fprintf().
|
||||||
|
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||||
|
return func(w io.Writer, format string, a ...interface{}) {
|
||||||
|
c.Fprintf(w, format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintfFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Printf().
|
||||||
|
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||||
|
return func(format string, a ...interface{}) {
|
||||||
|
c.Printf(format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Fprintln().
|
||||||
|
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||||
|
return func(w io.Writer, a ...interface{}) {
|
||||||
|
c.Fprintln(w, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||||
|
// colorized with color.Println().
|
||||||
|
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||||
|
return func(a ...interface{}) {
|
||||||
|
c.Println(a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SprintFunc returns a new function that returns colorized strings for the
|
||||||
|
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||||
|
// string. Windows users should use this in conjunction with color.Output, example:
|
||||||
|
//
|
||||||
|
// put := New(FgYellow).SprintFunc()
|
||||||
|
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||||
|
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||||
|
return func(a ...interface{}) string {
|
||||||
|
return c.wrap(fmt.Sprint(a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SprintfFunc returns a new function that returns colorized strings for the
|
||||||
|
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||||
|
// string. Windows users should use this in conjunction with color.Output.
|
||||||
|
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||||
|
return func(format string, a ...interface{}) string {
|
||||||
|
return c.wrap(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||||
|
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||||
|
// string. Windows users should use this in conjunction with color.Output.
|
||||||
|
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||||
|
return func(a ...interface{}) string {
|
||||||
|
return c.wrap(sprintln(a...)) + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||||
|
// an example output might be: "1;36" -> bold cyan
|
||||||
|
func (c *Color) sequence() string {
|
||||||
|
format := make([]string, len(c.params))
|
||||||
|
for i, v := range c.params {
|
||||||
|
format[i] = strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(format, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||||
|
// be printed.
|
||||||
|
func (c *Color) wrap(s string) string {
|
||||||
|
if c.isNoColorSet() {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.format() + s + c.unformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) format() string {
|
||||||
|
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) unformat() string {
|
||||||
|
//return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||||
|
//for each element in sequence let's use the specific reset escape, or the generic one if not found
|
||||||
|
format := make([]string, len(c.params))
|
||||||
|
for i, v := range c.params {
|
||||||
|
format[i] = strconv.Itoa(int(Reset))
|
||||||
|
ra, ok := mapResetAttributes[v]
|
||||||
|
if ok {
|
||||||
|
format[i] = strconv.Itoa(int(ra))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableColor disables the color output. Useful to not change any existing
|
||||||
|
// code and still being able to output. Can be used for flags like
|
||||||
|
// "--no-color". To enable back use EnableColor() method.
|
||||||
|
func (c *Color) DisableColor() {
|
||||||
|
c.noColor = boolPtr(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableColor enables the color output. Use it in conjunction with
|
||||||
|
// DisableColor(). Otherwise, this method has no side effects.
|
||||||
|
func (c *Color) EnableColor() {
|
||||||
|
c.noColor = boolPtr(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) isNoColorSet() bool {
|
||||||
|
// check first if we have user set action
|
||||||
|
if c.noColor != nil {
|
||||||
|
return *c.noColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not return the global option, which is disabled by default
|
||||||
|
return NoColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns a boolean value indicating whether two colors are equal.
|
||||||
|
func (c *Color) Equals(c2 *Color) bool {
|
||||||
|
if c == nil && c2 == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c == nil || c2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(c.params) != len(c2.params) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range c.params {
|
||||||
|
if !c2.attrExists(attr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) attrExists(a Attribute) bool {
|
||||||
|
for _, attr := range c.params {
|
||||||
|
if attr == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolPtr(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCachedColor(p Attribute) *Color {
|
||||||
|
colorsCacheMu.Lock()
|
||||||
|
defer colorsCacheMu.Unlock()
|
||||||
|
|
||||||
|
c, ok := colorsCache[p]
|
||||||
|
if !ok {
|
||||||
|
c = New(p)
|
||||||
|
colorsCache[p] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||||
|
c := getCachedColor(p)
|
||||||
|
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a) == 0 {
|
||||||
|
c.Print(format)
|
||||||
|
} else {
|
||||||
|
c.Printf(format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||||
|
c := getCachedColor(p)
|
||||||
|
|
||||||
|
if len(a) == 0 {
|
||||||
|
return c.SprintFunc()(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SprintfFunc()(format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black is a convenient helper function to print with black foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||||
|
|
||||||
|
// Red is a convenient helper function to print with red foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||||
|
|
||||||
|
// Green is a convenient helper function to print with green foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||||
|
|
||||||
|
// Yellow is a convenient helper function to print with yellow foreground.
|
||||||
|
// A newline is appended to format by default.
|
||||||
|
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||||
|
|
||||||
|
// Blue is a convenient helper function to print with blue foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||||
|
|
||||||
|
// Magenta is a convenient helper function to print with magenta foreground.
|
||||||
|
// A newline is appended to format by default.
|
||||||
|
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||||
|
|
||||||
|
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||||
|
|
||||||
|
// White is a convenient helper function to print with white foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||||
|
|
||||||
|
// BlackString is a convenient helper function to return a string with black
|
||||||
|
// foreground.
|
||||||
|
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||||
|
|
||||||
|
// RedString is a convenient helper function to return a string with red
|
||||||
|
// foreground.
|
||||||
|
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||||
|
|
||||||
|
// GreenString is a convenient helper function to return a string with green
|
||||||
|
// foreground.
|
||||||
|
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||||
|
|
||||||
|
// YellowString is a convenient helper function to return a string with yellow
|
||||||
|
// foreground.
|
||||||
|
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||||
|
|
||||||
|
// BlueString is a convenient helper function to return a string with blue
|
||||||
|
// foreground.
|
||||||
|
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||||
|
|
||||||
|
// MagentaString is a convenient helper function to return a string with magenta
|
||||||
|
// foreground.
|
||||||
|
func MagentaString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgMagenta, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CyanString is a convenient helper function to return a string with cyan
|
||||||
|
// foreground.
|
||||||
|
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||||
|
|
||||||
|
// WhiteString is a convenient helper function to return a string with white
|
||||||
|
// foreground.
|
||||||
|
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||||
|
|
||||||
|
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||||
|
|
||||||
|
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||||
|
|
||||||
|
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||||
|
|
||||||
|
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||||
|
// A newline is appended to format by default.
|
||||||
|
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||||
|
|
||||||
|
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||||
|
|
||||||
|
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||||
|
// A newline is appended to format by default.
|
||||||
|
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||||
|
|
||||||
|
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||||
|
|
||||||
|
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||||
|
// newline is appended to format by default.
|
||||||
|
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||||
|
|
||||||
|
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||||
|
// foreground.
|
||||||
|
func HiBlackString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgHiBlack, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||||
|
// foreground.
|
||||||
|
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||||
|
|
||||||
|
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||||
|
// foreground.
|
||||||
|
func HiGreenString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgHiGreen, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||||
|
// foreground.
|
||||||
|
func HiYellowString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgHiYellow, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||||
|
// foreground.
|
||||||
|
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||||
|
|
||||||
|
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||||
|
// foreground.
|
||||||
|
func HiMagentaString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgHiMagenta, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||||
|
// foreground.
|
||||||
|
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||||
|
|
||||||
|
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||||
|
// foreground.
|
||||||
|
func HiWhiteString(format string, a ...interface{}) string {
|
||||||
|
return colorString(format, FgHiWhite, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sprintln is a helper function to format a string with fmt.Sprintln and trim the trailing newline.
|
||||||
|
func sprintln(a ...interface{}) string {
|
||||||
|
return strings.TrimSuffix(fmt.Sprintln(a...), "\n")
|
||||||
|
}
|
||||||
19
vendor/github.com/fatih/color/color_windows.go
generated
vendored
Normal file
19
vendor/github.com/fatih/color/color_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Opt-in for ansi color support for current process.
|
||||||
|
// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences
|
||||||
|
var outMode uint32
|
||||||
|
out := windows.Handle(os.Stdout.Fd())
|
||||||
|
if err := windows.GetConsoleMode(out, &outMode); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
_ = windows.SetConsoleMode(out, outMode)
|
||||||
|
}
|
||||||
134
vendor/github.com/fatih/color/doc.go
generated
vendored
Normal file
134
vendor/github.com/fatih/color/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
Package color is an ANSI color package to output colorized or SGR defined
|
||||||
|
output to the standard output. The API can be used in several way, pick one
|
||||||
|
that suits you.
|
||||||
|
|
||||||
|
Use simple and default helper functions with predefined foreground colors:
|
||||||
|
|
||||||
|
color.Cyan("Prints text in cyan.")
|
||||||
|
|
||||||
|
// a newline will be appended automatically
|
||||||
|
color.Blue("Prints %s in blue.", "text")
|
||||||
|
|
||||||
|
// More default foreground colors..
|
||||||
|
color.Red("We have red")
|
||||||
|
color.Yellow("Yellow color too!")
|
||||||
|
color.Magenta("And many others ..")
|
||||||
|
|
||||||
|
// Hi-intensity colors
|
||||||
|
color.HiGreen("Bright green color.")
|
||||||
|
color.HiBlack("Bright black means gray..")
|
||||||
|
color.HiWhite("Shiny white color!")
|
||||||
|
|
||||||
|
However, there are times when custom color mixes are required. Below are some
|
||||||
|
examples to create custom color objects and use the print functions of each
|
||||||
|
separate color object.
|
||||||
|
|
||||||
|
// Create a new color object
|
||||||
|
c := color.New(color.FgCyan).Add(color.Underline)
|
||||||
|
c.Println("Prints cyan text with an underline.")
|
||||||
|
|
||||||
|
// Or just add them to New()
|
||||||
|
d := color.New(color.FgCyan, color.Bold)
|
||||||
|
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||||
|
|
||||||
|
|
||||||
|
// Mix up foreground and background colors, create new mixes!
|
||||||
|
red := color.New(color.FgRed)
|
||||||
|
|
||||||
|
boldRed := red.Add(color.Bold)
|
||||||
|
boldRed.Println("This will print text in bold red.")
|
||||||
|
|
||||||
|
whiteBackground := red.Add(color.BgWhite)
|
||||||
|
whiteBackground.Println("Red text with White background.")
|
||||||
|
|
||||||
|
// Use your own io.Writer output
|
||||||
|
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||||
|
|
||||||
|
blue := color.New(color.FgBlue)
|
||||||
|
blue.Fprint(myWriter, "This will print text in blue.")
|
||||||
|
|
||||||
|
You can create PrintXxx functions to simplify even more:
|
||||||
|
|
||||||
|
// Create a custom print function for convenient
|
||||||
|
red := color.New(color.FgRed).PrintfFunc()
|
||||||
|
red("warning")
|
||||||
|
red("error: %s", err)
|
||||||
|
|
||||||
|
// Mix up multiple attributes
|
||||||
|
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||||
|
notice("don't forget this...")
|
||||||
|
|
||||||
|
You can also FprintXxx functions to pass your own io.Writer:
|
||||||
|
|
||||||
|
blue := color.New(FgBlue).FprintfFunc()
|
||||||
|
blue(myWriter, "important notice: %s", stars)
|
||||||
|
|
||||||
|
// Mix up with multiple attributes
|
||||||
|
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||||
|
success(myWriter, don't forget this...")
|
||||||
|
|
||||||
|
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||||
|
|
||||||
|
yellow := New(FgYellow).SprintFunc()
|
||||||
|
red := New(FgRed).SprintFunc()
|
||||||
|
|
||||||
|
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||||
|
|
||||||
|
info := New(FgWhite, BgGreen).SprintFunc()
|
||||||
|
fmt.Printf("this %s rocks!\n", info("package"))
|
||||||
|
|
||||||
|
Windows support is enabled by default. All Print functions work as intended.
|
||||||
|
However, only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||||
|
set the output to color.Output:
|
||||||
|
|
||||||
|
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||||
|
|
||||||
|
info := New(FgWhite, BgGreen).SprintFunc()
|
||||||
|
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||||
|
|
||||||
|
Using with existing code is possible. Just use the Set() method to set the
|
||||||
|
standard output to the given parameters. That way a rewrite of an existing
|
||||||
|
code is not required.
|
||||||
|
|
||||||
|
// Use handy standard colors.
|
||||||
|
color.Set(color.FgYellow)
|
||||||
|
|
||||||
|
fmt.Println("Existing text will be now in Yellow")
|
||||||
|
fmt.Printf("This one %s\n", "too")
|
||||||
|
|
||||||
|
color.Unset() // don't forget to unset
|
||||||
|
|
||||||
|
// You can mix up parameters
|
||||||
|
color.Set(color.FgMagenta, color.Bold)
|
||||||
|
defer color.Unset() // use it in your function
|
||||||
|
|
||||||
|
fmt.Println("All text will be now bold magenta.")
|
||||||
|
|
||||||
|
There might be a case where you want to disable color output (for example to
|
||||||
|
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||||
|
disable colors both globally and for single color definition. For example
|
||||||
|
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||||
|
the color output with:
|
||||||
|
|
||||||
|
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||||
|
|
||||||
|
if *flagNoColor {
|
||||||
|
color.NoColor = true // disables colorized output
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also disable the color by setting the NO_COLOR environment variable to any value.
|
||||||
|
|
||||||
|
It also has support for single color definitions (local). You can
|
||||||
|
disable/enable color output on the fly:
|
||||||
|
|
||||||
|
c := color.New(color.FgCyan)
|
||||||
|
c.Println("Prints cyan text")
|
||||||
|
|
||||||
|
c.DisableColor()
|
||||||
|
c.Println("This is printed without any color")
|
||||||
|
|
||||||
|
c.EnableColor()
|
||||||
|
c.Println("This prints again cyan...")
|
||||||
|
*/
|
||||||
|
package color
|
||||||
2
vendor/github.com/getsentry/sentry-go/.codecov.yml
generated
vendored
2
vendor/github.com/getsentry/sentry-go/.codecov.yml
generated
vendored
|
|
@ -11,3 +11,5 @@ coverage:
|
||||||
default:
|
default:
|
||||||
# Do not fail the commit status if the coverage was reduced up to this value
|
# Do not fail the commit status if the coverage was reduced up to this value
|
||||||
threshold: 0.5%
|
threshold: 0.5%
|
||||||
|
ignore:
|
||||||
|
- "log_fallback.go"
|
||||||
|
|
|
||||||
52
vendor/github.com/getsentry/sentry-go/.golangci.yml
generated
vendored
52
vendor/github.com/getsentry/sentry-go/.golangci.yml
generated
vendored
|
|
@ -1,20 +1,17 @@
|
||||||
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
- exportloopref
|
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- goconst
|
- goconst
|
||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godot
|
- godot
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- misspell
|
- misspell
|
||||||
|
|
@ -22,25 +19,44 @@ linters:
|
||||||
- prealloc
|
- prealloc
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- whitespace
|
- whitespace
|
||||||
issues:
|
exclusions:
|
||||||
exclude-rules:
|
generated: lax
|
||||||
- path: _test\.go
|
presets:
|
||||||
linters:
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
- goconst
|
- goconst
|
||||||
- prealloc
|
- prealloc
|
||||||
- path: _test\.go
|
path: _test\.go
|
||||||
text: "G306:"
|
- linters:
|
||||||
linters:
|
|
||||||
- gosec
|
- gosec
|
||||||
- path: errors_test\.go
|
path: _test\.go
|
||||||
linters:
|
text: 'G306:'
|
||||||
|
- linters:
|
||||||
- unused
|
- unused
|
||||||
- path: http/example_test\.go
|
path: errors_test\.go
|
||||||
linters:
|
- linters:
|
||||||
- errcheck
|
|
||||||
- bodyclose
|
- bodyclose
|
||||||
|
- errcheck
|
||||||
|
path: http/example_test\.go
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
|
||||||
133
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
133
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
|
|
@ -1,5 +1,138 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.34.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.34.0.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Logrus structured logging support replaces the `sentrylogrus.Hook` signature from a `*Hook` to an interface.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var hook *sentrylogrus.Hook
|
||||||
|
hook = sentrylogrus.New(
|
||||||
|
// ... your setup
|
||||||
|
)
|
||||||
|
|
||||||
|
// should change the definition to
|
||||||
|
var hook sentrylogrus.Hook
|
||||||
|
hook = sentrylogrus.New(
|
||||||
|
// ... your setup
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Structured logging support for [slog](https://pkg.go.dev/log/slog). ([#1033](https://github.com/getsentry/sentry-go/pull/1033))
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx := context.Background()
|
||||||
|
handler := sentryslog.Option{
|
||||||
|
EventLevel: []slog.Level{slog.LevelError, sentryslog.LevelFatal}, // Only Error and Fatal as events
|
||||||
|
LogLevel: []slog.Level{slog.LevelWarn, slog.LevelInfo}, // Only Warn and Info as logs
|
||||||
|
}.NewSentryHandler(ctx)
|
||||||
|
logger := slog.New(handler)
|
||||||
|
logger.Info("hello"))
|
||||||
|
```
|
||||||
|
|
||||||
|
- Structured logging support for [logrus](https://github.com/sirupsen/logrus). ([#1036](https://github.com/getsentry/sentry-go/pull/1036))
|
||||||
|
```go
|
||||||
|
logHook, _ := sentrylogrus.NewLogHook(
|
||||||
|
[]logrus.Level{logrus.InfoLevel, logrus.WarnLevel},
|
||||||
|
sentry.ClientOptions{
|
||||||
|
Dsn: "your-dsn",
|
||||||
|
EnableLogs: true, // Required for log entries
|
||||||
|
})
|
||||||
|
defer logHook.Flush(5 * time.Secod)
|
||||||
|
logrus.RegisterExitHandler(func() {
|
||||||
|
logHook.Flush(5 * time.Second)
|
||||||
|
})
|
||||||
|
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.AddHook(logHook)
|
||||||
|
logger.Infof("hello")
|
||||||
|
```
|
||||||
|
|
||||||
|
- Add support for flushing events with context using `FlushWithContext()`. ([#935](https://github.com/getsentry/sentry-go/pull/935))
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if !sentry.FlushWithContext(ctx) {
|
||||||
|
// Handle timeout or cancellation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Add support for custom fingerprints in slog integration. ([#1039](https://github.com/getsentry/sentry-go/pull/1039))
|
||||||
|
|
||||||
|
### Deprecations
|
||||||
|
|
||||||
|
- Slog structured logging support replaces `Level` option with `EventLevel` and `LogLevel` options, for specifying fine-grained levels for capturing events and logs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
handler := sentryslog.Option{
|
||||||
|
EventLevel: []slog.Level{slog.LevelWarn, slog.LevelError, sentryslog.LevelFatal},
|
||||||
|
LogLevel: []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError, sentryslog.LevelFatal},
|
||||||
|
}.NewSentryHandler(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Logrus structured logging support replaces `New` and `NewFromClient` functions to `NewEventHook`, `NewEventHookFromClient`, to match the newly added `NewLogHook` functions, and specify the hook type being created each time.
|
||||||
|
|
||||||
|
```go
|
||||||
|
logHook, err := sentrylogrus.NewLogHook(
|
||||||
|
[]logrus.Level{logrus.InfoLevel},
|
||||||
|
sentry.ClientOptions{})
|
||||||
|
eventHook, err := sentrylogrus.NewEventHook([]logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.PanicLevel,
|
||||||
|
}, sentry.ClientOptions{})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fix issue where `ContinueTrace()` would panic when `sentry-trace` header does not exist. ([#1026](https://github.com/getsentry/sentry-go/pull/1026))
|
||||||
|
- Fix incorrect log level signature in structured logging. ([#1034](https://github.com/getsentry/sentry-go/pull/1034))
|
||||||
|
- Remove `sentry.origin` attribute from Sentry logger to prevent confusion in spans. ([#1038](https://github.com/getsentry/sentry-go/pull/1038))
|
||||||
|
- Don't gate user information behind `SendDefaultPII` flag for logs. ([#1032](https://github.com/getsentry/sentry-go/pull/1032))
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Add more sensitive HTTP headers to the default list of headers that are scrubbed by default. ([#1008](https://github.com/getsentry/sentry-go/pull/1008))
|
||||||
|
|
||||||
|
## 0.33.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.33.0.
|
||||||
|
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Rename the internal `Logger` to `DebugLogger`. This feature was only used when you set `Debug: True` in your `sentry.Init()` call. If you haven't used the Logger directly, no changes are necessary. ([#1012](https://github.com/getsentry/sentry-go/issues/1012))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add support for [Structured Logging](https://docs.sentry.io/product/explore/logs/). ([#1010](https://github.com/getsentry/sentry-go/issues/1010))
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := sentry.NewLogger(ctx)
|
||||||
|
logger.Info(ctx, "Hello, Logs!")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can learn more about Sentry Logs on our [docs](https://docs.sentry.io/product/explore/logs/) and the [examples](https://github.com/getsentry/sentry-go/blob/master/_examples/logs/main.go).
|
||||||
|
|
||||||
|
- Add new attributes APIs, which are currently only exposed on logs. ([#1007](https://github.com/getsentry/sentry-go/issues/1007))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Do not push a new scope on `StartSpan`. ([#1013](https://github.com/getsentry/sentry-go/issues/1013))
|
||||||
|
- Fix an issue where the propagated smapling decision wasn't used. ([#995](https://github.com/getsentry/sentry-go/issues/995))
|
||||||
|
- [Otel] Prefer `httpRoute` over `httpTarget` for span descriptions. ([#1002](https://github.com/getsentry/sentry-go/issues/1002))
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Update `github.com/stretchr/testify` to v1.8.4. ([#988](https://github.com/getsentry/sentry-go/issues/988))
|
||||||
|
|
||||||
## 0.32.0
|
## 0.32.0
|
||||||
|
|
||||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.32.0.
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.32.0.
|
||||||
|
|
|
||||||
36
vendor/github.com/getsentry/sentry-go/attribute/builder.go
generated
vendored
Normal file
36
vendor/github.com/getsentry/sentry-go/attribute/builder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package attribute
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
Key string
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a Builder for a string value.
|
||||||
|
func String(key, value string) Builder {
|
||||||
|
return Builder{key, StringValue(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns a Builder for an int64.
|
||||||
|
func Int64(key string, value int64) Builder {
|
||||||
|
return Builder{key, Int64Value(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns a Builder for an int64.
|
||||||
|
func Int(key string, value int) Builder {
|
||||||
|
return Builder{key, IntValue(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns a Builder for a float64.
|
||||||
|
func Float64(key string, v float64) Builder {
|
||||||
|
return Builder{key, Float64Value(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns a Builder for a boolean.
|
||||||
|
func Bool(key string, v bool) Builder {
|
||||||
|
return Builder{key, BoolValue(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid checks for valid key and type.
|
||||||
|
func (b *Builder) Valid() bool {
|
||||||
|
return len(b.Key) > 0 && b.Value.Type() != INVALID
|
||||||
|
}
|
||||||
49
vendor/github.com/getsentry/sentry-go/attribute/rawhelpers.go
generated
vendored
Normal file
49
vendor/github.com/getsentry/sentry-go/attribute/rawhelpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copied from https://github.com/open-telemetry/opentelemetry-go/blob/cc43e01c27892252aac9a8f20da28cdde957a289/attribute/rawhelpers.go
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package attribute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func boolToRaw(b bool) uint64 { // b is not a control flag.
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawToBool(r uint64) bool {
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64ToRaw(i int64) uint64 {
|
||||||
|
// Assumes original was a valid int64 (overflow not checked).
|
||||||
|
return uint64(i) // nolint: gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawToInt64(r uint64) int64 {
|
||||||
|
// Assumes original was a valid int64 (overflow not checked).
|
||||||
|
return int64(r) // nolint: gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
func float64ToRaw(f float64) uint64 {
|
||||||
|
return math.Float64bits(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawToFloat64(r uint64) float64 {
|
||||||
|
return math.Float64frombits(r)
|
||||||
|
}
|
||||||
170
vendor/github.com/getsentry/sentry-go/attribute/value.go
generated
vendored
Normal file
170
vendor/github.com/getsentry/sentry-go/attribute/value.go
generated
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
// Adapted from https://github.com/open-telemetry/opentelemetry-go/blob/cc43e01c27892252aac9a8f20da28cdde957a289/attribute/value.go
|
||||||
|
//
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package attribute
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type describes the type of the data Value holds.
|
||||||
|
type Type int // redefines builtin Type.
|
||||||
|
|
||||||
|
// Value represents the value part in key-value pairs.
|
||||||
|
type Value struct {
|
||||||
|
vtype Type
|
||||||
|
numeric uint64
|
||||||
|
stringly string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// INVALID is used for a Value with no value set.
|
||||||
|
INVALID Type = iota
|
||||||
|
// BOOL is a boolean Type Value.
|
||||||
|
BOOL
|
||||||
|
// INT64 is a 64-bit signed integral Type Value.
|
||||||
|
INT64
|
||||||
|
// FLOAT64 is a 64-bit floating point Type Value.
|
||||||
|
FLOAT64
|
||||||
|
// STRING is a string Type Value.
|
||||||
|
STRING
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoolValue creates a BOOL Value.
|
||||||
|
func BoolValue(v bool) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: BOOL,
|
||||||
|
numeric: boolToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntValue creates an INT64 Value.
|
||||||
|
func IntValue(v int) Value {
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Value creates an INT64 Value.
|
||||||
|
func Int64Value(v int64) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: INT64,
|
||||||
|
numeric: int64ToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Value creates a FLOAT64 Value.
|
||||||
|
func Float64Value(v float64) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: FLOAT64,
|
||||||
|
numeric: float64ToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringValue creates a STRING Value.
|
||||||
|
func StringValue(v string) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: STRING,
|
||||||
|
stringly: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a type of the Value.
|
||||||
|
func (v Value) Type() Type {
|
||||||
|
return v.vtype
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsBool returns the bool value. Make sure that the Value's type is
|
||||||
|
// BOOL.
|
||||||
|
func (v Value) AsBool() bool {
|
||||||
|
return rawToBool(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64 returns the int64 value. Make sure that the Value's type is
|
||||||
|
// INT64.
|
||||||
|
func (v Value) AsInt64() int64 {
|
||||||
|
return rawToInt64(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64 returns the float64 value. Make sure that the Value's
|
||||||
|
// type is FLOAT64.
|
||||||
|
func (v Value) AsFloat64() float64 {
|
||||||
|
return rawToFloat64(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsString returns the string value. Make sure that the Value's type
|
||||||
|
// is STRING.
|
||||||
|
func (v Value) AsString() string {
|
||||||
|
return v.stringly
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownValueType struct{}
|
||||||
|
|
||||||
|
// AsInterface returns Value's data as interface{}.
|
||||||
|
func (v Value) AsInterface() interface{} {
|
||||||
|
switch v.Type() {
|
||||||
|
case BOOL:
|
||||||
|
return v.AsBool()
|
||||||
|
case INT64:
|
||||||
|
return v.AsInt64()
|
||||||
|
case FLOAT64:
|
||||||
|
return v.AsFloat64()
|
||||||
|
case STRING:
|
||||||
|
return v.stringly
|
||||||
|
}
|
||||||
|
return unknownValueType{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of Value's data.
|
||||||
|
func (v Value) String() string {
|
||||||
|
switch v.Type() {
|
||||||
|
case BOOL:
|
||||||
|
return strconv.FormatBool(v.AsBool())
|
||||||
|
case INT64:
|
||||||
|
return strconv.FormatInt(v.AsInt64(), 10)
|
||||||
|
case FLOAT64:
|
||||||
|
return fmt.Sprint(v.AsFloat64())
|
||||||
|
case STRING:
|
||||||
|
return v.stringly
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of the Value.
|
||||||
|
func (v Value) MarshalJSON() ([]byte, error) {
|
||||||
|
var jsonVal struct {
|
||||||
|
Type string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
jsonVal.Type = v.Type().String()
|
||||||
|
jsonVal.Value = v.AsInterface()
|
||||||
|
return json.Marshal(jsonVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
switch t {
|
||||||
|
case BOOL:
|
||||||
|
return "bool"
|
||||||
|
case INT64:
|
||||||
|
return "int64"
|
||||||
|
case FLOAT64:
|
||||||
|
return "float64"
|
||||||
|
case STRING:
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
94
vendor/github.com/getsentry/sentry-go/batch_logger.go
generated
vendored
Normal file
94
vendor/github.com/getsentry/sentry-go/batch_logger.go
generated
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
package sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
batchSize = 100
|
||||||
|
batchTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type BatchLogger struct {
|
||||||
|
client *Client
|
||||||
|
logCh chan Log
|
||||||
|
cancel context.CancelFunc
|
||||||
|
wg sync.WaitGroup
|
||||||
|
startOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBatchLogger(client *Client) *BatchLogger {
|
||||||
|
return &BatchLogger{
|
||||||
|
client: client,
|
||||||
|
logCh: make(chan Log, batchSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BatchLogger) Start() {
|
||||||
|
l.startOnce.Do(func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
l.cancel = cancel
|
||||||
|
l.wg.Add(1)
|
||||||
|
go l.run(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BatchLogger) Flush() {
|
||||||
|
if l.cancel != nil {
|
||||||
|
l.cancel()
|
||||||
|
l.wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BatchLogger) run(ctx context.Context) {
|
||||||
|
defer l.wg.Done()
|
||||||
|
var logs []Log
|
||||||
|
timer := time.NewTimer(batchTimeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case log := <-l.logCh:
|
||||||
|
logs = append(logs, log)
|
||||||
|
if len(logs) >= batchSize {
|
||||||
|
l.processEvent(logs)
|
||||||
|
logs = nil
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
timer.Reset(batchTimeout)
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
if len(logs) > 0 {
|
||||||
|
l.processEvent(logs)
|
||||||
|
logs = nil
|
||||||
|
}
|
||||||
|
timer.Reset(batchTimeout)
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Drain remaining logs from channel
|
||||||
|
drain:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case log := <-l.logCh:
|
||||||
|
logs = append(logs, log)
|
||||||
|
default:
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) > 0 {
|
||||||
|
l.processEvent(logs)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BatchLogger) processEvent(logs []Log) {
|
||||||
|
event := NewEvent()
|
||||||
|
event.Timestamp = time.Now()
|
||||||
|
event.Type = logEvent.Type
|
||||||
|
event.Logs = logs
|
||||||
|
l.client.CaptureEvent(event, nil, nil)
|
||||||
|
}
|
||||||
79
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
79
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
|
|
@ -77,9 +77,9 @@ type usageError struct {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger is an instance of log.Logger that is use to provide debug information about running Sentry Client
|
// DebugLogger is an instance of log.Logger that is used to provide debug information about running Sentry Client
|
||||||
// can be enabled by either using Logger.SetOutput directly or with Debug client option.
|
// can be enabled by either using DebugLogger.SetOutput directly or with Debug client option.
|
||||||
var Logger = log.New(io.Discard, "[Sentry] ", log.LstdFlags)
|
var DebugLogger = log.New(io.Discard, "[Sentry] ", log.LstdFlags)
|
||||||
|
|
||||||
// EventProcessor is a function that processes an event.
|
// EventProcessor is a function that processes an event.
|
||||||
// Event processors are used to change an event before it is sent to Sentry.
|
// Event processors are used to change an event before it is sent to Sentry.
|
||||||
|
|
@ -144,8 +144,11 @@ type ClientOptions struct {
|
||||||
// By default, no such data is sent.
|
// By default, no such data is sent.
|
||||||
SendDefaultPII bool
|
SendDefaultPII bool
|
||||||
// BeforeSend is called before error events are sent to Sentry.
|
// BeforeSend is called before error events are sent to Sentry.
|
||||||
// Use it to mutate the event or return nil to discard the event.
|
// You can use it to mutate the event or return nil to discard it.
|
||||||
BeforeSend func(event *Event, hint *EventHint) *Event
|
BeforeSend func(event *Event, hint *EventHint) *Event
|
||||||
|
// BeforeSendLong is called before log events are sent to Sentry.
|
||||||
|
// You can use it to mutate the log event or return nil to discard it.
|
||||||
|
BeforeSendLog func(event *Log) *Log
|
||||||
// BeforeSendTransaction is called before transaction events are sent to Sentry.
|
// BeforeSendTransaction is called before transaction events are sent to Sentry.
|
||||||
// Use it to mutate the transaction or return nil to discard the transaction.
|
// Use it to mutate the transaction or return nil to discard the transaction.
|
||||||
BeforeSendTransaction func(event *Event, hint *EventHint) *Event
|
BeforeSendTransaction func(event *Event, hint *EventHint) *Event
|
||||||
|
|
@ -223,6 +226,8 @@ type ClientOptions struct {
|
||||||
MaxErrorDepth int
|
MaxErrorDepth int
|
||||||
// Default event tags. These are overridden by tags set on a scope.
|
// Default event tags. These are overridden by tags set on a scope.
|
||||||
Tags map[string]string
|
Tags map[string]string
|
||||||
|
// EnableLogs controls when logs should be emitted.
|
||||||
|
EnableLogs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is the underlying processor that is used by the main API and Hub
|
// Client is the underlying processor that is used by the main API and Hub
|
||||||
|
|
@ -238,6 +243,7 @@ type Client struct {
|
||||||
// Transport is read-only. Replacing the transport of an existing client is
|
// Transport is read-only. Replacing the transport of an existing client is
|
||||||
// not supported, create a new client instead.
|
// not supported, create a new client instead.
|
||||||
Transport Transport
|
Transport Transport
|
||||||
|
batchLogger *BatchLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates and returns an instance of Client configured using
|
// NewClient creates and returns an instance of Client configured using
|
||||||
|
|
@ -275,7 +281,7 @@ func NewClient(options ClientOptions) (*Client, error) {
|
||||||
if debugWriter == nil {
|
if debugWriter == nil {
|
||||||
debugWriter = os.Stderr
|
debugWriter = os.Stderr
|
||||||
}
|
}
|
||||||
Logger.SetOutput(debugWriter)
|
DebugLogger.SetOutput(debugWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Dsn == "" {
|
if options.Dsn == "" {
|
||||||
|
|
@ -337,6 +343,11 @@ func NewClient(options ClientOptions) (*Client, error) {
|
||||||
sdkVersion: SDKVersion,
|
sdkVersion: SDKVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.EnableLogs {
|
||||||
|
client.batchLogger = NewBatchLogger(&client)
|
||||||
|
client.batchLogger.Start()
|
||||||
|
}
|
||||||
|
|
||||||
client.setupTransport()
|
client.setupTransport()
|
||||||
client.setupIntegrations()
|
client.setupIntegrations()
|
||||||
|
|
||||||
|
|
@ -351,15 +362,7 @@ func (client *Client) setupTransport() {
|
||||||
if opts.Dsn == "" {
|
if opts.Dsn == "" {
|
||||||
transport = new(noopTransport)
|
transport = new(noopTransport)
|
||||||
} else {
|
} else {
|
||||||
httpTransport := NewHTTPTransport()
|
transport = NewHTTPTransport()
|
||||||
// When tracing is enabled, use larger buffer to
|
|
||||||
// accommodate more concurrent events.
|
|
||||||
// TODO(tracing): consider using separate buffers per
|
|
||||||
// event type.
|
|
||||||
if opts.EnableTracing {
|
|
||||||
httpTransport.BufferSize = 1000
|
|
||||||
}
|
|
||||||
transport = httpTransport
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,12 +386,12 @@ func (client *Client) setupIntegrations() {
|
||||||
|
|
||||||
for _, integration := range integrations {
|
for _, integration := range integrations {
|
||||||
if client.integrationAlreadyInstalled(integration.Name()) {
|
if client.integrationAlreadyInstalled(integration.Name()) {
|
||||||
Logger.Printf("Integration %s is already installed\n", integration.Name())
|
DebugLogger.Printf("Integration %s is already installed\n", integration.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
client.integrations = append(client.integrations, integration)
|
client.integrations = append(client.integrations, integration)
|
||||||
integration.SetupOnce(client)
|
integration.SetupOnce(client)
|
||||||
Logger.Printf("Integration installed: %s\n", integration.Name())
|
DebugLogger.Printf("Integration installed: %s\n", integration.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(client.integrations, func(i, j int) bool {
|
sort.Slice(client.integrations, func(i, j int) bool {
|
||||||
|
|
@ -507,9 +510,31 @@ func (client *Client) RecoverWithContext(
|
||||||
// the network synchronously, configure it to use the HTTPSyncTransport in the
|
// the network synchronously, configure it to use the HTTPSyncTransport in the
|
||||||
// call to Init.
|
// call to Init.
|
||||||
func (client *Client) Flush(timeout time.Duration) bool {
|
func (client *Client) Flush(timeout time.Duration) bool {
|
||||||
|
if client.batchLogger != nil {
|
||||||
|
client.batchLogger.Flush()
|
||||||
|
}
|
||||||
return client.Transport.Flush(timeout)
|
return client.Transport.Flush(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushWithContext waits until the underlying Transport sends any buffered events
|
||||||
|
// to the Sentry server, blocking for at most the duration specified by the context.
|
||||||
|
// It returns false if the context is canceled before the events are sent. In such a case,
|
||||||
|
// some events may not be delivered.
|
||||||
|
//
|
||||||
|
// FlushWithContext should be called before terminating the program to ensure no
|
||||||
|
// events are unintentionally dropped.
|
||||||
|
//
|
||||||
|
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
|
||||||
|
// CaptureException, or CaptureMessage. To send events synchronously over the network,
|
||||||
|
// configure the SDK to use HTTPSyncTransport during initialization with Init.
|
||||||
|
|
||||||
|
func (client *Client) FlushWithContext(ctx context.Context) bool {
|
||||||
|
if client.batchLogger != nil {
|
||||||
|
client.batchLogger.Flush()
|
||||||
|
}
|
||||||
|
return client.Transport.FlushWithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Close clean up underlying Transport resources.
|
// Close clean up underlying Transport resources.
|
||||||
//
|
//
|
||||||
// Close should be called after Flush and before terminating the program
|
// Close should be called after Flush and before terminating the program
|
||||||
|
|
@ -605,7 +630,7 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||||
// options.TracesSampler when they are started. Other events
|
// options.TracesSampler when they are started. Other events
|
||||||
// (errors, messages) are sampled here. Does not apply to check-ins.
|
// (errors, messages) are sampled here. Does not apply to check-ins.
|
||||||
if event.Type != transactionType && event.Type != checkInType && !sample(client.options.SampleRate) {
|
if event.Type != transactionType && event.Type != checkInType && !sample(client.options.SampleRate) {
|
||||||
Logger.Println("Event dropped due to SampleRate hit.")
|
DebugLogger.Println("Event dropped due to SampleRate hit.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -617,19 +642,23 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||||
if hint == nil {
|
if hint == nil {
|
||||||
hint = &EventHint{}
|
hint = &EventHint{}
|
||||||
}
|
}
|
||||||
if event.Type == transactionType && client.options.BeforeSendTransaction != nil {
|
switch event.Type {
|
||||||
// Transaction events
|
case transactionType:
|
||||||
|
if client.options.BeforeSendTransaction != nil {
|
||||||
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
|
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
|
||||||
Logger.Println("Transaction dropped due to BeforeSendTransaction callback.")
|
DebugLogger.Println("Transaction dropped due to BeforeSendTransaction callback.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else if event.Type != transactionType && event.Type != checkInType && client.options.BeforeSend != nil {
|
}
|
||||||
// All other events
|
case checkInType: // not a default case, since we shouldn't apply BeforeSend on check-in events
|
||||||
|
default:
|
||||||
|
if client.options.BeforeSend != nil {
|
||||||
if event = client.options.BeforeSend(event, hint); event == nil {
|
if event = client.options.BeforeSend(event, hint); event == nil {
|
||||||
Logger.Println("Event dropped due to BeforeSend callback.")
|
DebugLogger.Println("Event dropped due to BeforeSend callback.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client.Transport.SendEvent(event)
|
client.Transport.SendEvent(event)
|
||||||
|
|
||||||
|
|
@ -692,7 +721,7 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||||
id := event.EventID
|
id := event.EventID
|
||||||
event = processor(event, hint)
|
event = processor(event, hint)
|
||||||
if event == nil {
|
if event == nil {
|
||||||
Logger.Printf("Event dropped by one of the Client EventProcessors: %s\n", id)
|
DebugLogger.Printf("Event dropped by one of the Client EventProcessors: %s\n", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -701,7 +730,7 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||||
id := event.EventID
|
id := event.EventID
|
||||||
event = processor(event, hint)
|
event = processor(event, hint)
|
||||||
if event == nil {
|
if event == nil {
|
||||||
Logger.Printf("Event dropped by one of the Global EventProcessors: %s\n", id)
|
DebugLogger.Printf("Event dropped by one of the Global EventProcessors: %s\n", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
vendor/github.com/getsentry/sentry-go/hub.go
generated
vendored
24
vendor/github.com/getsentry/sentry-go/hub.go
generated
vendored
|
|
@ -308,7 +308,7 @@ func (hub *Hub) AddBreadcrumb(breadcrumb *Breadcrumb, hint *BreadcrumbHint) {
|
||||||
hint = &BreadcrumbHint{}
|
hint = &BreadcrumbHint{}
|
||||||
}
|
}
|
||||||
if breadcrumb = client.options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil {
|
if breadcrumb = client.options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil {
|
||||||
Logger.Println("breadcrumb dropped due to BeforeBreadcrumb callback.")
|
DebugLogger.Println("breadcrumb dropped due to BeforeBreadcrumb callback.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +365,28 @@ func (hub *Hub) Flush(timeout time.Duration) bool {
|
||||||
return client.Flush(timeout)
|
return client.Flush(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushWithContext waits until the underlying Transport sends any buffered events
|
||||||
|
// to the Sentry server, blocking for at most the duration specified by the context.
|
||||||
|
// It returns false if the context is canceled before the events are sent. In such a case,
|
||||||
|
// some events may not be delivered.
|
||||||
|
//
|
||||||
|
// FlushWithContext should be called before terminating the program to ensure no
|
||||||
|
// events are unintentionally dropped.
|
||||||
|
//
|
||||||
|
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
|
||||||
|
// CaptureException, or CaptureMessage. To send events synchronously over the network,
|
||||||
|
// configure the SDK to use HTTPSyncTransport during initialization with Init.
|
||||||
|
|
||||||
|
func (hub *Hub) FlushWithContext(ctx context.Context) bool {
|
||||||
|
client := hub.Client()
|
||||||
|
|
||||||
|
if client == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.FlushWithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// GetTraceparent returns the current Sentry traceparent string, to be used as a HTTP header value
|
// GetTraceparent returns the current Sentry traceparent string, to be used as a HTTP header value
|
||||||
// or HTML meta tag value.
|
// or HTML meta tag value.
|
||||||
// This function is context aware, as in it either returns the traceparent based
|
// This function is context aware, as in it either returns the traceparent based
|
||||||
|
|
|
||||||
6
vendor/github.com/getsentry/sentry-go/integrations.go
generated
vendored
6
vendor/github.com/getsentry/sentry-go/integrations.go
generated
vendored
|
|
@ -32,7 +32,7 @@ func (mi *modulesIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||||
mi.once.Do(func() {
|
mi.once.Do(func() {
|
||||||
info, ok := debug.ReadBuildInfo()
|
info, ok := debug.ReadBuildInfo()
|
||||||
if !ok {
|
if !ok {
|
||||||
Logger.Print("The Modules integration is not available in binaries built without module support.")
|
DebugLogger.Print("The Modules integration is not available in binaries built without module support.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mi.modules = extractModules(info)
|
mi.modules = extractModules(info)
|
||||||
|
|
@ -141,7 +141,7 @@ func (iei *ignoreErrorsIntegration) processor(event *Event, _ *EventHint) *Event
|
||||||
for _, suspect := range suspects {
|
for _, suspect := range suspects {
|
||||||
for _, pattern := range iei.ignoreErrors {
|
for _, pattern := range iei.ignoreErrors {
|
||||||
if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
|
if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
|
||||||
Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
|
DebugLogger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
|
||||||
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,7 @@ func (iei *ignoreTransactionsIntegration) processor(event *Event, _ *EventHint)
|
||||||
|
|
||||||
for _, pattern := range iei.ignoreTransactions {
|
for _, pattern := range iei.ignoreTransactions {
|
||||||
if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
|
if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
|
||||||
Logger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+
|
DebugLogger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+
|
||||||
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
115
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
115
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
|
|
@ -11,17 +11,22 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go/attribute"
|
||||||
)
|
)
|
||||||
|
|
||||||
// eventType is the type of an error event.
|
|
||||||
const eventType = "event"
|
const eventType = "event"
|
||||||
|
|
||||||
// transactionType is the type of a transaction event.
|
|
||||||
const transactionType = "transaction"
|
const transactionType = "transaction"
|
||||||
|
|
||||||
// checkInType is the type of a check in event.
|
|
||||||
const checkInType = "check_in"
|
const checkInType = "check_in"
|
||||||
|
|
||||||
|
var logEvent = struct {
|
||||||
|
Type string
|
||||||
|
ContentType string
|
||||||
|
}{
|
||||||
|
"log",
|
||||||
|
"application/vnd.sentry.items.log+json",
|
||||||
|
}
|
||||||
|
|
||||||
// Level marks the severity of the event.
|
// Level marks the severity of the event.
|
||||||
type Level string
|
type Level string
|
||||||
|
|
||||||
|
|
@ -34,7 +39,7 @@ const (
|
||||||
LevelFatal Level = "fatal"
|
LevelFatal Level = "fatal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SdkInfo contains all metadata about about the SDK being used.
|
// SdkInfo contains all metadata about the SDK.
|
||||||
type SdkInfo struct {
|
type SdkInfo struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
|
|
@ -96,6 +101,60 @@ func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal((*breadcrumb)(b))
|
return json.Marshal((*breadcrumb)(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
// Write implements the io.Writer interface. Currently, the [sentry.Hub] is
|
||||||
|
// context aware, in order to get the correct trace correlation. Using this
|
||||||
|
// might result in incorrect span association on logs. If you need to use
|
||||||
|
// Write it is recommended to create a NewLogger so that the associated context
|
||||||
|
// is passed correctly.
|
||||||
|
Write(p []byte) (n int, err error)
|
||||||
|
// Trace emits a [LogLevelTrace] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Trace(ctx context.Context, v ...interface{})
|
||||||
|
// Debug emits a [LogLevelDebug] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Debug(ctx context.Context, v ...interface{})
|
||||||
|
// Info emits a [LogLevelInfo] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Info(ctx context.Context, v ...interface{})
|
||||||
|
// Warn emits a [LogLevelWarn] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Warn(ctx context.Context, v ...interface{})
|
||||||
|
// Error emits a [LogLevelError] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Error(ctx context.Context, v ...interface{})
|
||||||
|
// Fatal emits a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Fatal(ctx context.Context, v ...interface{})
|
||||||
|
// Panic emits a [LogLevelFatal] log to Sentry followed by a call to panic().
|
||||||
|
// Arguments are handled in the manner of [fmt.Print].
|
||||||
|
Panic(ctx context.Context, v ...interface{})
|
||||||
|
|
||||||
|
// Tracef emits a [LogLevelTrace] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Tracef(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Debugf emits a [LogLevelDebug] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Debugf(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Infof emits a [LogLevelInfo] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Infof(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Warnf emits a [LogLevelWarn] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Warnf(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Errorf emits a [LogLevelError] log to Sentry.
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Errorf(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Fatalf emits a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Fatalf(ctx context.Context, format string, v ...interface{})
|
||||||
|
// Panicf emits a [LogLevelFatal] log to Sentry followed by a call to panic().
|
||||||
|
// Arguments are handled in the manner of [fmt.Printf].
|
||||||
|
Panicf(ctx context.Context, format string, v ...interface{})
|
||||||
|
// SetAttributes allows attaching parameters to the log message using the attribute API.
|
||||||
|
SetAttributes(...attribute.Builder)
|
||||||
|
}
|
||||||
|
|
||||||
// Attachment allows associating files with your events to aid in investigation.
|
// Attachment allows associating files with your events to aid in investigation.
|
||||||
// An event may contain one or more attachments.
|
// An event may contain one or more attachments.
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
|
|
@ -155,11 +214,36 @@ type Request struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var sensitiveHeaders = map[string]struct{}{
|
var sensitiveHeaders = map[string]struct{}{
|
||||||
|
"_csrf": {},
|
||||||
|
"_csrf_token": {},
|
||||||
|
"_session": {},
|
||||||
|
"_xsrf": {},
|
||||||
|
"Api-Key": {},
|
||||||
|
"Apikey": {},
|
||||||
|
"Auth": {},
|
||||||
"Authorization": {},
|
"Authorization": {},
|
||||||
"Proxy-Authorization": {},
|
|
||||||
"Cookie": {},
|
"Cookie": {},
|
||||||
|
"Credentials": {},
|
||||||
|
"Csrf": {},
|
||||||
|
"Csrf-Token": {},
|
||||||
|
"Csrftoken": {},
|
||||||
|
"Ip-Address": {},
|
||||||
|
"Passwd": {},
|
||||||
|
"Password": {},
|
||||||
|
"Private-Key": {},
|
||||||
|
"Privatekey": {},
|
||||||
|
"Proxy-Authorization": {},
|
||||||
|
"Remote-Addr": {},
|
||||||
|
"Secret": {},
|
||||||
|
"Session": {},
|
||||||
|
"Sessionid": {},
|
||||||
|
"Token": {},
|
||||||
|
"User-Session": {},
|
||||||
|
"X-Api-Key": {},
|
||||||
|
"X-Csrftoken": {},
|
||||||
"X-Forwarded-For": {},
|
"X-Forwarded-For": {},
|
||||||
"X-Real-Ip": {},
|
"X-Real-Ip": {},
|
||||||
|
"XSRF-TOKEN": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest returns a new Sentry Request from the given http.Request.
|
// NewRequest returns a new Sentry Request from the given http.Request.
|
||||||
|
|
@ -325,6 +409,9 @@ type Event struct {
|
||||||
CheckIn *CheckIn `json:"check_in,omitempty"`
|
CheckIn *CheckIn `json:"check_in,omitempty"`
|
||||||
MonitorConfig *MonitorConfig `json:"monitor_config,omitempty"`
|
MonitorConfig *MonitorConfig `json:"monitor_config,omitempty"`
|
||||||
|
|
||||||
|
// The fields below are only relevant for logs
|
||||||
|
Logs []Log `json:"items,omitempty"`
|
||||||
|
|
||||||
// The fields below are not part of the final JSON payload.
|
// The fields below are not part of the final JSON payload.
|
||||||
|
|
||||||
sdkMetaData SDKMetaData
|
sdkMetaData SDKMetaData
|
||||||
|
|
@ -547,3 +634,17 @@ type EventHint struct {
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Response *http.Response
|
Response *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||||
|
TraceID TraceID `json:"trace_id,omitempty"`
|
||||||
|
Level LogLevel `json:"level"`
|
||||||
|
Severity int `json:"severity_number,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
Attributes map[string]Attribute `json:"attributes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
Value any `json:"value"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
|
||||||
230
vendor/github.com/getsentry/sentry-go/log.go
generated
vendored
Normal file
230
vendor/github.com/getsentry/sentry-go/log.go
generated
vendored
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
package sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogLevel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogLevelTrace LogLevel = "trace"
|
||||||
|
LogLevelDebug LogLevel = "debug"
|
||||||
|
LogLevelInfo LogLevel = "info"
|
||||||
|
LogLevelWarn LogLevel = "warn"
|
||||||
|
LogLevelError LogLevel = "error"
|
||||||
|
LogLevelFatal LogLevel = "fatal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogSeverityTrace int = 1
|
||||||
|
LogSeverityDebug int = 5
|
||||||
|
LogSeverityInfo int = 9
|
||||||
|
LogSeverityWarning int = 13
|
||||||
|
LogSeverityError int = 17
|
||||||
|
LogSeverityFatal int = 21
|
||||||
|
)
|
||||||
|
|
||||||
|
var mapTypesToStr = map[attribute.Type]string{
|
||||||
|
attribute.INVALID: "",
|
||||||
|
attribute.BOOL: "boolean",
|
||||||
|
attribute.INT64: "integer",
|
||||||
|
attribute.FLOAT64: "double",
|
||||||
|
attribute.STRING: "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
type sentryLogger struct {
|
||||||
|
client *Client
|
||||||
|
attributes map[string]Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger returns a Logger that emits logs to Sentry. If logging is turned off, all logs get discarded.
|
||||||
|
func NewLogger(ctx context.Context) Logger {
|
||||||
|
var hub *Hub
|
||||||
|
hub = GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
hub = CurrentHub()
|
||||||
|
}
|
||||||
|
|
||||||
|
client := hub.Client()
|
||||||
|
if client != nil && client.batchLogger != nil {
|
||||||
|
return &sentryLogger{client, make(map[string]Attribute)}
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLogger.Println("fallback to noopLogger: enableLogs disabled")
|
||||||
|
return &noopLogger{} // fallback: does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *sentryLogger) Write(p []byte) (int, error) {
|
||||||
|
// Avoid sending double newlines to Sentry
|
||||||
|
msg := strings.TrimRight(string(p), "\n")
|
||||||
|
l.log(context.Background(), LogLevelInfo, LogSeverityInfo, msg)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *sentryLogger) log(ctx context.Context, level LogLevel, severity int, message string, args ...interface{}) {
|
||||||
|
if message == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hub := GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
hub = CurrentHub()
|
||||||
|
}
|
||||||
|
|
||||||
|
var traceID TraceID
|
||||||
|
var spanID SpanID
|
||||||
|
|
||||||
|
span := hub.Scope().span
|
||||||
|
if span != nil {
|
||||||
|
traceID = span.TraceID
|
||||||
|
spanID = span.SpanID
|
||||||
|
} else {
|
||||||
|
traceID = hub.Scope().propagationContext.TraceID
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := map[string]Attribute{}
|
||||||
|
if len(args) > 0 {
|
||||||
|
attrs["sentry.message.template"] = Attribute{
|
||||||
|
Value: message, Type: "string",
|
||||||
|
}
|
||||||
|
for i, p := range args {
|
||||||
|
attrs[fmt.Sprintf("sentry.message.parameters.%d", i)] = Attribute{
|
||||||
|
Value: fmt.Sprint(p), Type: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `log` was called with SetAttributes, pass the attributes to attrs
|
||||||
|
if len(l.attributes) > 0 {
|
||||||
|
for k, v := range l.attributes {
|
||||||
|
attrs[k] = v
|
||||||
|
}
|
||||||
|
// flush attributes from logger after send
|
||||||
|
clear(l.attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default attributes
|
||||||
|
if release := l.client.options.Release; release != "" {
|
||||||
|
attrs["sentry.release"] = Attribute{Value: release, Type: "string"}
|
||||||
|
}
|
||||||
|
if environment := l.client.options.Environment; environment != "" {
|
||||||
|
attrs["sentry.environment"] = Attribute{Value: environment, Type: "string"}
|
||||||
|
}
|
||||||
|
if serverName := l.client.options.ServerName; serverName != "" {
|
||||||
|
attrs["sentry.server.address"] = Attribute{Value: serverName, Type: "string"}
|
||||||
|
} else if serverAddr, err := os.Hostname(); err == nil {
|
||||||
|
attrs["sentry.server.address"] = Attribute{Value: serverAddr, Type: "string"}
|
||||||
|
}
|
||||||
|
scope := hub.Scope()
|
||||||
|
if scope != nil {
|
||||||
|
user := scope.user
|
||||||
|
if !user.IsEmpty() {
|
||||||
|
if user.ID != "" {
|
||||||
|
attrs["user.id"] = Attribute{Value: user.ID, Type: "string"}
|
||||||
|
}
|
||||||
|
if user.Name != "" {
|
||||||
|
attrs["user.name"] = Attribute{Value: user.Name, Type: "string"}
|
||||||
|
}
|
||||||
|
if user.Email != "" {
|
||||||
|
attrs["user.email"] = Attribute{Value: user.Email, Type: "string"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if spanID.String() != "0000000000000000" {
|
||||||
|
attrs["sentry.trace.parent_span_id"] = Attribute{Value: spanID.String(), Type: "string"}
|
||||||
|
}
|
||||||
|
if sdkIdentifier := l.client.sdkIdentifier; sdkIdentifier != "" {
|
||||||
|
attrs["sentry.sdk.name"] = Attribute{Value: sdkIdentifier, Type: "string"}
|
||||||
|
}
|
||||||
|
if sdkVersion := l.client.sdkVersion; sdkVersion != "" {
|
||||||
|
attrs["sentry.sdk.version"] = Attribute{Value: sdkVersion, Type: "string"}
|
||||||
|
}
|
||||||
|
|
||||||
|
log := &Log{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
TraceID: traceID,
|
||||||
|
Level: level,
|
||||||
|
Severity: severity,
|
||||||
|
Body: fmt.Sprintf(message, args...),
|
||||||
|
Attributes: attrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.client.options.BeforeSendLog != nil {
|
||||||
|
log = l.client.options.BeforeSendLog(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
l.client.batchLogger.logCh <- *log
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.client.options.Debug {
|
||||||
|
DebugLogger.Printf(message, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *sentryLogger) SetAttributes(attrs ...attribute.Builder) {
|
||||||
|
for _, v := range attrs {
|
||||||
|
t, ok := mapTypesToStr[v.Value.Type()]
|
||||||
|
if !ok || t == "" {
|
||||||
|
DebugLogger.Printf("invalid attribute type set: %v", t)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
l.attributes[v.Key] = Attribute{
|
||||||
|
Value: v.Value.AsInterface(),
|
||||||
|
Type: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *sentryLogger) Trace(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelTrace, LogSeverityTrace, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Debug(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelDebug, LogSeverityDebug, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Info(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelInfo, LogSeverityInfo, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Warn(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelWarn, LogSeverityWarning, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Error(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelError, LogSeverityError, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Fatal(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelFatal, LogSeverityFatal, fmt.Sprint(v...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Panic(ctx context.Context, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelFatal, LogSeverityFatal, fmt.Sprint(v...))
|
||||||
|
panic(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Tracef(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelTrace, LogSeverityTrace, format, v...)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Debugf(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelDebug, LogSeverityDebug, format, v...)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Infof(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelInfo, LogSeverityInfo, format, v...)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Warnf(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelWarn, LogSeverityWarning, format, v...)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Errorf(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelError, LogSeverityError, format, v...)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Fatalf(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelFatal, LogSeverityFatal, format, v...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
func (l *sentryLogger) Panicf(ctx context.Context, format string, v ...interface{}) {
|
||||||
|
l.log(ctx, LogLevelFatal, LogSeverityFatal, format, v...)
|
||||||
|
panic(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
65
vendor/github.com/getsentry/sentry-go/log_fallback.go
generated
vendored
Normal file
65
vendor/github.com/getsentry/sentry-go/log_fallback.go
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fallback, no-op logger if logging is disabled.
|
||||||
|
type noopLogger struct{}
|
||||||
|
|
||||||
|
func (*noopLogger) Trace(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelTrace)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Debug(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelDebug)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Info(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Warn(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelWarn)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Error(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelError)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Fatal(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Panic(_ context.Context, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||||
|
panic(fmt.Sprintf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal))
|
||||||
|
}
|
||||||
|
func (*noopLogger) Tracef(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelTrace)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Debugf(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelDebug)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Infof(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Warnf(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelWarn)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Errorf(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelError)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Fatalf(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
func (*noopLogger) Panicf(_ context.Context, _ string, _ ...interface{}) {
|
||||||
|
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||||
|
panic(fmt.Sprintf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal))
|
||||||
|
}
|
||||||
|
func (*noopLogger) SetAttributes(...attribute.Builder) {
|
||||||
|
DebugLogger.Printf("No attributes attached. Turn on logging via EnableLogs")
|
||||||
|
}
|
||||||
|
func (*noopLogger) Write(_ []byte) (n int, err error) {
|
||||||
|
return 0, fmt.Errorf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||||
|
}
|
||||||
2
vendor/github.com/getsentry/sentry-go/mocks.go
generated
vendored
2
vendor/github.com/getsentry/sentry-go/mocks.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
package sentry
|
package sentry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -39,6 +40,7 @@ func (t *MockTransport) SendEvent(event *Event) {
|
||||||
func (t *MockTransport) Flush(_ time.Duration) bool {
|
func (t *MockTransport) Flush(_ time.Duration) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
func (t *MockTransport) FlushWithContext(_ context.Context) bool { return true }
|
||||||
func (t *MockTransport) Events() []*Event {
|
func (t *MockTransport) Events() []*Event {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
|
|
||||||
7
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
7
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
|
|
@ -302,6 +302,11 @@ func (scope *Scope) SetPropagationContext(propagationContext PropagationContext)
|
||||||
scope.propagationContext = propagationContext
|
scope.propagationContext = propagationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSpan returns the span from the current scope.
|
||||||
|
func (scope *Scope) GetSpan() *Span {
|
||||||
|
return scope.span
|
||||||
|
}
|
||||||
|
|
||||||
// SetSpan sets a span for the current scope.
|
// SetSpan sets a span for the current scope.
|
||||||
func (scope *Scope) SetSpan(span *Span) {
|
func (scope *Scope) SetSpan(span *Span) {
|
||||||
scope.mu.Lock()
|
scope.mu.Lock()
|
||||||
|
|
@ -464,7 +469,7 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client *Client)
|
||||||
id := event.EventID
|
id := event.EventID
|
||||||
event = processor(event, hint)
|
event = processor(event, hint)
|
||||||
if event == nil {
|
if event == nil {
|
||||||
Logger.Printf("Event dropped by one of the Scope EventProcessors: %s\n", id)
|
DebugLogger.Printf("Event dropped by one of the Scope EventProcessors: %s\n", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
19
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// The version of the SDK.
|
// The version of the SDK.
|
||||||
const SDKVersion = "0.32.0"
|
const SDKVersion = "0.34.0"
|
||||||
|
|
||||||
// apiVersion is the minimum version of the Sentry API compatible with the
|
// apiVersion is the minimum version of the Sentry API compatible with the
|
||||||
// sentry-go SDK.
|
// sentry-go SDK.
|
||||||
|
|
@ -125,6 +125,23 @@ func Flush(timeout time.Duration) bool {
|
||||||
return hub.Flush(timeout)
|
return hub.Flush(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushWithContext waits until the underlying Transport sends any buffered events
|
||||||
|
// to the Sentry server, blocking for at most the duration specified by the context.
|
||||||
|
// It returns false if the context is canceled before the events are sent. In such a case,
|
||||||
|
// some events may not be delivered.
|
||||||
|
//
|
||||||
|
// FlushWithContext should be called before terminating the program to ensure no
|
||||||
|
// events are unintentionally dropped.
|
||||||
|
//
|
||||||
|
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
|
||||||
|
// CaptureException, or CaptureMessage. To send events synchronously over the network,
|
||||||
|
// configure the SDK to use HTTPSyncTransport during initialization with Init.
|
||||||
|
|
||||||
|
func FlushWithContext(ctx context.Context) bool {
|
||||||
|
hub := CurrentHub()
|
||||||
|
return hub.FlushWithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// LastEventID returns an ID of last captured event.
|
// LastEventID returns an ID of last captured event.
|
||||||
func LastEventID() EventID {
|
func LastEventID() EventID {
|
||||||
hub := CurrentHub()
|
hub := CurrentHub()
|
||||||
|
|
|
||||||
2
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
2
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
|
|
@ -24,7 +24,7 @@ func (r *spanRecorder) record(s *Span) {
|
||||||
if len(r.spans) >= maxSpans {
|
if len(r.spans) >= maxSpans {
|
||||||
r.overflowOnce.Do(func() {
|
r.overflowOnce.Do(func() {
|
||||||
root := r.spans[0]
|
root := r.spans[0]
|
||||||
Logger.Printf("Too many spans: dropping spans from transaction with TraceID=%s SpanID=%s limit=%d",
|
DebugLogger.Printf("Too many spans: dropping spans from transaction with TraceID=%s SpanID=%s limit=%d",
|
||||||
root.TraceID, root.SpanID, maxSpans)
|
root.TraceID, root.SpanID, maxSpans)
|
||||||
})
|
})
|
||||||
// TODO(tracing): mark the transaction event in some way to
|
// TODO(tracing): mark the transaction event in some way to
|
||||||
|
|
|
||||||
65
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
65
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -72,6 +73,8 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||||
contexts map[string]Context
|
contexts map[string]Context
|
||||||
// a Once instance to make sure that Finish() is only called once.
|
// a Once instance to make sure that Finish() is only called once.
|
||||||
finishOnce sync.Once
|
finishOnce sync.Once
|
||||||
|
// explicitSampled is a flag for configuring sampling by using `WithSpanSampled` option.
|
||||||
|
explicitSampled Sampled
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceParentContext describes the context of a (remote) parent span.
|
// TraceParentContext describes the context of a (remote) parent span.
|
||||||
|
|
@ -195,10 +198,6 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||||
clientOptions := span.clientOptions()
|
clientOptions := span.clientOptions()
|
||||||
if clientOptions.EnableTracing {
|
if clientOptions.EnableTracing {
|
||||||
hub := hubFromContext(ctx)
|
hub := hubFromContext(ctx)
|
||||||
if !span.IsTransaction() {
|
|
||||||
// Push a new scope to stack for non transaction span
|
|
||||||
hub.PushScope()
|
|
||||||
}
|
|
||||||
hub.Scope().SetSpan(&span)
|
hub.Scope().SetSpan(&span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,7 +301,7 @@ func (s *Span) GetTransaction() *Span {
|
||||||
// func (s *Span) TransactionName() string
|
// func (s *Span) TransactionName() string
|
||||||
// func (s *Span) SetTransactionName(name string)
|
// func (s *Span) SetTransactionName(name string)
|
||||||
|
|
||||||
// ToSentryTrace returns the seralized TraceParentContext from a transaction/span.
|
// ToSentryTrace returns the serialized TraceParentContext from a transaction/span.
|
||||||
// Use this function to propagate the TraceParentContext to a downstream SDK,
|
// Use this function to propagate the TraceParentContext to a downstream SDK,
|
||||||
// either as the value of the "sentry-trace" HTTP header, or as an html "sentry-trace" meta tag.
|
// either as the value of the "sentry-trace" HTTP header, or as an html "sentry-trace" meta tag.
|
||||||
func (s *Span) ToSentryTrace() string {
|
func (s *Span) ToSentryTrace() string {
|
||||||
|
|
@ -356,8 +355,9 @@ func (s *Span) doFinish() {
|
||||||
|
|
||||||
hub := hubFromContext(s.ctx)
|
hub := hubFromContext(s.ctx)
|
||||||
if !s.IsTransaction() {
|
if !s.IsTransaction() {
|
||||||
// Referenced to StartSpan function that pushes current non-transaction span to scope stack
|
if s.parent != nil {
|
||||||
defer hub.PopScope()
|
hub.Scope().SetSpan(s.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.Sampled.Bool() {
|
if !s.Sampled.Bool() {
|
||||||
|
|
@ -449,21 +449,21 @@ func (s *Span) sample() Sampled {
|
||||||
// https://develop.sentry.dev/sdk/performance/#sampling
|
// https://develop.sentry.dev/sdk/performance/#sampling
|
||||||
// #1 tracing is not enabled.
|
// #1 tracing is not enabled.
|
||||||
if !clientOptions.EnableTracing {
|
if !clientOptions.EnableTracing {
|
||||||
Logger.Printf("Dropping transaction: EnableTracing is set to %t", clientOptions.EnableTracing)
|
DebugLogger.Printf("Dropping transaction: EnableTracing is set to %t", clientOptions.EnableTracing)
|
||||||
s.sampleRate = 0.0
|
s.sampleRate = 0.0
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
// #2 explicit sampling decision via StartSpan/StartTransaction options.
|
// #2 explicit sampling decision via StartSpan/StartTransaction options.
|
||||||
if s.Sampled != SampledUndefined {
|
if s.explicitSampled != SampledUndefined {
|
||||||
Logger.Printf("Using explicit sampling decision from StartSpan/StartTransaction: %v", s.Sampled)
|
DebugLogger.Printf("Using explicit sampling decision from StartSpan/StartTransaction: %v", s.explicitSampled)
|
||||||
switch s.Sampled {
|
switch s.explicitSampled {
|
||||||
case SampledTrue:
|
case SampledTrue:
|
||||||
s.sampleRate = 1.0
|
s.sampleRate = 1.0
|
||||||
case SampledFalse:
|
case SampledFalse:
|
||||||
s.sampleRate = 0.0
|
s.sampleRate = 0.0
|
||||||
}
|
}
|
||||||
return s.Sampled
|
return s.explicitSampled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variant for non-transaction spans: they inherit the parent decision.
|
// Variant for non-transaction spans: they inherit the parent decision.
|
||||||
|
|
@ -484,42 +484,52 @@ func (s *Span) sample() Sampled {
|
||||||
if sampler != nil {
|
if sampler != nil {
|
||||||
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
||||||
s.sampleRate = tracesSamplerSampleRate
|
s.sampleRate = tracesSamplerSampleRate
|
||||||
|
// tracesSampler can update the sample_rate on frozen DSC
|
||||||
|
if s.dynamicSamplingContext.HasEntries() {
|
||||||
|
s.dynamicSamplingContext.Entries["sample_rate"] = strconv.FormatFloat(tracesSamplerSampleRate, 'f', -1, 64)
|
||||||
|
}
|
||||||
if tracesSamplerSampleRate < 0.0 || tracesSamplerSampleRate > 1.0 {
|
if tracesSamplerSampleRate < 0.0 || tracesSamplerSampleRate > 1.0 {
|
||||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is out of range [0.0, 1.0]: %f", tracesSamplerSampleRate)
|
DebugLogger.Printf("Dropping transaction: Returned TracesSampler rate is out of range [0.0, 1.0]: %f", tracesSamplerSampleRate)
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
if tracesSamplerSampleRate == 0 {
|
if tracesSamplerSampleRate == 0.0 {
|
||||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is: %f", tracesSamplerSampleRate)
|
DebugLogger.Printf("Dropping transaction: Returned TracesSampler rate is: %f", tracesSamplerSampleRate)
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
if rng.Float64() < tracesSamplerSampleRate {
|
if rng.Float64() < tracesSamplerSampleRate {
|
||||||
return SampledTrue
|
return SampledTrue
|
||||||
}
|
}
|
||||||
Logger.Printf("Dropping transaction: TracesSampler returned rate: %f", tracesSamplerSampleRate)
|
DebugLogger.Printf("Dropping transaction: TracesSampler returned rate: %f", tracesSamplerSampleRate)
|
||||||
|
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
// #4 inherit parent decision.
|
// #4 inherit parent decision.
|
||||||
if s.parent != nil {
|
if s.Sampled != SampledUndefined {
|
||||||
Logger.Printf("Using sampling decision from parent: %v", s.parent.Sampled)
|
DebugLogger.Printf("Using sampling decision from parent: %v", s.Sampled)
|
||||||
switch s.parent.Sampled {
|
switch s.Sampled {
|
||||||
case SampledTrue:
|
case SampledTrue:
|
||||||
s.sampleRate = 1.0
|
s.sampleRate = 1.0
|
||||||
case SampledFalse:
|
case SampledFalse:
|
||||||
s.sampleRate = 0.0
|
s.sampleRate = 0.0
|
||||||
}
|
}
|
||||||
return s.parent.Sampled
|
return s.Sampled
|
||||||
}
|
}
|
||||||
|
|
||||||
// #5 use TracesSampleRate from ClientOptions.
|
// #5 use TracesSampleRate from ClientOptions.
|
||||||
sampleRate := clientOptions.TracesSampleRate
|
sampleRate := clientOptions.TracesSampleRate
|
||||||
s.sampleRate = sampleRate
|
s.sampleRate = sampleRate
|
||||||
|
// tracesSampleRate can update the sample_rate on frozen DSC
|
||||||
|
if s.dynamicSamplingContext.HasEntries() {
|
||||||
|
s.dynamicSamplingContext.Entries["sample_rate"] = strconv.FormatFloat(sampleRate, 'f', -1, 64)
|
||||||
|
}
|
||||||
if sampleRate < 0.0 || sampleRate > 1.0 {
|
if sampleRate < 0.0 || sampleRate > 1.0 {
|
||||||
Logger.Printf("Dropping transaction: TracesSamplerRate out of range [0.0, 1.0]: %f", sampleRate)
|
DebugLogger.Printf("Dropping transaction: TracesSampleRate out of range [0.0, 1.0]: %f", sampleRate)
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
if sampleRate == 0.0 {
|
if sampleRate == 0.0 {
|
||||||
Logger.Printf("Dropping transaction: TracesSampleRate rate is: %f", sampleRate)
|
DebugLogger.Printf("Dropping transaction: TracesSampleRate rate is: %f", sampleRate)
|
||||||
return SampledFalse
|
return SampledFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,7 +552,7 @@ func (s *Span) toEvent() *Event {
|
||||||
finished := make([]*Span, 0, len(children))
|
finished := make([]*Span, 0, len(children))
|
||||||
for _, child := range children {
|
for _, child := range children {
|
||||||
if child.EndTime.IsZero() {
|
if child.EndTime.IsZero() {
|
||||||
Logger.Printf("Dropped unfinished span: Op=%q TraceID=%s SpanID=%s", child.Op, child.TraceID, child.SpanID)
|
DebugLogger.Printf("Dropped unfinished span: Op=%q TraceID=%s SpanID=%s", child.Op, child.TraceID, child.SpanID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
finished = append(finished, child)
|
finished = append(finished, child)
|
||||||
|
|
@ -892,7 +902,7 @@ func WithTransactionSource(source TransactionSource) SpanOption {
|
||||||
// WithSpanSampled updates the sampling flag for a given span.
|
// WithSpanSampled updates the sampling flag for a given span.
|
||||||
func WithSpanSampled(sampled Sampled) SpanOption {
|
func WithSpanSampled(sampled Sampled) SpanOption {
|
||||||
return func(s *Span) {
|
return func(s *Span) {
|
||||||
s.Sampled = sampled
|
s.explicitSampled = sampled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -903,7 +913,7 @@ func WithSpanOrigin(origin SpanOrigin) SpanOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue a trace based on traceparent and bagge values.
|
// ContinueTrace continues a trace based on traceparent and baggage values.
|
||||||
// If the SDK is configured with tracing enabled,
|
// If the SDK is configured with tracing enabled,
|
||||||
// this function returns populated SpanOption.
|
// this function returns populated SpanOption.
|
||||||
// In any other cases, it populates the propagation context on the scope.
|
// In any other cases, it populates the propagation context on the scope.
|
||||||
|
|
@ -932,20 +942,21 @@ func ContinueFromHeaders(trace, baggage string) SpanOption {
|
||||||
return func(s *Span) {
|
return func(s *Span) {
|
||||||
if trace != "" {
|
if trace != "" {
|
||||||
s.updateFromSentryTrace([]byte(trace))
|
s.updateFromSentryTrace([]byte(trace))
|
||||||
}
|
|
||||||
if baggage != "" {
|
if baggage != "" {
|
||||||
s.updateFromBaggage([]byte(baggage))
|
s.updateFromBaggage([]byte(baggage))
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case a sentry-trace header is present but there are no sentry-related
|
// In case a sentry-trace header is present but there are no sentry-related
|
||||||
// values in the baggage, create an empty, frozen DynamicSamplingContext.
|
// values in the baggage, create an empty, frozen DynamicSamplingContext.
|
||||||
if trace != "" && !s.dynamicSamplingContext.HasEntries() {
|
if !s.dynamicSamplingContext.HasEntries() {
|
||||||
s.dynamicSamplingContext = DynamicSamplingContext{
|
s.dynamicSamplingContext = DynamicSamplingContext{
|
||||||
Frozen: true,
|
Frozen: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ContinueFromTrace returns a span option that updates the span to continue
|
// ContinueFromTrace returns a span option that updates the span to continue
|
||||||
// an existing TraceID.
|
// an existing TraceID.
|
||||||
|
|
|
||||||
105
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
105
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
|
|
@ -16,8 +16,10 @@ import (
|
||||||
"github.com/getsentry/sentry-go/internal/ratelimit"
|
"github.com/getsentry/sentry-go/internal/ratelimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultBufferSize = 30
|
const (
|
||||||
const defaultTimeout = time.Second * 30
|
defaultBufferSize = 1000
|
||||||
|
defaultTimeout = time.Second * 30
|
||||||
|
)
|
||||||
|
|
||||||
// maxDrainResponseBytes is the maximum number of bytes that transport
|
// maxDrainResponseBytes is the maximum number of bytes that transport
|
||||||
// implementations will read from response bodies when draining them.
|
// implementations will read from response bodies when draining them.
|
||||||
|
|
@ -33,6 +35,7 @@ const maxDrainResponseBytes = 16 << 10
|
||||||
// Transport is used by the Client to deliver events to remote server.
|
// Transport is used by the Client to deliver events to remote server.
|
||||||
type Transport interface {
|
type Transport interface {
|
||||||
Flush(timeout time.Duration) bool
|
Flush(timeout time.Duration) bool
|
||||||
|
FlushWithContext(ctx context.Context) bool
|
||||||
Configure(options ClientOptions)
|
Configure(options ClientOptions)
|
||||||
SendEvent(event *Event)
|
SendEvent(event *Event)
|
||||||
Close()
|
Close()
|
||||||
|
|
@ -84,14 +87,14 @@ func getRequestBodyFromEvent(event *Event) []byte {
|
||||||
}
|
}
|
||||||
body, err = json.Marshal(event)
|
body, err = json.Marshal(event)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Logger.Println(msg)
|
DebugLogger.Println(msg)
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should _only_ happen when Event.Exception[0].Stacktrace.Frames[0].Vars is unserializable
|
// This should _only_ happen when Event.Exception[0].Stacktrace.Frames[0].Vars is unserializable
|
||||||
// Which won't ever happen, as we don't use it now (although it's the part of public interface accepted by Sentry)
|
// Which won't ever happen, as we don't use it now (although it's the part of public interface accepted by Sentry)
|
||||||
// Juuust in case something, somehow goes utterly wrong.
|
// Juuust in case something, somehow goes utterly wrong.
|
||||||
Logger.Println("Event couldn't be marshaled, even with stripped contextual data. Skipping delivery. " +
|
DebugLogger.Println("Event couldn't be marshaled, even with stripped contextual data. Skipping delivery. " +
|
||||||
"Please notify the SDK owners with possibly broken payload.")
|
"Please notify the SDK owners with possibly broken payload.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +147,23 @@ func encodeEnvelopeItem(enc *json.Encoder, itemType string, body json.RawMessage
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeEnvelopeLogs(enc *json.Encoder, itemsLength int, body json.RawMessage) error {
|
||||||
|
err := enc.Encode(
|
||||||
|
struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ItemCount int `json:"item_count"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
}{
|
||||||
|
Type: logEvent.Type,
|
||||||
|
ItemCount: itemsLength,
|
||||||
|
ContentType: logEvent.ContentType,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
err = enc.Encode(body)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
enc := json.NewEncoder(&b)
|
enc := json.NewEncoder(&b)
|
||||||
|
|
@ -180,6 +200,8 @@ func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMes
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case transactionType, checkInType:
|
case transactionType, checkInType:
|
||||||
err = encodeEnvelopeItem(enc, event.Type, body)
|
err = encodeEnvelopeItem(enc, event.Type, body)
|
||||||
|
case logEvent.Type:
|
||||||
|
err = encodeEnvelopeLogs(enc, len(event.Logs), body)
|
||||||
default:
|
default:
|
||||||
err = encodeEnvelopeItem(enc, eventType, body)
|
err = encodeEnvelopeItem(enc, eventType, body)
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +331,7 @@ func NewHTTPTransport() *HTTPTransport {
|
||||||
func (t *HTTPTransport) Configure(options ClientOptions) {
|
func (t *HTTPTransport) Configure(options ClientOptions) {
|
||||||
dsn, err := NewDsn(options.Dsn)
|
dsn, err := NewDsn(options.Dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("%v\n", err)
|
DebugLogger.Printf("%v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.dsn = dsn
|
t.dsn = dsn
|
||||||
|
|
@ -393,7 +415,7 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event)
|
||||||
} else {
|
} else {
|
||||||
eventType = fmt.Sprintf("%s event", event.Level)
|
eventType = fmt.Sprintf("%s event", event.Level)
|
||||||
}
|
}
|
||||||
Logger.Printf(
|
DebugLogger.Printf(
|
||||||
"Sending %s [%s] to %s project: %s",
|
"Sending %s [%s] to %s project: %s",
|
||||||
eventType,
|
eventType,
|
||||||
event.EventID,
|
event.EventID,
|
||||||
|
|
@ -401,7 +423,7 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event)
|
||||||
t.dsn.projectID,
|
t.dsn.projectID,
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
Logger.Println("Event dropped due to transport buffer being full.")
|
DebugLogger.Println("Event dropped due to transport buffer being full.")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.buffer <- b
|
t.buffer <- b
|
||||||
|
|
@ -418,8 +440,19 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event)
|
||||||
// have the SDK send events over the network synchronously, configure it to use
|
// have the SDK send events over the network synchronously, configure it to use
|
||||||
// the HTTPSyncTransport in the call to Init.
|
// the HTTPSyncTransport in the call to Init.
|
||||||
func (t *HTTPTransport) Flush(timeout time.Duration) bool {
|
func (t *HTTPTransport) Flush(timeout time.Duration) bool {
|
||||||
toolate := time.After(timeout)
|
timeoutCh := make(chan struct{})
|
||||||
|
time.AfterFunc(timeout, func() {
|
||||||
|
close(timeoutCh)
|
||||||
|
})
|
||||||
|
return t.flushInternal(timeoutCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushWithContext works like Flush, but it accepts a context.Context instead of a timeout.
|
||||||
|
func (t *HTTPTransport) FlushWithContext(ctx context.Context) bool {
|
||||||
|
return t.flushInternal(ctx.Done())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPTransport) flushInternal(timeout <-chan struct{}) bool {
|
||||||
// Wait until processing the current batch has started or the timeout.
|
// Wait until processing the current batch has started or the timeout.
|
||||||
//
|
//
|
||||||
// We must wait until the worker has seen the current batch, because it is
|
// We must wait until the worker has seen the current batch, because it is
|
||||||
|
|
@ -427,6 +460,7 @@ func (t *HTTPTransport) Flush(timeout time.Duration) bool {
|
||||||
// possible execution flow in which b.done is never closed, and the only way
|
// possible execution flow in which b.done is never closed, and the only way
|
||||||
// out of Flush would be waiting for the timeout, which is undesired.
|
// out of Flush would be waiting for the timeout, which is undesired.
|
||||||
var b batch
|
var b batch
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case b = <-t.buffer:
|
case b = <-t.buffer:
|
||||||
|
|
@ -436,7 +470,7 @@ func (t *HTTPTransport) Flush(timeout time.Duration) bool {
|
||||||
default:
|
default:
|
||||||
t.buffer <- b
|
t.buffer <- b
|
||||||
}
|
}
|
||||||
case <-toolate:
|
case <-timeout:
|
||||||
goto fail
|
goto fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -455,14 +489,14 @@ started:
|
||||||
// Wait until the current batch is done or the timeout.
|
// Wait until the current batch is done or the timeout.
|
||||||
select {
|
select {
|
||||||
case <-b.done:
|
case <-b.done:
|
||||||
Logger.Println("Buffer flushed successfully.")
|
DebugLogger.Println("Buffer flushed successfully.")
|
||||||
return true
|
return true
|
||||||
case <-toolate:
|
case <-timeout:
|
||||||
goto fail
|
goto fail
|
||||||
}
|
}
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
Logger.Println("Buffer flushing reached the timeout.")
|
DebugLogger.Println("Buffer flushing was canceled or timed out.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,15 +534,15 @@ func (t *HTTPTransport) worker() {
|
||||||
|
|
||||||
response, err := t.client.Do(item.request)
|
response, err := t.client.Do(item.request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("There was an issue with sending an event: %v", err)
|
DebugLogger.Printf("There was an issue with sending an event: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
||||||
b, err := io.ReadAll(response.Body)
|
b, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("Error while reading response code: %v", err)
|
DebugLogger.Printf("Error while reading response code: %v", err)
|
||||||
}
|
}
|
||||||
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
|
DebugLogger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
|
|
@ -535,7 +569,7 @@ func (t *HTTPTransport) disabled(c ratelimit.Category) bool {
|
||||||
defer t.mu.RUnlock()
|
defer t.mu.RUnlock()
|
||||||
disabled := t.limits.IsRateLimited(c)
|
disabled := t.limits.IsRateLimited(c)
|
||||||
if disabled {
|
if disabled {
|
||||||
Logger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c))
|
DebugLogger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c))
|
||||||
}
|
}
|
||||||
return disabled
|
return disabled
|
||||||
}
|
}
|
||||||
|
|
@ -581,7 +615,7 @@ func NewHTTPSyncTransport() *HTTPSyncTransport {
|
||||||
func (t *HTTPSyncTransport) Configure(options ClientOptions) {
|
func (t *HTTPSyncTransport) Configure(options ClientOptions) {
|
||||||
dsn, err := NewDsn(options.Dsn)
|
dsn, err := NewDsn(options.Dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("%v\n", err)
|
DebugLogger.Printf("%v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.dsn = dsn
|
t.dsn = dsn
|
||||||
|
|
@ -627,16 +661,18 @@ func (t *HTTPSyncTransport) SendEventWithContext(ctx context.Context, event *Eve
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventType string
|
var eventIdentifier string
|
||||||
switch {
|
switch event.Type {
|
||||||
case event.Type == transactionType:
|
case transactionType:
|
||||||
eventType = "transaction"
|
eventIdentifier = "transaction"
|
||||||
|
case logEvent.Type:
|
||||||
|
eventIdentifier = fmt.Sprintf("%v log events", len(event.Logs))
|
||||||
default:
|
default:
|
||||||
eventType = fmt.Sprintf("%s event", event.Level)
|
eventIdentifier = fmt.Sprintf("%s event", event.Level)
|
||||||
}
|
}
|
||||||
Logger.Printf(
|
DebugLogger.Printf(
|
||||||
"Sending %s [%s] to %s project: %s",
|
"Sending %s [%s] to %s project: %s",
|
||||||
eventType,
|
eventIdentifier,
|
||||||
event.EventID,
|
event.EventID,
|
||||||
t.dsn.host,
|
t.dsn.host,
|
||||||
t.dsn.projectID,
|
t.dsn.projectID,
|
||||||
|
|
@ -644,15 +680,15 @@ func (t *HTTPSyncTransport) SendEventWithContext(ctx context.Context, event *Eve
|
||||||
|
|
||||||
response, err := t.client.Do(request)
|
response, err := t.client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("There was an issue with sending an event: %v", err)
|
DebugLogger.Printf("There was an issue with sending an event: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
||||||
b, err := io.ReadAll(response.Body)
|
b, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Printf("Error while reading response code: %v", err)
|
DebugLogger.Printf("Error while reading response code: %v", err)
|
||||||
}
|
}
|
||||||
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
|
DebugLogger.Printf("Sending %s failed with the following error: %s", eventIdentifier, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
|
|
@ -674,12 +710,17 @@ func (t *HTTPSyncTransport) Flush(_ time.Duration) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushWithContext is a no-op for HTTPSyncTransport. It always returns true immediately.
|
||||||
|
func (t *HTTPSyncTransport) FlushWithContext(_ context.Context) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool {
|
func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
disabled := t.limits.IsRateLimited(c)
|
disabled := t.limits.IsRateLimited(c)
|
||||||
if disabled {
|
if disabled {
|
||||||
Logger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c))
|
DebugLogger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c))
|
||||||
}
|
}
|
||||||
return disabled
|
return disabled
|
||||||
}
|
}
|
||||||
|
|
@ -695,15 +736,19 @@ type noopTransport struct{}
|
||||||
var _ Transport = noopTransport{}
|
var _ Transport = noopTransport{}
|
||||||
|
|
||||||
func (noopTransport) Configure(ClientOptions) {
|
func (noopTransport) Configure(ClientOptions) {
|
||||||
Logger.Println("Sentry client initialized with an empty DSN. Using noopTransport. No events will be delivered.")
|
DebugLogger.Println("Sentry client initialized with an empty DSN. Using noopTransport. No events will be delivered.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (noopTransport) SendEvent(*Event) {
|
func (noopTransport) SendEvent(*Event) {
|
||||||
Logger.Println("Event dropped due to noopTransport usage.")
|
DebugLogger.Println("Event dropped due to noopTransport usage.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (noopTransport) Flush(time.Duration) bool {
|
func (noopTransport) Flush(time.Duration) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (noopTransport) FlushWithContext(context.Context) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (noopTransport) Close() {}
|
func (noopTransport) Close() {}
|
||||||
|
|
|
||||||
14
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
14
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
|
|
@ -36,7 +36,7 @@ func monotonicTimeSince(start time.Time) (end time.Time) {
|
||||||
return start.Add(time.Since(start))
|
return start.Add(time.Since(start))
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: deadcode, unused
|
// nolint: unused
|
||||||
func prettyPrint(data interface{}) {
|
func prettyPrint(data interface{}) {
|
||||||
dbg, _ := json.MarshalIndent(data, "", " ")
|
dbg, _ := json.MarshalIndent(data, "", " ")
|
||||||
fmt.Println(string(dbg))
|
fmt.Println(string(dbg))
|
||||||
|
|
@ -62,7 +62,7 @@ func defaultRelease() (release string) {
|
||||||
}
|
}
|
||||||
for _, e := range envs {
|
for _, e := range envs {
|
||||||
if release = os.Getenv(e); release != "" {
|
if release = os.Getenv(e); release != "" {
|
||||||
Logger.Printf("Using release from environment variable %s: %s", e, release)
|
DebugLogger.Printf("Using release from environment variable %s: %s", e, release)
|
||||||
return release
|
return release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,23 +89,23 @@ func defaultRelease() (release string) {
|
||||||
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
|
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
|
||||||
fmt.Fprintf(&s, ": %s", err.Stderr)
|
fmt.Fprintf(&s, ": %s", err.Stderr)
|
||||||
}
|
}
|
||||||
Logger.Print(s.String())
|
DebugLogger.Print(s.String())
|
||||||
} else {
|
} else {
|
||||||
release = strings.TrimSpace(string(b))
|
release = strings.TrimSpace(string(b))
|
||||||
Logger.Printf("Using release from Git: %s", release)
|
DebugLogger.Printf("Using release from Git: %s", release)
|
||||||
return release
|
return release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Print("Some Sentry features will not be available. See https://docs.sentry.io/product/releases/.")
|
DebugLogger.Print("Some Sentry features will not be available. See https://docs.sentry.io/product/releases/.")
|
||||||
Logger.Print("To stop seeing this message, pass a Release to sentry.Init or set the SENTRY_RELEASE environment variable.")
|
DebugLogger.Print("To stop seeing this message, pass a Release to sentry.Init or set the SENTRY_RELEASE environment variable.")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func revisionFromBuildInfo(info *debug.BuildInfo) string {
|
func revisionFromBuildInfo(info *debug.BuildInfo) string {
|
||||||
for _, setting := range info.Settings {
|
for _, setting := range info.Settings {
|
||||||
if setting.Key == "vcs.revision" && setting.Value != "" {
|
if setting.Key == "vcs.revision" && setting.Value != "" {
|
||||||
Logger.Printf("Using release from debug info: %s", setting.Value)
|
DebugLogger.Printf("Using release from debug info: %s", setting.Value)
|
||||||
return setting.Value
|
return setting.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,5 @@ _testmain.go
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
.direnv
|
||||||
|
.envrc
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
# html2text
|
# html2text
|
||||||
|
|
||||||
[](https://godoc.org/github.com/jaytaylor/html2text)
|
[](https://pkg.go.dev/github.com/inbucket/html2text)
|
||||||
[](https://travis-ci.org/jaytaylor/html2text)
|
[](https://github.com/inbucket/html2text/actions/workflows/build-and-test.yml)
|
||||||
[](https://goreportcard.com/report/github.com/jaytaylor/html2text)
|
[](https://goreportcard.com/report/github.com/inbucket/html2text)
|
||||||
|
|
||||||
### Converts HTML into text of the markdown-flavored variety
|
### Converts HTML into text of the markdown-flavored variety
|
||||||
|
|
||||||
|
This is a permanent fork of the original
|
||||||
|
[jaytaylor.com/html2text](https://github.com/jaytaylor/html2text) package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Ensure your emails are readable by all!
|
Ensure your emails are readable by all!
|
||||||
|
|
@ -21,7 +24,7 @@ It requires go 1.x or newer ;)
|
||||||
## Download the package
|
## Download the package
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get jaytaylor.com/html2text
|
go get github.com/inbucket/html2text
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example usage
|
## Example usage
|
||||||
|
|
@ -34,7 +37,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"jaytaylor.com/html2text"
|
"github.com/inbucket/html2text"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -127,17 +130,3 @@ go test
|
||||||
# License
|
# License
|
||||||
|
|
||||||
Permissive MIT license.
|
Permissive MIT license.
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
You are more than welcome to open issues and send pull requests if you find a bug or want a new feature.
|
|
||||||
|
|
||||||
If you appreciate this library please feel free to drop me a line and tell me! It's always nice to hear from people who have benefitted from my work.
|
|
||||||
|
|
||||||
Email: jay at (my github username).com
|
|
||||||
|
|
||||||
Twitter: [@jtaylor](https://twitter.com/jtaylor)
|
|
||||||
|
|
||||||
# Alternatives
|
|
||||||
|
|
||||||
https://github.com/k3a/html2text - Lightweight
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701680307,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1702272962,
|
"lastModified": 1749369410,
|
||||||
"narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=",
|
"narHash": "sha256-P1eYm8bewiyWg3QaPCHrOp6iWg/7ESi/aGHT4yilyNo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d",
|
"rev": "8207ad0d501dd3590e0e81a7c56b386a5c4342c9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
17
vendor/github.com/inbucket/html2text/flake.nix
generated
vendored
Normal file
17
vendor/github.com/inbucket/html2text/flake.nix
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem
|
||||||
|
(system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShell = pkgs.callPackage ./shell.nix { };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -21,48 +21,6 @@ type Options struct {
|
||||||
TextOnly bool // Returns only plain text
|
TextOnly bool // Returns only plain text
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrettyTablesOptions overrides tablewriter behaviors
|
|
||||||
type PrettyTablesOptions struct {
|
|
||||||
AutoFormatHeader bool
|
|
||||||
AutoWrapText bool
|
|
||||||
ReflowDuringAutoWrap bool
|
|
||||||
ColWidth int
|
|
||||||
ColumnSeparator string
|
|
||||||
RowSeparator string
|
|
||||||
CenterSeparator string
|
|
||||||
HeaderAlignment int
|
|
||||||
FooterAlignment int
|
|
||||||
Alignment int
|
|
||||||
ColumnAlignment []int
|
|
||||||
NewLine string
|
|
||||||
HeaderLine bool
|
|
||||||
RowLine bool
|
|
||||||
AutoMergeCells bool
|
|
||||||
Borders tablewriter.Border
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPrettyTablesOptions creates PrettyTablesOptions with default settings
|
|
||||||
func NewPrettyTablesOptions() *PrettyTablesOptions {
|
|
||||||
return &PrettyTablesOptions{
|
|
||||||
AutoFormatHeader: true,
|
|
||||||
AutoWrapText: true,
|
|
||||||
ReflowDuringAutoWrap: true,
|
|
||||||
ColWidth: tablewriter.MAX_ROW_WIDTH,
|
|
||||||
ColumnSeparator: tablewriter.COLUMN,
|
|
||||||
RowSeparator: tablewriter.ROW,
|
|
||||||
CenterSeparator: tablewriter.CENTER,
|
|
||||||
HeaderAlignment: tablewriter.ALIGN_DEFAULT,
|
|
||||||
FooterAlignment: tablewriter.ALIGN_DEFAULT,
|
|
||||||
Alignment: tablewriter.ALIGN_DEFAULT,
|
|
||||||
ColumnAlignment: []int{},
|
|
||||||
NewLine: tablewriter.NEWLINE,
|
|
||||||
HeaderLine: true,
|
|
||||||
RowLine: false,
|
|
||||||
AutoMergeCells: false,
|
|
||||||
Borders: tablewriter.Border{Left: true, Right: true, Bottom: true, Top: true},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromHTMLNode renders text output from a pre-parsed HTML document.
|
// FromHTMLNode renders text output from a pre-parsed HTML document.
|
||||||
func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
|
func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
|
||||||
var options Options
|
var options Options
|
||||||
|
|
@ -70,6 +28,11 @@ func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
|
||||||
options = o[0]
|
options = o[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.PrettyTables && options.PrettyTablesOptions == nil {
|
||||||
|
// defaults need to make explicit as they are no longer identical with tablewriter
|
||||||
|
options.PrettyTablesOptions = NewPrettyTablesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
ctx := textifyTraverseContext{
|
ctx := textifyTraverseContext{
|
||||||
buf: bytes.Buffer{},
|
buf: bytes.Buffer{},
|
||||||
options: options,
|
options: options,
|
||||||
|
|
@ -333,31 +296,18 @@ func (ctx *textifyTraverseContext) handleTableElement(node *html.Node) error {
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
table := tablewriter.NewWriter(buf)
|
table := tablewriter.NewWriter(buf)
|
||||||
if ctx.options.PrettyTablesOptions != nil {
|
ctx.options.PrettyTablesOptions.configureTable(table)
|
||||||
options := ctx.options.PrettyTablesOptions
|
|
||||||
table.SetAutoFormatHeaders(options.AutoFormatHeader)
|
table.Header(ctx.tableCtx.header)
|
||||||
table.SetAutoWrapText(options.AutoWrapText)
|
table.Footer(ctx.tableCtx.footer)
|
||||||
table.SetReflowDuringAutoWrap(options.ReflowDuringAutoWrap)
|
if err := table.Bulk(ctx.tableCtx.body); err != nil {
|
||||||
table.SetColWidth(options.ColWidth)
|
return err
|
||||||
table.SetColumnSeparator(options.ColumnSeparator)
|
|
||||||
table.SetRowSeparator(options.RowSeparator)
|
|
||||||
table.SetCenterSeparator(options.CenterSeparator)
|
|
||||||
table.SetHeaderAlignment(options.HeaderAlignment)
|
|
||||||
table.SetFooterAlignment(options.FooterAlignment)
|
|
||||||
table.SetAlignment(options.Alignment)
|
|
||||||
table.SetColumnAlignment(options.ColumnAlignment)
|
|
||||||
table.SetNewLine(options.NewLine)
|
|
||||||
table.SetHeaderLine(options.HeaderLine)
|
|
||||||
table.SetRowLine(options.RowLine)
|
|
||||||
table.SetAutoMergeCells(options.AutoMergeCells)
|
|
||||||
table.SetBorders(options.Borders)
|
|
||||||
}
|
}
|
||||||
table.SetHeader(ctx.tableCtx.header)
|
|
||||||
table.SetFooter(ctx.tableCtx.footer)
|
|
||||||
table.AppendBulk(ctx.tableCtx.body)
|
|
||||||
|
|
||||||
// Render the table using ASCII.
|
// Render the table using ASCII.
|
||||||
table.Render()
|
if err := table.Render(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := ctx.emit(buf.String()); err != nil {
|
if err := ctx.emit(buf.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
212
vendor/github.com/inbucket/html2text/prettytables.go
generated
vendored
Normal file
212
vendor/github.com/inbucket/html2text/prettytables.go
generated
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
package html2text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrettyTablesOptions overrides tablewriter behaviors
|
||||||
|
type PrettyTablesOptions struct {
|
||||||
|
AutoFormatHeader bool
|
||||||
|
AutoWrapText bool
|
||||||
|
// Deprecated. Tablewriter always assumes this to be `true`
|
||||||
|
ReflowDuringAutoWrap bool
|
||||||
|
ColWidth int
|
||||||
|
ColumnSeparator string
|
||||||
|
RowSeparator string
|
||||||
|
CenterSeparator string
|
||||||
|
HeaderAlignment tw.Align
|
||||||
|
FooterAlignment tw.Align
|
||||||
|
Alignment tw.Align
|
||||||
|
ColumnAlignment tw.Alignment
|
||||||
|
// Deprecated. Tablewriter always assumes this to be `\n`
|
||||||
|
NewLine string
|
||||||
|
HeaderLine bool
|
||||||
|
RowLine bool
|
||||||
|
AutoMergeCells bool
|
||||||
|
Borders Border
|
||||||
|
// Configuration allows to directly manipulate the `Table` with all what [tablewriter] offers.
|
||||||
|
// Setting this ignores all the rest of the settings of this struct.
|
||||||
|
Configuration func(table *tablewriter.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrettyTablesOptions creates PrettyTablesOptions with default settings
|
||||||
|
func NewPrettyTablesOptions() *PrettyTablesOptions {
|
||||||
|
return &PrettyTablesOptions{
|
||||||
|
AutoFormatHeader: true,
|
||||||
|
AutoWrapText: true,
|
||||||
|
ColWidth: 32, // old tablewriter.MAX_ROW_WIDTH + borders now count into width
|
||||||
|
ColumnSeparator: defaultBorderStyle.ColumnSeparator,
|
||||||
|
RowSeparator: defaultBorderStyle.RowSeparator,
|
||||||
|
CenterSeparator: defaultBorderStyle.CenterSeparator,
|
||||||
|
HeaderAlignment: tw.AlignCenter,
|
||||||
|
FooterAlignment: tw.AlignCenter,
|
||||||
|
Alignment: tw.AlignDefault,
|
||||||
|
ColumnAlignment: make(tw.Alignment, 0),
|
||||||
|
HeaderLine: true,
|
||||||
|
RowLine: false,
|
||||||
|
AutoMergeCells: false,
|
||||||
|
Borders: Border{Left: true, Right: true, Bottom: true, Top: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrettyTablesOptions) configureTable(table *tablewriter.Table) {
|
||||||
|
if p.Configuration != nil {
|
||||||
|
p.Configuration(table)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := tablewriter.NewConfigBuilder()
|
||||||
|
|
||||||
|
cfg.WithHeaderAutoFormat(asState(p.AutoFormatHeader)).WithFooterAutoFormat(asState(p.AutoFormatHeader)).
|
||||||
|
WithRowAutoWrap(p.wrapMode()).WithHeaderAutoWrap(p.wrapMode()).WithFooterAutoWrap(p.wrapMode()).
|
||||||
|
WithRowMaxWidth(p.ColWidth).
|
||||||
|
WithHeaderAlignment(p.HeaderAlignment).
|
||||||
|
WithFooterAlignment(p.FooterAlignment).
|
||||||
|
WithRowAlignment(p.Alignment).
|
||||||
|
WithRowMergeMode(p.mergeMode())
|
||||||
|
|
||||||
|
if len(p.ColumnAlignment) > 0 {
|
||||||
|
cfg.Row().Alignment().WithPerColumn(p.ColumnAlignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
rendition := tw.Rendition{
|
||||||
|
Borders: p.Borders.withStates(),
|
||||||
|
Symbols: p.borderStyle(),
|
||||||
|
Settings: p.renderSettings(),
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Options(
|
||||||
|
tablewriter.WithConfig(cfg.Build()),
|
||||||
|
tablewriter.WithRendition(rendition))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrettyTablesOptions) wrapMode() int {
|
||||||
|
if p.AutoWrapText {
|
||||||
|
return tw.WrapNormal
|
||||||
|
} else {
|
||||||
|
return tw.WrapNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrettyTablesOptions) mergeMode() int {
|
||||||
|
if p.AutoMergeCells {
|
||||||
|
return tw.MergeVertical
|
||||||
|
} else {
|
||||||
|
return tw.MergeNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrettyTablesOptions) renderSettings() tw.Settings {
|
||||||
|
return tw.Settings{
|
||||||
|
Lines: tw.Lines{
|
||||||
|
ShowHeaderLine: asState(p.HeaderLine),
|
||||||
|
},
|
||||||
|
Separators: tw.Separators{
|
||||||
|
BetweenRows: asState(p.RowLine),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border controls tablewriter borders. It uses simple bools instead of tablewriters `State`
|
||||||
|
type Border struct {
|
||||||
|
Left, Right, Bottom, Top bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Border) withStates() tw.Border {
|
||||||
|
return tw.Border{
|
||||||
|
Left: asState(b.Left),
|
||||||
|
Right: asState(b.Right),
|
||||||
|
Bottom: asState(b.Bottom),
|
||||||
|
Top: asState(b.Top),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BorderStyle struct {
|
||||||
|
ColumnSeparator string
|
||||||
|
RowSeparator string
|
||||||
|
CenterSeparator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) Name() string {
|
||||||
|
return "html2text"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) Center() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) Row() string {
|
||||||
|
return b.RowSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) Column() string {
|
||||||
|
return b.ColumnSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) TopLeft() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) TopMid() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) TopRight() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) MidLeft() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) MidRight() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) BottomLeft() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) BottomMid() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) BottomRight() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) HeaderLeft() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) HeaderMid() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BorderStyle) HeaderRight() string {
|
||||||
|
return b.CenterSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBorderStyle = BorderStyle{
|
||||||
|
ColumnSeparator: "|",
|
||||||
|
RowSeparator: "-",
|
||||||
|
CenterSeparator: "+",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrettyTablesOptions) borderStyle() BorderStyle {
|
||||||
|
return BorderStyle{
|
||||||
|
ColumnSeparator: p.ColumnSeparator,
|
||||||
|
RowSeparator: p.RowSeparator,
|
||||||
|
CenterSeparator: p.CenterSeparator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func asState(b bool) tw.State {
|
||||||
|
// TableWriter does not provide this by default :(
|
||||||
|
if b {
|
||||||
|
return tw.On
|
||||||
|
} else {
|
||||||
|
return tw.Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
delve
|
delve
|
||||||
go_1_20
|
go
|
||||||
golint
|
golangci-lint
|
||||||
gopls
|
gopls
|
||||||
];
|
];
|
||||||
|
|
||||||
13
vendor/github.com/jaytaylor/html2text/.travis.yml
generated
vendored
13
vendor/github.com/jaytaylor/html2text/.travis.yml
generated
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
# n.b. For golang release history, see https://golang.org/doc/devel/release.html
|
|
||||||
- tip
|
|
||||||
- "1.13.8"
|
|
||||||
- "1.12.17"
|
|
||||||
- "1.11.13"
|
|
||||||
- "1.10.8"
|
|
||||||
- "1.9.7"
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
on_success: change
|
|
||||||
on_failure: always
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
run:
|
version: "2"
|
||||||
timeout: 5m
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asasalint
|
- asasalint
|
||||||
|
|
@ -8,32 +7,19 @@ linters:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- containedctx
|
- containedctx
|
||||||
- contextcheck
|
- contextcheck
|
||||||
|
- copyloopvar
|
||||||
- decorder
|
- decorder
|
||||||
# - dupl
|
|
||||||
# - dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
# - errorlint
|
|
||||||
- execinquery
|
|
||||||
# - exhaustive
|
|
||||||
- exportloopref
|
|
||||||
# - forcetypeassert
|
|
||||||
- ginkgolinter
|
- ginkgolinter
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
# - gochecknoinits
|
|
||||||
- gochecksumtype
|
- gochecksumtype
|
||||||
- gocritic
|
- gocritic
|
||||||
# - godot
|
|
||||||
# - goerr113
|
|
||||||
- gofmt
|
|
||||||
# - gofumpt
|
|
||||||
- goheader
|
- goheader
|
||||||
- goimports
|
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- gomodguard
|
- gomodguard
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
# - gosec
|
|
||||||
- gosmopolitan
|
- gosmopolitan
|
||||||
- grouper
|
- grouper
|
||||||
- importas
|
- importas
|
||||||
|
|
@ -45,8 +31,6 @@ linters:
|
||||||
- misspell
|
- misspell
|
||||||
- musttag
|
- musttag
|
||||||
- nilerr
|
- nilerr
|
||||||
# - nilnil
|
|
||||||
# - nlreturn
|
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- nosprintfhostport
|
- nosprintfhostport
|
||||||
|
|
@ -56,20 +40,44 @@ linters:
|
||||||
- promlinter
|
- promlinter
|
||||||
- protogetter
|
- protogetter
|
||||||
- reassign
|
- reassign
|
||||||
# - revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sloglint
|
- sloglint
|
||||||
- stylecheck
|
- staticcheck
|
||||||
- tagalign
|
- tagalign
|
||||||
- tagliatelle
|
- tagliatelle
|
||||||
- tenv
|
|
||||||
- testableexamples
|
- testableexamples
|
||||||
- testifylint
|
- testifylint
|
||||||
- thelper
|
- thelper
|
||||||
- tparallel
|
- tparallel
|
||||||
# - unconvert
|
|
||||||
- unparam
|
- unparam
|
||||||
- usestdlibvars
|
- usestdlibvars
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
- zerologlint
|
- zerologlint
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
rules:
|
||||||
|
# Exclude specific linters from test files.
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gosmopolitan
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
@ -27,8 +27,6 @@ simplify:
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@test -z "$(shell gofmt -l . | tee /dev/stderr)" || echo "[WARN] Fix formatting issues with 'make fmt'"
|
@test -z "$(shell gofmt -l . | tee /dev/stderr)" || echo "[WARN] Fix formatting issues with 'make fmt'"
|
||||||
@# TODO: lint disabled until `mediatype.MediaTypeParseOptions` renamed to remove repetition.
|
|
||||||
@#@golint -set_exit_status $(PKGS)
|
|
||||||
@go vet $(PKGS)
|
@go vet $(PKGS)
|
||||||
|
|
||||||
reflex:
|
reflex:
|
||||||
|
|
@ -180,7 +180,7 @@ func (b *boundaryReader) Next() (bool, error) {
|
||||||
_, _ = io.Copy(io.Discard, b)
|
_, _ = io.Copy(io.Discard, b)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
var line []byte = nil
|
var line []byte
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
// Read whole line, handle extra long lines in cycle
|
// Read whole line, handle extra long lines in cycle
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jhillyerd/enmime/internal/stringutil"
|
"github.com/jhillyerd/enmime/v2/internal/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MailBuilder facilitates the easy construction of a MIME message. Each manipulation method
|
// MailBuilder facilitates the easy construction of a MIME message. Each manipulation method
|
||||||
|
|
@ -3,7 +3,7 @@ package enmime
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
inttp "github.com/jhillyerd/enmime/internal/textproto"
|
inttp "github.com/jhillyerd/enmime/v2/internal/textproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// detectMultipartMessage returns true if the message has a recognized multipart Content-Type header
|
// detectMultipartMessage returns true if the message has a recognized multipart Content-Type header
|
||||||
|
|
@ -8,10 +8,11 @@ import (
|
||||||
"mime/quotedprintable"
|
"mime/quotedprintable"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
"github.com/jhillyerd/enmime/internal/stringutil"
|
"github.com/jhillyerd/enmime/v2/internal/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// b64Percent determines the percent of non-ASCII characters enmime will tolerate before switching
|
// b64Percent determines the percent of non-ASCII characters enmime will tolerate before switching
|
||||||
|
|
@ -22,6 +23,7 @@ type transferEncoding byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
te7Bit transferEncoding = iota
|
te7Bit transferEncoding = iota
|
||||||
|
te8Bit
|
||||||
teQuoted
|
teQuoted
|
||||||
teBase64
|
teBase64
|
||||||
teRaw
|
teRaw
|
||||||
|
|
@ -106,21 +108,33 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
|
||||||
|
|
||||||
cte := te7Bit
|
cte := te7Bit
|
||||||
if len(p.Content) > 0 {
|
if len(p.Content) > 0 {
|
||||||
|
if strings.Index(strings.ToLower(p.ContentType), "message/") == 0 {
|
||||||
|
// RFC 1341: `message` types must have no encoding other than "7bit", "8bit", or
|
||||||
|
// "binary". The message header fields are always US-ASCII in any case, and data within
|
||||||
|
// the body can still be encoded, in which case the Content-Transfer-Encoding header
|
||||||
|
// field in the encapsulated message will reflect this.
|
||||||
|
cte = te8Bit
|
||||||
|
} else {
|
||||||
cte = teBase64
|
cte = teBase64
|
||||||
if p.TextContent() && p.ContentReader == nil {
|
if p.TextContent() && p.ContentReader == nil {
|
||||||
cte = selectTransferEncoding(p.Content, false)
|
cte = p.selectTransferEncoding(p.Content, false)
|
||||||
if p.Charset == "" {
|
if p.Charset == "" {
|
||||||
p.Charset = utf8
|
p.Charset = utf8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RFC 2045: 7bit is assumed if CTE header not present.
|
// RFC 2045: 7bit is assumed if CTE header not present.
|
||||||
switch cte {
|
switch cte {
|
||||||
|
case te8Bit:
|
||||||
|
p.Header.Set(hnContentEncoding, cte8Bit)
|
||||||
case teBase64:
|
case teBase64:
|
||||||
p.Header.Set(hnContentEncoding, cteBase64)
|
p.Header.Set(hnContentEncoding, cteBase64)
|
||||||
case teQuoted:
|
case teQuoted:
|
||||||
p.Header.Set(hnContentEncoding, cteQuotedPrintable)
|
p.Header.Set(hnContentEncoding, cteQuotedPrintable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup headers.
|
// Setup headers.
|
||||||
if p.FirstChild != nil && p.Boundary == "" {
|
if p.FirstChild != nil && p.Boundary == "" {
|
||||||
// Multipart, generate random boundary marker.
|
// Multipart, generate random boundary marker.
|
||||||
|
|
@ -130,12 +144,13 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
|
||||||
p.Header.Set(hnContentID, coding.ToIDHeader(p.ContentID))
|
p.Header.Set(hnContentID, coding.ToIDHeader(p.ContentID))
|
||||||
}
|
}
|
||||||
fileName := p.FileName
|
fileName := p.FileName
|
||||||
switch selectTransferEncoding([]byte(p.FileName), true) {
|
switch p.selectTransferEncoding([]byte(p.FileName), true) {
|
||||||
case teBase64:
|
case teBase64:
|
||||||
fileName = mime.BEncoding.Encode(utf8, p.FileName)
|
fileName = mime.BEncoding.Encode(utf8, p.FileName)
|
||||||
case teQuoted:
|
case teQuoted:
|
||||||
fileName = mime.QEncoding.Encode(utf8, p.FileName)
|
fileName = mime.QEncoding.Encode(utf8, p.FileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ContentType != "" {
|
if p.ContentType != "" {
|
||||||
// Build content type header.
|
// Build content type header.
|
||||||
param := make(map[string]string)
|
param := make(map[string]string)
|
||||||
|
|
@ -150,6 +165,7 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
|
||||||
}
|
}
|
||||||
p.Header.Set(hnContentType, p.ContentType)
|
p.Header.Set(hnContentType, p.ContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Disposition != "" {
|
if p.Disposition != "" {
|
||||||
// Build disposition header.
|
// Build disposition header.
|
||||||
param := make(map[string]string)
|
param := make(map[string]string)
|
||||||
|
|
@ -162,6 +178,7 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
|
||||||
}
|
}
|
||||||
p.Header.Set(hnContentDisposition, p.Disposition)
|
p.Header.Set(hnContentDisposition, p.Disposition)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cte
|
return cte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +195,7 @@ func (p *Part) encodeHeader(b *bufio.Writer) error {
|
||||||
for _, v := range p.Header[k] {
|
for _, v := range p.Header[k] {
|
||||||
encv := v
|
encv := v
|
||||||
if !rawContent {
|
if !rawContent {
|
||||||
switch selectTransferEncoding([]byte(v), true) {
|
switch p.selectTransferEncoding([]byte(v), true) {
|
||||||
case teBase64:
|
case teBase64:
|
||||||
encv = mime.BEncoding.Encode(utf8, v)
|
encv = mime.BEncoding.Encode(utf8, v)
|
||||||
case teQuoted:
|
case teQuoted:
|
||||||
|
|
@ -286,10 +303,15 @@ func (p *Part) encodeContentFromReader(b *bufio.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectTransferEncoding scans content for non-ASCII characters and selects 'b' or 'q' encoding.
|
// selectTransferEncoding scans content for non-ASCII characters and selects 'b' or 'q' encoding.
|
||||||
func selectTransferEncoding(content []byte, quoteLineBreaks bool) transferEncoding {
|
func (p *Part) selectTransferEncoding(content []byte, quoteLineBreaks bool) transferEncoding {
|
||||||
if len(content) == 0 {
|
if len(content) == 0 {
|
||||||
return te7Bit
|
return te7Bit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.encoder != nil && p.encoder.forceQuotedPrintableCteOption {
|
||||||
|
return teQuoted
|
||||||
|
}
|
||||||
|
|
||||||
// Binary chars remaining before we choose b64 encoding.
|
// Binary chars remaining before we choose b64 encoding.
|
||||||
threshold := b64Percent * len(content) / 100
|
threshold := b64Percent * len(content) / 100
|
||||||
bincount := 0
|
bincount := 0
|
||||||
33
vendor/github.com/jhillyerd/enmime/v2/encoder_options.go
generated
vendored
Normal file
33
vendor/github.com/jhillyerd/enmime/v2/encoder_options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package enmime
|
||||||
|
|
||||||
|
type EncoderOption interface {
|
||||||
|
apply(p *Encoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder implements MIME part encoding options
|
||||||
|
type Encoder struct {
|
||||||
|
forceQuotedPrintableCteOption bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceQuotedPrintableCte forces "quoted-printable" transfer encoding when selecting Content Transfer Encoding, preventing the use of base64.
|
||||||
|
func ForceQuotedPrintableCte(b bool) EncoderOption {
|
||||||
|
return forceQuotedPrintableCteOption(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type forceQuotedPrintableCteOption bool
|
||||||
|
|
||||||
|
func (o forceQuotedPrintableCteOption) apply(p *Encoder) {
|
||||||
|
p.forceQuotedPrintableCteOption = bool(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEncoder(ops ...EncoderOption) *Encoder {
|
||||||
|
e := Encoder{
|
||||||
|
forceQuotedPrintableCteOption: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range ops {
|
||||||
|
o.apply(&e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
// multi-gigabyte attachments.
|
// multi-gigabyte attachments.
|
||||||
//
|
//
|
||||||
// enmime is open source software released under the MIT License. The latest version can be found
|
// enmime is open source software released under the MIT License. The latest version can be found
|
||||||
// at https://github.com/jhillyerd/enmime
|
// at https://github.com/jhillyerd/enmime/v2
|
||||||
//
|
//
|
||||||
// [1]: https://github.com/jaytaylor/html2text
|
// [1]: https://github.com/inbucket/html2text
|
||||||
package enmime // import "github.com/jhillyerd/enmime"
|
package enmime // import "github.com/jhillyerd/enmime/v2"
|
||||||
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jaytaylor/html2text"
|
"github.com/inbucket/html2text"
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
inttp "github.com/jhillyerd/enmime/internal/textproto"
|
inttp "github.com/jhillyerd/enmime/v2/internal/textproto"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -198,16 +198,14 @@ func (p Parser) EnvelopeFromPart(root *Part) (*Envelope, error) {
|
||||||
// Down-convert HTML to text if necessary
|
// Down-convert HTML to text if necessary
|
||||||
if e.Text == "" && e.HTML != "" {
|
if e.Text == "" && e.HTML != "" {
|
||||||
// We always warn when this happens
|
// We always warn when this happens
|
||||||
e.Root.addWarning(
|
e.Root.addWarning(ErrorPlainTextFromHTML, "Message did not contain a text/plain part")
|
||||||
ErrorPlainTextFromHTML,
|
|
||||||
"Message did not contain a text/plain part")
|
|
||||||
|
|
||||||
if !p.disableTextConversion {
|
if !p.disableTextConversion {
|
||||||
var err error
|
var err error
|
||||||
if e.Text, err = html2text.FromString(e.HTML); err != nil {
|
if e.Text, err = html2text.FromString(e.HTML); err != nil {
|
||||||
e.Text = "" // Down-conversion shouldn't fail
|
e.Text = "" // Down-conversion shouldn't fail
|
||||||
p := e.Root.BreadthMatchFirst(matchHTMLBodyPart)
|
p := e.Root.BreadthMatchFirst(matchHTMLBodyPart)
|
||||||
p.addError(ErrorPlainTextFromHTML, "Failed to downconvert HTML: %v", err)
|
p.addErrorf(ErrorPlainTextFromHTML, "Failed to downconvert HTML: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +27,6 @@ const (
|
||||||
ErrorMalformedChildPart = "Malformed child part"
|
ErrorMalformedChildPart = "Malformed child part"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxPartErrors limits number of part parsing errors, errors after the limit are ignored.
|
|
||||||
// 0 means unlimited.
|
|
||||||
//
|
|
||||||
// Deprecated: This limit may be set via the `MaxStoredPartErrors` Parser option.
|
|
||||||
var MaxPartErrors = 0
|
|
||||||
|
|
||||||
// Error describes an error encountered while parsing.
|
// Error describes an error encountered while parsing.
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Name string // The name or type of error encountered, from Error consts.
|
Name string // The name or type of error encountered, from Error consts.
|
||||||
|
|
@ -49,13 +43,13 @@ func (e *Error) Error() string {
|
||||||
return fmt.Sprintf("[%s] %s: %s", sev, e.Name, e.Detail)
|
return fmt.Sprintf("[%s] %s: %s", sev, e.Name, e.Detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String formats the enmime.Error as a string. DEPRECATED; use Error() instead.
|
// addError builds a severe Error and appends to the Part error slice.
|
||||||
func (e *Error) String() string {
|
func (p *Part) addError(name string, detail string) {
|
||||||
return e.Error()
|
p.addProblem(&Error{name, detail, true})
|
||||||
}
|
}
|
||||||
|
|
||||||
// addWarning builds a severe Error and appends to the Part error slice.
|
// addErrorf builds a severe Error and appends to the Part error slice.
|
||||||
func (p *Part) addError(name string, detailFmt string, args ...interface{}) {
|
func (p *Part) addErrorf(name string, detailFmt string, args ...interface{}) {
|
||||||
p.addProblem(&Error{
|
p.addProblem(&Error{
|
||||||
name,
|
name,
|
||||||
fmt.Sprintf(detailFmt, args...),
|
fmt.Sprintf(detailFmt, args...),
|
||||||
|
|
@ -64,7 +58,12 @@ func (p *Part) addError(name string, detailFmt string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addWarning builds a non-severe Error and appends to the Part error slice.
|
// addWarning builds a non-severe Error and appends to the Part error slice.
|
||||||
func (p *Part) addWarning(name string, detailFmt string, args ...interface{}) {
|
func (p *Part) addWarning(name string, detail string) {
|
||||||
|
p.addProblem(&Error{name, detail, false})
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWarningf builds a non-severe Error and appends to the Part error slice.
|
||||||
|
func (p *Part) addWarningf(name string, detailFmt string, args ...interface{}) {
|
||||||
p.addProblem(&Error{
|
p.addProblem(&Error{
|
||||||
name,
|
name,
|
||||||
fmt.Sprintf(detailFmt, args...),
|
fmt.Sprintf(detailFmt, args...),
|
||||||
|
|
@ -74,10 +73,10 @@ func (p *Part) addWarning(name string, detailFmt string, args ...interface{}) {
|
||||||
|
|
||||||
// addProblem adds general *Error to the Part error slice.
|
// addProblem adds general *Error to the Part error slice.
|
||||||
func (p *Part) addProblem(err *Error) {
|
func (p *Part) addProblem(err *Error) {
|
||||||
maxErrors := MaxPartErrors
|
maxErrors := 0
|
||||||
if p.parser != nil && p.parser.maxStoredPartErrors != nil {
|
if p.parser != nil {
|
||||||
// Override global var.
|
// Override global var.
|
||||||
maxErrors = *p.parser.maxStoredPartErrors
|
maxErrors = p.parser.maxStoredPartErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxErrors == 0) || (len(p.Errors) < maxErrors) {
|
if (maxErrors == 0) || (len(p.Errors) < maxErrors) {
|
||||||
|
|
@ -97,9 +96,9 @@ type partErrorCollector struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *partErrorCollector) AddError(name string, detailFmt string, args ...any) {
|
func (p *partErrorCollector) AddError(name string, detailFmt string, args ...any) {
|
||||||
p.part.addError(name, detailFmt, args...)
|
p.part.addErrorf(name, detailFmt, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *partErrorCollector) AddWarning(name string, detailFmt string, args ...any) {
|
func (p *partErrorCollector) AddWarning(name string, detailFmt string, args ...any) {
|
||||||
p.part.addWarning(name, detailFmt, args...)
|
p.part.addWarningf(name, detailFmt, args...)
|
||||||
}
|
}
|
||||||
61
vendor/github.com/jhillyerd/enmime/v2/flake.lock
generated
vendored
Normal file
61
vendor/github.com/jhillyerd/enmime/v2/flake.lock
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736657626,
|
||||||
|
"narHash": "sha256-FWlPMUzp0lkQBdhKlPqtQdqmp+/C+1MBiEytaYfrCTY=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "2f9e2f85cb14a46410a1399aa9ea7ecf433e422e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
|
|
@ -9,10 +9,9 @@ import (
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
"github.com/jhillyerd/enmime/internal/stringutil"
|
"github.com/jhillyerd/enmime/v2/internal/stringutil"
|
||||||
inttp "github.com/jhillyerd/enmime/internal/textproto"
|
inttp "github.com/jhillyerd/enmime/v2/internal/textproto"
|
||||||
"github.com/jhillyerd/enmime/mediatype"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
@ -101,26 +100,6 @@ func ParseAddressList(list string) ([]*mail.Address, error) {
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminology from RFC 2047:
|
|
||||||
// encoded-word: the entire =?charset?encoding?encoded-text?= string
|
|
||||||
// charset: the character set portion of the encoded word
|
|
||||||
// encoding: the character encoding type used for the encoded-text
|
|
||||||
// encoded-text: the text we are decoding
|
|
||||||
|
|
||||||
// ParseMediaType is a more tolerant implementation of Go's mime.ParseMediaType function.
|
|
||||||
//
|
|
||||||
// Tolerances accounted for:
|
|
||||||
// - Missing ';' between content-type and media parameters
|
|
||||||
// - Repeating media parameters
|
|
||||||
// - Unquoted values in media parameters containing 'tspecials' characters
|
|
||||||
//
|
|
||||||
// Deprecated: Use mediaType.Parse instead
|
|
||||||
func ParseMediaType(ctype string) (mtype string, params map[string]string, invalidParams []string,
|
|
||||||
err error) {
|
|
||||||
// Export of internal function.
|
|
||||||
return mediatype.Parse(ctype)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadHeader reads a block of SMTP or MIME headers and returns a
|
// ReadHeader reads a block of SMTP or MIME headers and returns a
|
||||||
// textproto.MIMEHeader. Header parse warnings & errors will be added to
|
// textproto.MIMEHeader. Header parse warnings & errors will be added to
|
||||||
// ErrorCollector, io errors will be returned directly.
|
// ErrorCollector, io errors will be returned directly.
|
||||||
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
"github.com/jhillyerd/enmime/internal/textproto"
|
"github.com/jhillyerd/enmime/v2/internal/textproto"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
@ -34,6 +34,7 @@ func EnsureCommaDelimitedAddresses(s string) string {
|
||||||
inQuotes := false
|
inQuotes := false
|
||||||
inDomain := false
|
inDomain := false
|
||||||
escapeSequence := false
|
escapeSequence := false
|
||||||
|
inAngles := false
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
for i, r := range s {
|
for i, r := range s {
|
||||||
if escapeSequence {
|
if escapeSequence {
|
||||||
|
|
@ -46,6 +47,19 @@ func EnsureCommaDelimitedAddresses(s string) string {
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r == '<' {
|
||||||
|
inAngles = true
|
||||||
|
sb.WriteRune(r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '>' {
|
||||||
|
inAngles = false
|
||||||
|
sb.WriteRune(r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if inQuotes {
|
if inQuotes {
|
||||||
if r == '\\' {
|
if r == '\\' {
|
||||||
escapeSequence = true
|
escapeSequence = true
|
||||||
|
|
@ -74,13 +88,17 @@ func EnsureCommaDelimitedAddresses(s string) string {
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r == ' ' {
|
if r == ' ' && !inAngles {
|
||||||
inDomain = false
|
inDomain = false
|
||||||
sb.WriteRune(',')
|
sb.WriteRune(',')
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if inAngles && r == ' ' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package stringutil
|
package stringutil
|
||||||
|
|
||||||
// Wrap builds a byte slice from strs, wrapping on word boundaries before max chars
|
// Wrap builds a byte slice from strs, wrapping on word boundaries before maxLen chars
|
||||||
func Wrap(max int, strs ...string) []byte {
|
func Wrap(maxLen int, strs ...string) []byte {
|
||||||
input := make([]byte, 0)
|
input := make([]byte, 0)
|
||||||
output := make([]byte, 0)
|
output := make([]byte, 0)
|
||||||
for _, s := range strs {
|
for _, s := range strs {
|
||||||
input = append(input, []byte(s)...)
|
input = append(input, []byte(s)...)
|
||||||
}
|
}
|
||||||
if len(input) < max {
|
if len(input) < maxLen {
|
||||||
// Doesn't need to be wrapped
|
// Doesn't need to be wrapped
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ func Wrap(max int, strs ...string) []byte {
|
||||||
case ' ', '\t':
|
case ' ', '\t':
|
||||||
ls = i
|
ls = i
|
||||||
}
|
}
|
||||||
if ll >= max {
|
if ll >= maxLen {
|
||||||
if ls >= 0 {
|
if ls >= 0 {
|
||||||
output = append(output, input[lw+1:ls]...)
|
output = append(output, input[lw+1:ls]...)
|
||||||
output = append(output, '\r', '\n', ' ')
|
output = append(output, '\r', '\n', ' ')
|
||||||
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
_utf8 "unicode/utf8"
|
_utf8 "unicode/utf8"
|
||||||
|
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
"github.com/jhillyerd/enmime/internal/stringutil"
|
"github.com/jhillyerd/enmime/v2/internal/stringutil"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,8 +30,8 @@ const (
|
||||||
utf8 = "utf-8"
|
utf8 = "utf-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MediaTypeParseOptions controls the parsing of content-type and media-type strings.
|
// ParseOptions controls the parsing of content-type and media-type strings.
|
||||||
type MediaTypeParseOptions struct {
|
type ParseOptions struct {
|
||||||
StripMediaTypeInvalidCharacters bool
|
StripMediaTypeInvalidCharacters bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,11 +43,11 @@ type MediaTypeParseOptions struct {
|
||||||
// - Unquoted values in media parameters containing 'tspecials' characters
|
// - Unquoted values in media parameters containing 'tspecials' characters
|
||||||
// - Newline characters
|
// - Newline characters
|
||||||
func Parse(ctype string) (mtype string, params map[string]string, invalidParams []string, err error) {
|
func Parse(ctype string) (mtype string, params map[string]string, invalidParams []string, err error) {
|
||||||
return ParseWithOptions(ctype, MediaTypeParseOptions{})
|
return ParseWithOptions(ctype, ParseOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseWithOptions parses media-type with additional options controlling the parsing behavior.
|
// ParseWithOptions parses media-type with additional options controlling the parsing behavior.
|
||||||
func ParseWithOptions(ctype string, options MediaTypeParseOptions) (mtype string, params map[string]string, invalidParams []string, err error) {
|
func ParseWithOptions(ctype string, options ParseOptions) (mtype string, params map[string]string, invalidParams []string, err error) {
|
||||||
mtype, params, err = mime.ParseMediaType(
|
mtype, params, err = mime.ParseMediaType(
|
||||||
fixNewlines(fixUnescapedQuotes(fixUnquotedSpecials(fixMangledMediaType(removeTrailingHTMLTags(ctype), ';', options)))))
|
fixNewlines(fixUnescapedQuotes(fixUnquotedSpecials(fixMangledMediaType(removeTrailingHTMLTags(ctype), ';', options)))))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -73,7 +73,7 @@ func ParseWithOptions(ctype string, options MediaTypeParseOptions) (mtype string
|
||||||
|
|
||||||
// fixMangledMediaType is used to insert ; separators into media type strings that lack them, and
|
// fixMangledMediaType is used to insert ; separators into media type strings that lack them, and
|
||||||
// remove repeated parameters.
|
// remove repeated parameters.
|
||||||
func fixMangledMediaType(mtype string, sep rune, options MediaTypeParseOptions) string {
|
func fixMangledMediaType(mtype string, sep rune, options ParseOptions) string {
|
||||||
strsep := string([]rune{sep})
|
strsep := string([]rune{sep})
|
||||||
if mtype == "" {
|
if mtype == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -48,8 +48,7 @@ func MaxStoredPartErrors(n int) Option {
|
||||||
type maxStoredPartErrorsOption int
|
type maxStoredPartErrorsOption int
|
||||||
|
|
||||||
func (o maxStoredPartErrorsOption) apply(p *Parser) {
|
func (o maxStoredPartErrorsOption) apply(p *Parser) {
|
||||||
max := int(o)
|
p.maxStoredPartErrors = int(o)
|
||||||
p.maxStoredPartErrors = &max
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawContent if set to true will not try to decode the CTE and return the raw part content.
|
// RawContent if set to true will not try to decode the CTE and return the raw part content.
|
||||||
|
|
@ -112,3 +111,16 @@ func (o disableCharacterDetectionOption) apply(p *Parser) {
|
||||||
func DisableCharacterDetection(disableCharacterDetection bool) Option {
|
func DisableCharacterDetection(disableCharacterDetection bool) Option {
|
||||||
return disableCharacterDetectionOption(disableCharacterDetection)
|
return disableCharacterDetectionOption(disableCharacterDetection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type minCharsetDetectRunesOption int
|
||||||
|
|
||||||
|
func (o minCharsetDetectRunesOption) apply(p *Parser) {
|
||||||
|
p.minCharsetDetectRunes = int(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinCharsetDetectRunes sets the minimum length of a MIME part before enmime will attempt to
|
||||||
|
// detect its character set. The shorter the text, the more likely an incorrect character set
|
||||||
|
// will be chosen. The default is 100.
|
||||||
|
func MinCharsetDetectRunes(minCharsetDetectRunes int) Option {
|
||||||
|
return minCharsetDetectRunesOption(minCharsetDetectRunes)
|
||||||
|
}
|
||||||
|
|
@ -16,10 +16,9 @@ func AllowCorruptTextPartErrorPolicy(p *Part, err error) bool {
|
||||||
// CustomParseMediaType parses media type. See ParseMediaType for more details
|
// CustomParseMediaType parses media type. See ParseMediaType for more details
|
||||||
type CustomParseMediaType func(ctype string) (mtype string, params map[string]string, invalidParams []string, err error)
|
type CustomParseMediaType func(ctype string) (mtype string, params map[string]string, invalidParams []string, err error)
|
||||||
|
|
||||||
// Parser parses MIME.
|
// Parser parses MIME. Create with NewParser to inherit recommended defaults.
|
||||||
// Default parser is a valid one.
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
maxStoredPartErrors *int // TODO: Pointer until global var removed.
|
maxStoredPartErrors int
|
||||||
multipartWOBoundaryAsSinglePart bool
|
multipartWOBoundaryAsSinglePart bool
|
||||||
readPartErrorPolicy ReadPartErrorPolicy
|
readPartErrorPolicy ReadPartErrorPolicy
|
||||||
skipMalformedParts bool
|
skipMalformedParts bool
|
||||||
|
|
@ -28,14 +27,18 @@ type Parser struct {
|
||||||
stripMediaTypeInvalidCharacters bool
|
stripMediaTypeInvalidCharacters bool
|
||||||
disableTextConversion bool
|
disableTextConversion bool
|
||||||
disableCharacterDetection bool
|
disableCharacterDetection bool
|
||||||
|
minCharsetDetectRunes int
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultParser is a Parser with default configuration.
|
// defaultParser is a Parser with default configuration.
|
||||||
var defaultParser = Parser{}
|
var defaultParser = *NewParser()
|
||||||
|
|
||||||
// NewParser creates new parser with given options.
|
// NewParser creates new parser with given options.
|
||||||
func NewParser(ops ...Option) *Parser {
|
func NewParser(ops ...Option) *Parser {
|
||||||
p := Parser{}
|
// Construct parser with default options.
|
||||||
|
p := Parser{
|
||||||
|
minCharsetDetectRunes: 100,
|
||||||
|
}
|
||||||
|
|
||||||
for _, o := range ops {
|
for _, o := range ops {
|
||||||
o.apply(&p)
|
o.apply(&p)
|
||||||
|
|
@ -13,15 +13,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gogs/chardet"
|
"github.com/gogs/chardet"
|
||||||
"github.com/jhillyerd/enmime/internal/coding"
|
"github.com/jhillyerd/enmime/v2/internal/coding"
|
||||||
inttp "github.com/jhillyerd/enmime/internal/textproto"
|
inttp "github.com/jhillyerd/enmime/v2/internal/textproto"
|
||||||
"github.com/jhillyerd/enmime/mediatype"
|
"github.com/jhillyerd/enmime/v2/mediatype"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minCharsetConfidence = 85
|
minCharsetConfidence = 85
|
||||||
minCharsetRuneLength = 100
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Part represents a node in the MIME multipart tree. The Content-Type, Disposition and File Name
|
// Part represents a node in the MIME multipart tree. The Content-Type, Disposition and File Name
|
||||||
|
|
@ -51,6 +50,8 @@ type Part struct {
|
||||||
parser *Parser // Provides access to parsing options.
|
parser *Parser // Provides access to parsing options.
|
||||||
|
|
||||||
randSource rand.Source // optional rand for uuid boundary generation
|
randSource rand.Source // optional rand for uuid boundary generation
|
||||||
|
|
||||||
|
encoder *Encoder // provides encoding options
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPart creates a new Part object.
|
// NewPart creates a new Part object.
|
||||||
|
|
@ -130,7 +131,7 @@ func (p *Part) setupHeaders(r *bufio.Reader, defaultContentType string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i := range minvalidParams {
|
for i := range minvalidParams {
|
||||||
p.addWarning(
|
p.addWarningf(
|
||||||
ErrorMalformedHeader,
|
ErrorMalformedHeader,
|
||||||
"Content-Type header has malformed parameter %q",
|
"Content-Type header has malformed parameter %q",
|
||||||
minvalidParams[i])
|
minvalidParams[i])
|
||||||
|
|
@ -172,7 +173,7 @@ func (p *Part) readPartContent(r io.Reader, readPartErrorPolicy ReadPartErrorPol
|
||||||
buf, err := io.ReadAll(r)
|
buf, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if readPartErrorPolicy != nil && readPartErrorPolicy(p, err) {
|
if readPartErrorPolicy != nil && readPartErrorPolicy(p, err) {
|
||||||
p.addWarning(ErrorMalformedChildPart, "partial content: %s", err.Error())
|
p.addWarningf(ErrorMalformedChildPart, "partial content: %s", err.Error())
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -199,26 +200,30 @@ func (p *Part) convertFromDetectedCharset(r io.Reader, readPartErrorPolicy ReadP
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore r.
|
||||||
|
r = bytes.NewReader(buf)
|
||||||
|
|
||||||
|
if p.parser.disableCharacterDetection && p.Charset != "" {
|
||||||
|
// Charset detection is disabled, use declared charset.
|
||||||
|
return p.convertFromStatedCharset(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
cs, err := cd.DetectBest(buf)
|
cs, err := cd.DetectBest(buf)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
// Carry on
|
// Carry on
|
||||||
default:
|
default:
|
||||||
p.addWarning(ErrorCharsetDeclaration, "charset could not be detected: %v", err)
|
p.addWarningf(ErrorCharsetDeclaration, "charset could not be detected: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore r.
|
if cs == nil || cs.Confidence < minCharsetConfidence || len(bytes.Runes(buf)) < p.parser.minCharsetDetectRunes {
|
||||||
r = bytes.NewReader(buf)
|
|
||||||
|
|
||||||
if (p.parser.disableCharacterDetection && p.Charset != "") ||
|
|
||||||
(cs == nil || cs.Confidence < minCharsetConfidence || len(bytes.Runes(buf)) < minCharsetRuneLength) {
|
|
||||||
// Low confidence or not enough characters, use declared character set.
|
// Low confidence or not enough characters, use declared character set.
|
||||||
return p.convertFromStatedCharset(r), nil
|
return p.convertFromStatedCharset(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confidence exceeded our threshold, use detected character set.
|
// Confidence exceeded our threshold, use detected character set.
|
||||||
if p.Charset != "" && !strings.EqualFold(cs.Charset, p.Charset) {
|
if p.Charset != "" && !strings.EqualFold(cs.Charset, p.Charset) {
|
||||||
p.addWarning(ErrorCharsetDeclaration,
|
p.addWarningf(ErrorCharsetDeclaration,
|
||||||
"declared charset %q, detected %q, confidence %d",
|
"declared charset %q, detected %q, confidence %d",
|
||||||
p.Charset, cs.Charset, cs.Confidence)
|
p.Charset, cs.Charset, cs.Confidence)
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +249,7 @@ func (p *Part) convertFromStatedCharset(r io.Reader) io.Reader {
|
||||||
reader, err := coding.NewCharsetReader(p.Charset, r)
|
reader, err := coding.NewCharsetReader(p.Charset, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Failed to get a conversion reader.
|
// Failed to get a conversion reader.
|
||||||
p.addWarning(ErrorCharsetConversion, "failed to get reader for charset %q: %v", p.Charset, err)
|
p.addWarningf(ErrorCharsetConversion, "failed to get reader for charset %q: %v", p.Charset, err)
|
||||||
} else {
|
} else {
|
||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +263,7 @@ func (p *Part) convertFromStatedCharset(r io.Reader) io.Reader {
|
||||||
reader, err = coding.NewCharsetReader(p.Charset, r)
|
reader, err = coding.NewCharsetReader(p.Charset, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Failed to get a conversion reader.
|
// Failed to get a conversion reader.
|
||||||
p.addWarning(ErrorCharsetConversion, "failed to get reader for charset %q: %v", p.Charset, err)
|
p.addWarningf(ErrorCharsetConversion, "failed to get reader for charset %q: %v", p.Charset, err)
|
||||||
} else {
|
} else {
|
||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|
@ -294,7 +299,7 @@ func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolic
|
||||||
default:
|
default:
|
||||||
// Unknown encoding.
|
// Unknown encoding.
|
||||||
validEncoding = false
|
validEncoding = false
|
||||||
p.addWarning(
|
p.addWarningf(
|
||||||
ErrorContentEncoding,
|
ErrorContentEncoding,
|
||||||
"Unrecognized Content-Transfer-Encoding type %q",
|
"Unrecognized Content-Transfer-Encoding type %q",
|
||||||
encoding)
|
encoding)
|
||||||
|
|
@ -321,7 +326,7 @@ func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolic
|
||||||
}
|
}
|
||||||
// Set empty content-type error.
|
// Set empty content-type error.
|
||||||
if p.ContentType == "" {
|
if p.ContentType == "" {
|
||||||
p.addWarning(
|
p.addWarningf(
|
||||||
ErrorMissingContentType, "content-type is empty for part id: %s", p.PartID)
|
ErrorMissingContentType, "content-type is empty for part id: %s", p.PartID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -330,7 +335,7 @@ func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolic
|
||||||
// parses media type using custom or default media type parser
|
// parses media type using custom or default media type parser
|
||||||
func (p *Part) parseMediaType(ctype string) (mtype string, params map[string]string, invalidParams []string, err error) {
|
func (p *Part) parseMediaType(ctype string) (mtype string, params map[string]string, invalidParams []string, err error) {
|
||||||
if p.parser == nil || p.parser.customParseMediaType == nil {
|
if p.parser == nil || p.parser.customParseMediaType == nil {
|
||||||
return mediatype.ParseWithOptions(ctype, mediatype.MediaTypeParseOptions{StripMediaTypeInvalidCharacters: p.parser.stripMediaTypeInvalidCharacters})
|
return mediatype.ParseWithOptions(ctype, mediatype.ParseOptions{StripMediaTypeInvalidCharacters: p.parser.stripMediaTypeInvalidCharacters})
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.parser.customParseMediaType(ctype)
|
return p.parser.customParseMediaType(ctype)
|
||||||
|
|
@ -426,7 +431,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if br.unbounded {
|
if br.unbounded {
|
||||||
parent.addWarning(ErrorMissingBoundary, "Boundary %q was not closed correctly",
|
parent.addWarningf(ErrorMissingBoundary, "Boundary %q was not closed correctly",
|
||||||
parent.Boundary)
|
parent.Boundary)
|
||||||
}
|
}
|
||||||
if !next {
|
if !next {
|
||||||
|
|
@ -445,7 +450,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
|
||||||
bbr := bufio.NewReader(br)
|
bbr := bufio.NewReader(br)
|
||||||
if err = p.setupHeaders(bbr, ""); err != nil {
|
if err = p.setupHeaders(bbr, ""); err != nil {
|
||||||
if p.parser.skipMalformedParts {
|
if p.parser.skipMalformedParts {
|
||||||
parent.addError(ErrorMalformedChildPart, "read header: %s", err.Error())
|
parent.addErrorf(ErrorMalformedChildPart, "read header: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -457,7 +462,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
|
||||||
// Content is text or data, decode it.
|
// Content is text or data, decode it.
|
||||||
if err = p.decodeContent(bbr, p.parser.readPartErrorPolicy); err != nil {
|
if err = p.decodeContent(bbr, p.parser.readPartErrorPolicy); err != nil {
|
||||||
if p.parser.skipMalformedParts {
|
if p.parser.skipMalformedParts {
|
||||||
parent.addError(ErrorMalformedChildPart, "decode content: %s", err.Error())
|
parent.addErrorf(ErrorMalformedChildPart, "decode content: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
@ -470,7 +475,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
|
||||||
// Content is another multipart.
|
// Content is another multipart.
|
||||||
if err = parseParts(p, bbr); err != nil {
|
if err = parseParts(p, bbr); err != nil {
|
||||||
if p.parser.skipMalformedParts {
|
if p.parser.skipMalformedParts {
|
||||||
parent.addError(ErrorMalformedChildPart, "parse parts: %s", err.Error())
|
parent.addErrorf(ErrorMalformedChildPart, "parse parts: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
@ -491,3 +496,10 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Part) WithEncoder(e *Encoder) *Part {
|
||||||
|
if e != nil {
|
||||||
|
p.encoder = e
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
11
vendor/github.com/jhillyerd/enmime/v2/shell.nix
generated
vendored
Normal file
11
vendor/github.com/jhillyerd/enmime/v2/shell.nix
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
delve
|
||||||
|
go
|
||||||
|
golangci-lint
|
||||||
|
gopls
|
||||||
|
];
|
||||||
|
|
||||||
|
hardeningDisable = [ "fortify" ];
|
||||||
|
}
|
||||||
3
vendor/github.com/olekukonko/errors/.gitignore
generated
vendored
Normal file
3
vendor/github.com/olekukonko/errors/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
.idea/
|
||||||
|
tmp/
|
||||||
21
vendor/github.com/olekukonko/errors/LICENSE
generated
vendored
Normal file
21
vendor/github.com/olekukonko/errors/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Oleku Konko
|
||||||
|
|
||||||
|
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.
|
||||||
1565
vendor/github.com/olekukonko/errors/README.md
generated
vendored
Normal file
1565
vendor/github.com/olekukonko/errors/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
610
vendor/github.com/olekukonko/errors/chain.go
generated
vendored
Normal file
610
vendor/github.com/olekukonko/errors/chain.go
generated
vendored
Normal file
|
|
@ -0,0 +1,610 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog" // Standard structured logging package
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chain executes functions sequentially with enhanced error handling.
|
||||||
|
// Logging is optional and configured via a slog.Handler.
|
||||||
|
type Chain struct {
|
||||||
|
steps []chainStep // List of steps to execute
|
||||||
|
errors []error // Accumulated errors during execution
|
||||||
|
config chainConfig // Chain-wide configuration
|
||||||
|
lastStep *chainStep // Pointer to the last added step for configuration
|
||||||
|
logHandler slog.Handler // Optional logging handler (nil means no logging)
|
||||||
|
cancel context.CancelFunc // Function to cancel the context
|
||||||
|
}
|
||||||
|
|
||||||
|
// chainStep represents a single step in the chain.
|
||||||
|
type chainStep struct {
|
||||||
|
execute func() error // Function to execute for this step
|
||||||
|
optional bool // If true, errors don't stop the chain
|
||||||
|
config stepConfig // Step-specific configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// chainConfig holds chain-wide settings.
|
||||||
|
type chainConfig struct {
|
||||||
|
timeout time.Duration // Maximum duration for the entire chain
|
||||||
|
maxErrors int // Maximum number of errors before stopping (-1 for unlimited)
|
||||||
|
autoWrap bool // Whether to automatically wrap errors with additional context
|
||||||
|
}
|
||||||
|
|
||||||
|
// stepConfig holds configuration for an individual step.
|
||||||
|
type stepConfig struct {
|
||||||
|
context map[string]interface{} // Arbitrary key-value pairs for context
|
||||||
|
category ErrorCategory // Category for error classification
|
||||||
|
code int // Numeric error code
|
||||||
|
retry *Retry // Retry policy for the step
|
||||||
|
logOnFail bool // Whether to log errors automatically
|
||||||
|
metricsLabel string // Label for metrics (not used in this code)
|
||||||
|
logAttrs []slog.Attr // Additional attributes for logging
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainOption defines a function that configures a Chain.
|
||||||
|
type ChainOption func(*Chain)
|
||||||
|
|
||||||
|
// NewChain creates a new Chain with the given options.
|
||||||
|
// Logging is disabled by default (logHandler is nil).
|
||||||
|
func NewChain(opts ...ChainOption) *Chain {
|
||||||
|
c := &Chain{
|
||||||
|
config: chainConfig{
|
||||||
|
autoWrap: true, // Enable error wrapping by default
|
||||||
|
maxErrors: -1, // No limit on errors by default
|
||||||
|
},
|
||||||
|
// logHandler is nil, meaning no logging unless explicitly configured
|
||||||
|
}
|
||||||
|
// Apply each configuration option
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainWithLogHandler sets a custom slog.Handler for logging.
|
||||||
|
// If handler is nil, logging is effectively disabled.
|
||||||
|
func ChainWithLogHandler(handler slog.Handler) ChainOption {
|
||||||
|
return func(c *Chain) {
|
||||||
|
c.logHandler = handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainWithTimeout sets a timeout for the entire chain.
|
||||||
|
func ChainWithTimeout(d time.Duration) ChainOption {
|
||||||
|
return func(c *Chain) {
|
||||||
|
c.config.timeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainWithMaxErrors sets the maximum number of errors allowed.
|
||||||
|
// A value <= 0 means no limit.
|
||||||
|
func ChainWithMaxErrors(max int) ChainOption {
|
||||||
|
return func(c *Chain) {
|
||||||
|
if max <= 0 {
|
||||||
|
c.config.maxErrors = -1 // No limit
|
||||||
|
} else {
|
||||||
|
c.config.maxErrors = max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainWithAutoWrap enables or disables automatic error wrapping.
|
||||||
|
func ChainWithAutoWrap(auto bool) ChainOption {
|
||||||
|
return func(c *Chain) {
|
||||||
|
c.config.autoWrap = auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step adds a new step to the chain with the provided function.
|
||||||
|
// The function must return an error or nil.
|
||||||
|
func (c *Chain) Step(fn func() error) *Chain {
|
||||||
|
if fn == nil {
|
||||||
|
// Panic to enforce valid input
|
||||||
|
panic("Chain.Step: provided function cannot be nil")
|
||||||
|
}
|
||||||
|
// Create a new step with default configuration
|
||||||
|
step := chainStep{execute: fn, config: stepConfig{}}
|
||||||
|
c.steps = append(c.steps, step)
|
||||||
|
// Update lastStep to point to the newly added step
|
||||||
|
c.lastStep = &c.steps[len(c.steps)-1]
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call adds a step by wrapping a function with arguments.
|
||||||
|
// It uses reflection to validate and invoke the function.
|
||||||
|
func (c *Chain) Call(fn interface{}, args ...interface{}) *Chain {
|
||||||
|
// Wrap the function and arguments into an executable step
|
||||||
|
wrappedFn, err := c.wrapCallable(fn, args...)
|
||||||
|
if err != nil {
|
||||||
|
// Panic on setup errors to catch them early
|
||||||
|
panic(fmt.Sprintf("Chain.Call setup error: %v", err))
|
||||||
|
}
|
||||||
|
// Add the wrapped function as a step
|
||||||
|
step := chainStep{execute: wrappedFn, config: stepConfig{}}
|
||||||
|
c.steps = append(c.steps, step)
|
||||||
|
c.lastStep = &c.steps[len(c.steps)-1]
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional marks the last step as optional.
|
||||||
|
// Optional steps don't stop the chain on error.
|
||||||
|
func (c *Chain) Optional() *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to mark as optional
|
||||||
|
panic("Chain.Optional: must call Step() or Call() before Optional()")
|
||||||
|
}
|
||||||
|
c.lastStep.optional = true
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLog adds logging attributes to the last step.
|
||||||
|
func (c *Chain) WithLog(attrs ...slog.Attr) *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to configure
|
||||||
|
panic("Chain.WithLog: must call Step() or Call() before WithLog()")
|
||||||
|
}
|
||||||
|
// Append attributes to the step's logging configuration
|
||||||
|
c.lastStep.config.logAttrs = append(c.lastStep.config.logAttrs, attrs...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout sets a timeout for the entire chain.
|
||||||
|
func (c *Chain) Timeout(d time.Duration) *Chain {
|
||||||
|
c.config.timeout = d
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxErrors sets the maximum number of errors allowed.
|
||||||
|
func (c *Chain) MaxErrors(max int) *Chain {
|
||||||
|
if max <= 0 {
|
||||||
|
c.config.maxErrors = -1 // No limit
|
||||||
|
} else {
|
||||||
|
c.config.maxErrors = max
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// With adds a key-value pair to the last step's context.
|
||||||
|
func (c *Chain) With(key string, value interface{}) *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to configure
|
||||||
|
panic("Chain.With: must call Step() or Call() before With()")
|
||||||
|
}
|
||||||
|
// Initialize context map if nil
|
||||||
|
if c.lastStep.config.context == nil {
|
||||||
|
c.lastStep.config.context = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
// Add the key-value pair
|
||||||
|
c.lastStep.config.context[key] = value
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag sets an error category for the last step.
|
||||||
|
func (c *Chain) Tag(category ErrorCategory) *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to configure
|
||||||
|
panic("Chain.Tag: must call Step() or Call() before Tag()")
|
||||||
|
}
|
||||||
|
c.lastStep.config.category = category
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code sets a numeric error code for the last step.
|
||||||
|
func (c *Chain) Code(code int) *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to configure
|
||||||
|
panic("Chain.Code: must call Step() or Call() before Code()")
|
||||||
|
}
|
||||||
|
c.lastStep.config.code = code
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry configures retry behavior for the last step.
|
||||||
|
// Retry configures retry behavior for the last step.
|
||||||
|
func (c *Chain) Retry(maxAttempts int, delay time.Duration, opts ...RetryOption) *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
panic("Chain.Retry: must call Step() or Call() before Retry()")
|
||||||
|
}
|
||||||
|
if maxAttempts < 1 {
|
||||||
|
maxAttempts = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define default retry options
|
||||||
|
retryOpts := []RetryOption{
|
||||||
|
WithMaxAttempts(maxAttempts),
|
||||||
|
WithDelay(delay),
|
||||||
|
WithRetryIf(func(err error) bool { return IsRetryable(err) }),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add logging for retry attempts if a handler is configured
|
||||||
|
if c.logHandler != nil {
|
||||||
|
step := c.lastStep
|
||||||
|
retryOpts = append(retryOpts, WithOnRetry(func(attempt int, err error) {
|
||||||
|
// Prepare logging attributes
|
||||||
|
logAttrs := []slog.Attr{
|
||||||
|
slog.Int("attempt", attempt),
|
||||||
|
slog.Int("max_attempts", maxAttempts),
|
||||||
|
}
|
||||||
|
// Enhance the error with step context
|
||||||
|
enhancedErr := c.enhanceError(err, step)
|
||||||
|
// Log the retry attempt
|
||||||
|
c.logError(enhancedErr, fmt.Sprintf("Retrying step (attempt %d/%d)", attempt, maxAttempts), step.config, logAttrs...)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append any additional retry options
|
||||||
|
retryOpts = append(retryOpts, opts...)
|
||||||
|
// Create and assign the retry configuration
|
||||||
|
c.lastStep.config.retry = NewRetry(retryOpts...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogOnFail enables automatic logging of errors for the last step.
|
||||||
|
func (c *Chain) LogOnFail() *Chain {
|
||||||
|
if c.lastStep == nil {
|
||||||
|
// Panic if no step exists to configure
|
||||||
|
panic("Chain.LogOnFail: must call Step() or Call() before LogOnFail()")
|
||||||
|
}
|
||||||
|
c.lastStep.config.logOnFail = true
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the chain, stopping on the first non-optional error.
|
||||||
|
// It returns the first error encountered or nil if all steps succeed.
|
||||||
|
func (c *Chain) Run() error {
|
||||||
|
// Create a context with timeout or cancellation
|
||||||
|
ctx, cancel := c.getContextAndCancel()
|
||||||
|
defer cancel()
|
||||||
|
c.cancel = cancel
|
||||||
|
// Clear any previous errors
|
||||||
|
c.errors = c.errors[:0]
|
||||||
|
|
||||||
|
// Execute each step in sequence
|
||||||
|
for i := range c.steps {
|
||||||
|
step := &c.steps[i]
|
||||||
|
// Check if the context has been canceled
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := ctx.Err()
|
||||||
|
// Enhance the error with step context
|
||||||
|
enhancedErr := c.enhanceError(err, step)
|
||||||
|
c.errors = append(c.errors, enhancedErr)
|
||||||
|
// Log the context error
|
||||||
|
c.logError(enhancedErr, "Chain stopped due to context error before step", step.config)
|
||||||
|
return enhancedErr
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the step
|
||||||
|
err := c.executeStep(ctx, step)
|
||||||
|
if err != nil {
|
||||||
|
// Enhance the error with step context
|
||||||
|
enhancedErr := c.enhanceError(err, step)
|
||||||
|
c.errors = append(c.errors, enhancedErr)
|
||||||
|
// Log the error if required
|
||||||
|
if step.config.logOnFail || !step.optional {
|
||||||
|
logMsg := "Chain stopped due to error in step"
|
||||||
|
if step.optional {
|
||||||
|
logMsg = "Optional step failed"
|
||||||
|
}
|
||||||
|
c.logError(enhancedErr, logMsg, step.config)
|
||||||
|
}
|
||||||
|
// Stop execution if the step is not optional
|
||||||
|
if !step.optional {
|
||||||
|
return enhancedErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return nil if all steps completed successfully
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAll executes all steps, collecting errors without stopping.
|
||||||
|
// It returns a MultiError containing all errors or nil if none occurred.
|
||||||
|
func (c *Chain) RunAll() error {
|
||||||
|
ctx, cancel := c.getContextAndCancel()
|
||||||
|
defer cancel()
|
||||||
|
c.cancel = cancel
|
||||||
|
c.errors = c.errors[:0]
|
||||||
|
multi := NewMultiError()
|
||||||
|
|
||||||
|
for i := range c.steps {
|
||||||
|
step := &c.steps[i]
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := ctx.Err()
|
||||||
|
enhancedErr := c.enhanceError(err, step)
|
||||||
|
c.errors = append(c.errors, enhancedErr)
|
||||||
|
multi.Add(enhancedErr)
|
||||||
|
c.logError(enhancedErr, "Chain stopped due to context error before step (RunAll)", step.config)
|
||||||
|
goto endRunAll
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.executeStep(ctx, step)
|
||||||
|
if err != nil {
|
||||||
|
enhancedErr := c.enhanceError(err, step)
|
||||||
|
c.errors = append(c.errors, enhancedErr)
|
||||||
|
multi.Add(enhancedErr)
|
||||||
|
if step.config.logOnFail && c.logHandler != nil {
|
||||||
|
c.logError(enhancedErr, "Step failed during RunAll", step.config)
|
||||||
|
}
|
||||||
|
if c.config.maxErrors > 0 && multi.Count() >= c.config.maxErrors {
|
||||||
|
if c.logHandler != nil {
|
||||||
|
// Create a logger to log the max errors condition
|
||||||
|
logger := slog.New(c.logHandler)
|
||||||
|
logger.LogAttrs(
|
||||||
|
context.Background(),
|
||||||
|
slog.LevelError,
|
||||||
|
fmt.Sprintf("Stopping RunAll after reaching max errors (%d)", c.config.maxErrors),
|
||||||
|
slog.Int("max_errors", c.config.maxErrors),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
goto endRunAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endRunAll:
|
||||||
|
return multi.Single()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns a copy of the collected errors.
|
||||||
|
func (c *Chain) Errors() []error {
|
||||||
|
if len(c.errors) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Create a copy to prevent external modification
|
||||||
|
errs := make([]error, len(c.errors))
|
||||||
|
copy(errs, c.errors)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of steps in the chain.
|
||||||
|
func (c *Chain) Len() int {
|
||||||
|
return len(c.steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasErrors checks if any errors were collected.
|
||||||
|
func (c *Chain) HasErrors() bool {
|
||||||
|
return len(c.errors) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastError returns the most recent error or nil if none exist.
|
||||||
|
func (c *Chain) LastError() error {
|
||||||
|
if len(c.errors) > 0 {
|
||||||
|
return c.errors[len(c.errors)-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the chain's steps, errors, and context.
|
||||||
|
func (c *Chain) Reset() {
|
||||||
|
if c.cancel != nil {
|
||||||
|
// Cancel any active context
|
||||||
|
c.cancel()
|
||||||
|
c.cancel = nil
|
||||||
|
}
|
||||||
|
// Clear steps and errors
|
||||||
|
c.steps = c.steps[:0]
|
||||||
|
c.errors = c.errors[:0]
|
||||||
|
c.lastStep = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the collected errors (alias for Errors).
|
||||||
|
func (c *Chain) Unwrap() []error {
|
||||||
|
return c.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContextAndCancel creates a context based on the chain's timeout.
|
||||||
|
// It returns a context and its cancellation function.
|
||||||
|
func (c *Chain) getContextAndCancel() (context.Context, context.CancelFunc) {
|
||||||
|
parentCtx := context.Background()
|
||||||
|
if c.config.timeout > 0 {
|
||||||
|
// Create a context with a timeout
|
||||||
|
return context.WithTimeout(parentCtx, c.config.timeout)
|
||||||
|
}
|
||||||
|
// Create a cancellable context
|
||||||
|
return context.WithCancel(parentCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// logError logs an error with step-specific context and attributes.
|
||||||
|
// It only logs if a handler is configured and the error is non-nil.
|
||||||
|
func (c *Chain) logError(err error, msg string, config stepConfig, additionalAttrs ...slog.Attr) {
|
||||||
|
// Skip logging if no handler is set or error is nil
|
||||||
|
if c == nil || c.logHandler == nil || err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a logger on demand using the configured handler
|
||||||
|
logger := slog.New(c.logHandler)
|
||||||
|
|
||||||
|
// Initialize attributes with error and timestamp
|
||||||
|
allAttrs := make([]slog.Attr, 0, 5+len(config.logAttrs)+len(additionalAttrs))
|
||||||
|
allAttrs = append(allAttrs, slog.Any("error", err))
|
||||||
|
allAttrs = append(allAttrs, slog.Time("timestamp", time.Now()))
|
||||||
|
|
||||||
|
// Add step-specific metadata
|
||||||
|
if config.category != "" {
|
||||||
|
allAttrs = append(allAttrs, slog.String("category", string(config.category)))
|
||||||
|
}
|
||||||
|
if config.code != 0 {
|
||||||
|
allAttrs = append(allAttrs, slog.Int("code", config.code))
|
||||||
|
}
|
||||||
|
for k, v := range config.context {
|
||||||
|
allAttrs = append(allAttrs, slog.Any(k, v))
|
||||||
|
}
|
||||||
|
allAttrs = append(allAttrs, config.logAttrs...)
|
||||||
|
allAttrs = append(allAttrs, additionalAttrs...)
|
||||||
|
|
||||||
|
// Add stack trace and error name if the error is of type *Error
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
if stack := e.Stack(); len(stack) > 0 {
|
||||||
|
// Format stack trace, truncating if too long
|
||||||
|
stackStr := "\n\t" + strings.Join(stack, "\n\t")
|
||||||
|
if len(stackStr) > 1000 {
|
||||||
|
stackStr = stackStr[:1000] + "..."
|
||||||
|
}
|
||||||
|
allAttrs = append(allAttrs, slog.String("stacktrace", stackStr))
|
||||||
|
}
|
||||||
|
if name := e.Name(); name != "" {
|
||||||
|
allAttrs = append(allAttrs, slog.String("error_name", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the error at ERROR level with all attributes
|
||||||
|
// Use a defer to catch any panics during logging
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
// Print to stdout to avoid infinite recursion
|
||||||
|
fmt.Printf("ERROR: Recovered from panic during logging: %v\nAttributes: %v\n", r, allAttrs)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
logger.LogAttrs(context.Background(), slog.LevelError, msg, allAttrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapCallable wraps a function and its arguments into an executable step.
|
||||||
|
// It uses reflection to validate the function and arguments.
|
||||||
|
func (c *Chain) wrapCallable(fn interface{}, args ...interface{}) (func() error, error) {
|
||||||
|
val := reflect.ValueOf(fn)
|
||||||
|
typ := val.Type()
|
||||||
|
|
||||||
|
// Ensure the provided value is a function
|
||||||
|
if typ.Kind() != reflect.Func {
|
||||||
|
return nil, fmt.Errorf("provided 'fn' is not a function (got %T)", fn)
|
||||||
|
}
|
||||||
|
// Check if the number of arguments matches the function's signature
|
||||||
|
if typ.NumIn() != len(args) {
|
||||||
|
return nil, fmt.Errorf("function expects %d arguments, but %d were provided", typ.NumIn(), len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare argument values
|
||||||
|
argVals := make([]reflect.Value, len(args))
|
||||||
|
errorType := reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
for i, arg := range args {
|
||||||
|
expectedType := typ.In(i)
|
||||||
|
var providedVal reflect.Value
|
||||||
|
if arg != nil {
|
||||||
|
providedVal = reflect.ValueOf(arg)
|
||||||
|
// Check if the argument type is assignable to the expected type
|
||||||
|
if !providedVal.Type().AssignableTo(expectedType) {
|
||||||
|
// Special case for error interfaces
|
||||||
|
if expectedType.Kind() == reflect.Interface && expectedType.Implements(errorType) && providedVal.Type().Implements(errorType) {
|
||||||
|
// Allow error interface
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("argument %d type mismatch: expected %s, got %s", i, expectedType, providedVal.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle nil arguments for nullable types
|
||||||
|
switch expectedType.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
||||||
|
providedVal = reflect.Zero(expectedType)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("argument %d is nil, but expected non-nillable type %s", i, expectedType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argVals[i] = providedVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the function's return type
|
||||||
|
if typ.NumOut() > 1 || (typ.NumOut() == 1 && !typ.Out(0).Implements(errorType)) {
|
||||||
|
return nil, fmt.Errorf("function must return either no values or a single error (got %d return values)", typ.NumOut())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a wrapped function that calls the original with the provided arguments
|
||||||
|
return func() error {
|
||||||
|
results := val.Call(argVals)
|
||||||
|
if len(results) == 1 && results[0].Interface() != nil {
|
||||||
|
return results[0].Interface().(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeStep runs a single step, applying retries if configured.
|
||||||
|
func (c *Chain) executeStep(ctx context.Context, step *chainStep) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if step.config.retry != nil {
|
||||||
|
retry := step.config.retry.Transform(WithContext(ctx))
|
||||||
|
// Wrap step execution to respect context
|
||||||
|
wrappedFn := func() error {
|
||||||
|
type result struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
done := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
done <- result{err: step.execute()}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case res := <-done:
|
||||||
|
return res.err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retry.Execute(wrappedFn)
|
||||||
|
}
|
||||||
|
// Non-retry case also respects context
|
||||||
|
type result struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
done := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
done <- result{err: step.execute()}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case res := <-done:
|
||||||
|
return res.err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enhanceError wraps an error with additional context from the step.
|
||||||
|
func (c *Chain) enhanceError(err error, step *chainStep) error {
|
||||||
|
if err == nil || !c.config.autoWrap {
|
||||||
|
// Return the error unchanged if nil or autoWrap is disabled
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the base error
|
||||||
|
var baseError *Error
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
// Copy existing *Error to preserve its properties
|
||||||
|
baseError = e.Copy()
|
||||||
|
} else {
|
||||||
|
// Create a new *Error wrapping the original
|
||||||
|
baseError = New(err.Error()).Wrap(err).WithStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
if step != nil {
|
||||||
|
// Add step-specific context to the error
|
||||||
|
if step.config.category != "" && baseError.Category() == "" {
|
||||||
|
baseError.WithCategory(step.config.category)
|
||||||
|
}
|
||||||
|
if step.config.code != 0 && baseError.Code() == 0 {
|
||||||
|
baseError.WithCode(step.config.code)
|
||||||
|
}
|
||||||
|
for k, v := range step.config.context {
|
||||||
|
baseError.With(k, v)
|
||||||
|
}
|
||||||
|
for _, attr := range step.config.logAttrs {
|
||||||
|
baseError.With(attr.Key, attr.Value.Any())
|
||||||
|
}
|
||||||
|
if step.config.retry != nil && !baseError.HasContextKey(ctxRetry) {
|
||||||
|
// Mark the error as retryable if retries are configured
|
||||||
|
baseError.WithRetryable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseError
|
||||||
|
}
|
||||||
1496
vendor/github.com/olekukonko/errors/errors.go
generated
vendored
Normal file
1496
vendor/github.com/olekukonko/errors/errors.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
432
vendor/github.com/olekukonko/errors/helper.go
generated
vendored
Normal file
432
vendor/github.com/olekukonko/errors/helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// As wraps errors.As, using custom type assertion for *Error types.
|
||||||
|
// Falls back to standard errors.As for non-*Error types.
|
||||||
|
// Returns false if either err or target is nil.
|
||||||
|
func As(err error, target interface{}) bool {
|
||||||
|
if err == nil || target == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try our custom *Error handling
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.As(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to standard errors.As
|
||||||
|
return errors.As(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns the status code of an error, if it is an *Error.
|
||||||
|
// Returns 500 as a default for non-*Error types to indicate an internal error.
|
||||||
|
func Code(err error) int {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Code()
|
||||||
|
}
|
||||||
|
return DefaultCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context extracts the context map from an error, if it is an *Error.
|
||||||
|
// Returns nil for non-*Error types or if no context is present.
|
||||||
|
func Context(err error) map[string]interface{} {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Context()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert transforms any error into an *Error, preserving its message and wrapping it if needed.
|
||||||
|
// Returns nil if the input is nil; returns the original if already an *Error.
|
||||||
|
// Uses multiple strategies: direct assertion, errors.As, manual unwrapping, and fallback creation.
|
||||||
|
func Convert(err error) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try direct type assertion (fast path)
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try using errors.As (more flexible)
|
||||||
|
var e *Error
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual unwrapping as fallback
|
||||||
|
visited := make(map[error]bool)
|
||||||
|
for unwrapped := err; unwrapped != nil; {
|
||||||
|
if visited[unwrapped] {
|
||||||
|
break // Cycle detected
|
||||||
|
}
|
||||||
|
visited[unwrapped] = true
|
||||||
|
if e, ok := unwrapped.(*Error); ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
unwrapped = errors.Unwrap(unwrapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback: create new error with original message and wrap it
|
||||||
|
return New(err.Error()).Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the occurrence count of an error, if it is an *Error.
|
||||||
|
// Returns 0 for non-*Error types.
|
||||||
|
func Count(err error) uint64 {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Count()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find searches the error chain for the first error matching pred.
|
||||||
|
// Returns nil if no match is found or pred is nil; traverses both Unwrap() and Cause() chains.
|
||||||
|
func Find(err error, pred func(error) bool) error {
|
||||||
|
for current := err; current != nil; {
|
||||||
|
if pred(current) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to unwrap using Unwrap() or Cause()
|
||||||
|
switch v := current.(type) {
|
||||||
|
case interface{ Unwrap() error }:
|
||||||
|
current = v.Unwrap()
|
||||||
|
case interface{ Cause() error }:
|
||||||
|
current = v.Cause()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// From transforms any error into an *Error, preserving its message and wrapping it if needed.
|
||||||
|
// Alias of Convert; returns nil if input is nil, original if already an *Error.
|
||||||
|
func From(err error) *Error {
|
||||||
|
return Convert(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContext creates an *Error from a context and an existing error.
|
||||||
|
// Enhances the error with context info: timeout status, deadline, or cancellation.
|
||||||
|
// Returns nil if input error is nil; does not store context values directly.
|
||||||
|
func FromContext(ctx context.Context, err error) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
e := New(err.Error())
|
||||||
|
|
||||||
|
// Handle context errors
|
||||||
|
switch ctx.Err() {
|
||||||
|
case context.DeadlineExceeded:
|
||||||
|
e.WithTimeout()
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
e.With("deadline", deadline.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
case context.Canceled:
|
||||||
|
e.With("cancelled", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category returns the category of an error, if it is an *Error.
|
||||||
|
// Returns an empty string for non-*Error types or unset categories.
|
||||||
|
func Category(err error) string {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.category
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has checks if an error contains meaningful content.
|
||||||
|
// Returns true for non-nil standard errors or *Error with content (msg, name, template, or cause).
|
||||||
|
func Has(err error) bool {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Has()
|
||||||
|
}
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasContextKey checks if the error's context contains the specified key.
|
||||||
|
// Returns false for non-*Error types or if the key is not present in the context.
|
||||||
|
func HasContextKey(err error, key string) bool {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
ctx := e.Context()
|
||||||
|
if ctx != nil {
|
||||||
|
_, exists := ctx[key]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is wraps errors.Is, using custom matching for *Error types.
|
||||||
|
// Falls back to standard errors.Is for non-*Error types; returns true if err equals target.
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
if err == nil || target == nil {
|
||||||
|
return err == target
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Is(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use standard errors.Is for non-Error types
|
||||||
|
return errors.Is(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsError checks if an error is an instance of *Error.
|
||||||
|
// Returns true only for this package's custom error type; false for nil or other types.
|
||||||
|
func IsError(err error) bool {
|
||||||
|
_, ok := err.(*Error)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty checks if an error has no meaningful content.
|
||||||
|
// Returns true for nil errors, empty *Error instances, or standard errors with whitespace-only messages.
|
||||||
|
func IsEmpty(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.IsEmpty()
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(err.Error()) == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNull checks if an error is nil or represents a NULL value.
|
||||||
|
// Delegates to *Error’s IsNull for custom errors; uses sqlNull for others.
|
||||||
|
func IsNull(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.IsNull()
|
||||||
|
}
|
||||||
|
return sqlNull(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRetryable checks if an error is retryable.
|
||||||
|
// For *Error, checks context for retry flag; for others, looks for "retry" or timeout in message.
|
||||||
|
// Returns false for nil errors; thread-safe for *Error types.
|
||||||
|
func IsRetryable(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
// Check smallContext directly if context map isn’t populated
|
||||||
|
for i := int32(0); i < e.smallCount; i++ {
|
||||||
|
if e.smallContext[i].key == ctxRetry {
|
||||||
|
if val, ok := e.smallContext[i].value.(bool); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check regular context
|
||||||
|
if e.context != nil {
|
||||||
|
if val, ok := e.context[ctxRetry].(bool); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check cause recursively
|
||||||
|
if e.cause != nil {
|
||||||
|
return IsRetryable(e.cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lowerMsg := strings.ToLower(err.Error())
|
||||||
|
return IsTimeout(err) || strings.Contains(lowerMsg, "retry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTimeout checks if an error indicates a timeout.
|
||||||
|
// For *Error, checks context for timeout flag; for others, looks for "timeout" in message.
|
||||||
|
// Returns false for nil errors.
|
||||||
|
func IsTimeout(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
if val, ok := e.Context()[ctxTimeout].(bool); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.ToLower(err.Error()), "timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge combines multiple errors into a single *Error.
|
||||||
|
// Aggregates messages with "; " separator, merges contexts and stacks; returns nil if no errors provided.
|
||||||
|
func Merge(errs ...error) *Error {
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var messages []string
|
||||||
|
combined := New("")
|
||||||
|
for _, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
if e.stack != nil && combined.stack == nil {
|
||||||
|
combined.WithStack() // Capture stack from first *Error with stack
|
||||||
|
}
|
||||||
|
if ctx := e.Context(); ctx != nil {
|
||||||
|
for k, v := range ctx {
|
||||||
|
combined.With(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.cause != nil {
|
||||||
|
combined.Wrap(e.cause)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
combined.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(messages) > 0 {
|
||||||
|
combined.msg = strings.Join(messages, "; ")
|
||||||
|
}
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of an error, if it is an *Error.
|
||||||
|
// Returns an empty string for non-*Error types or unset names.
|
||||||
|
func Name(err error) string {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnwrapAll returns a slice of all errors in the chain, including the root error.
|
||||||
|
// Traverses both Unwrap() and Cause() chains; returns nil if err is nil.
|
||||||
|
func UnwrapAll(err error) []error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.UnwrapAll()
|
||||||
|
}
|
||||||
|
var result []error
|
||||||
|
Walk(err, func(e error) {
|
||||||
|
result = append(result, e)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack extracts the stack trace from an error, if it is an *Error.
|
||||||
|
// Returns nil for non-*Error types or if no stack is present.
|
||||||
|
func Stack(err error) []string {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.Stack()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform applies transformations to an error, returning a new *Error.
|
||||||
|
// Creates a new *Error from non-*Error types before applying fn; returns nil if err is nil.
|
||||||
|
func Transform(err error, fn func(*Error)) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
newErr := e.Copy()
|
||||||
|
fn(newErr)
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
// If not an *Error, create a new one and transform it
|
||||||
|
newErr := New(err.Error())
|
||||||
|
fn(newErr)
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying cause of an error, if it implements Unwrap.
|
||||||
|
// For *Error, returns cause; for others, returns the error itself; nil if err is nil.
|
||||||
|
func Unwrap(err error) error {
|
||||||
|
for current := err; current != nil; {
|
||||||
|
if e, ok := current.(*Error); ok {
|
||||||
|
if e.cause == nil {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current = e.cause
|
||||||
|
} else {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk traverses the error chain, applying fn to each error.
|
||||||
|
// Supports both Unwrap() and Cause() interfaces; stops at nil or non-unwrappable errors.
|
||||||
|
func Walk(err error, fn func(error)) {
|
||||||
|
for current := err; current != nil; {
|
||||||
|
fn(current)
|
||||||
|
|
||||||
|
// Attempt to unwrap using Unwrap() or Cause()
|
||||||
|
switch v := current.(type) {
|
||||||
|
case interface{ Unwrap() error }:
|
||||||
|
current = v.Unwrap()
|
||||||
|
case interface{ Cause() error }:
|
||||||
|
current = v.Cause()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With adds a key-value pair to an error's context, if it is an *Error.
|
||||||
|
// Returns the original error unchanged if not an *Error; no-op for non-*Error types.
|
||||||
|
func With(err error, key string, value interface{}) error {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.With(key, value)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack converts any error to an *Error and captures a stack trace.
|
||||||
|
// Returns nil if input is nil; adds stack to existing *Error or wraps non-*Error types.
|
||||||
|
func WithStack(err error) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e.WithStack()
|
||||||
|
}
|
||||||
|
return New(err.Error()).WithStack().Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap creates a new *Error that wraps another error with additional context.
|
||||||
|
// Uses a copy of the provided wrapper *Error; returns nil if err is nil.
|
||||||
|
func Wrap(err error, wrapper *Error) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if wrapper == nil {
|
||||||
|
wrapper = newError()
|
||||||
|
}
|
||||||
|
newErr := wrapper.Copy()
|
||||||
|
newErr.cause = err
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf creates a new formatted *Error that wraps another error.
|
||||||
|
// Formats the message and sets the cause; returns nil if err is nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) *Error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e := newError()
|
||||||
|
e.msg = fmt.Sprintf(format, args...)
|
||||||
|
e.cause = err
|
||||||
|
return e
|
||||||
|
}
|
||||||
225
vendor/github.com/olekukonko/errors/inspect.go
generated
vendored
Normal file
225
vendor/github.com/olekukonko/errors/inspect.go
generated
vendored
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
// File: inspect.go
|
||||||
|
// Updated to support both error and *Error with delegation for cleaner *Error handling
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
stderrs "errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Inspect provides detailed examination of an error, handling both single errors and MultiError
|
||||||
|
func Inspect(err error) {
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("No error occurred")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n=== Error Inspection ===\n")
|
||||||
|
fmt.Printf("Top-level error: %v\n", err)
|
||||||
|
fmt.Printf("Top-level error type: %T\n", err)
|
||||||
|
|
||||||
|
// Handle *Error directly
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
InspectError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle MultiError
|
||||||
|
if multi, ok := err.(*MultiError); ok {
|
||||||
|
allErrors := multi.Errors()
|
||||||
|
fmt.Printf("\nContains %d errors:\n", len(allErrors))
|
||||||
|
for i, e := range allErrors {
|
||||||
|
fmt.Printf("\n--- Error %d ---\n", i+1)
|
||||||
|
inspectSingleError(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Inspect single error if not MultiError or *Error
|
||||||
|
fmt.Println("\n--- Details ---")
|
||||||
|
inspectSingleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional diagnostics
|
||||||
|
fmt.Println("\n--- Diagnostics ---")
|
||||||
|
if IsRetryable(err) {
|
||||||
|
fmt.Println("- Error chain contains retryable errors")
|
||||||
|
}
|
||||||
|
if IsTimeout(err) {
|
||||||
|
fmt.Println("- Error chain contains timeout errors")
|
||||||
|
}
|
||||||
|
if code := getErrorCode(err); code != 0 {
|
||||||
|
fmt.Printf("- Highest priority error code: %d\n", code)
|
||||||
|
}
|
||||||
|
fmt.Printf("========================\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InspectError provides detailed inspection of a specific *Error instance
|
||||||
|
func InspectError(err *Error) {
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("No error occurred")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n=== Error Inspection (*Error) ===\n")
|
||||||
|
fmt.Printf("Top-level error: %v\n", err)
|
||||||
|
fmt.Printf("Top-level error type: %T\n", err)
|
||||||
|
|
||||||
|
fmt.Println("\n--- Details ---")
|
||||||
|
inspectSingleError(err) // Delegate to handle unwrapping and details
|
||||||
|
|
||||||
|
// Additional diagnostics specific to *Error
|
||||||
|
fmt.Println("\n--- Diagnostics ---")
|
||||||
|
if IsRetryable(err) {
|
||||||
|
fmt.Println("- Error is retryable")
|
||||||
|
}
|
||||||
|
if IsTimeout(err) {
|
||||||
|
fmt.Println("- Error chain contains timeout errors")
|
||||||
|
}
|
||||||
|
if code := err.Code(); code != 0 {
|
||||||
|
fmt.Printf("- Error code: %d\n", code)
|
||||||
|
}
|
||||||
|
fmt.Printf("========================\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspectSingleError handles inspection of a single error (may be part of a chain)
|
||||||
|
func inspectSingleError(err error) {
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(" (nil error)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" Error: %v\n", err)
|
||||||
|
fmt.Printf(" Type: %T\n", err)
|
||||||
|
|
||||||
|
// Handle wrapped errors, including *Error type
|
||||||
|
var currentErr error = err
|
||||||
|
depth := 0
|
||||||
|
for currentErr != nil {
|
||||||
|
prefix := strings.Repeat(" ", depth+1)
|
||||||
|
if depth > 0 {
|
||||||
|
fmt.Printf("%sWrapped Cause (%T): %v\n", prefix, currentErr, currentErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's our specific *Error type
|
||||||
|
if e, ok := currentErr.(*Error); ok {
|
||||||
|
if name := e.Name(); name != "" {
|
||||||
|
fmt.Printf("%sName: %s\n", prefix, name)
|
||||||
|
}
|
||||||
|
if cat := e.Category(); cat != "" {
|
||||||
|
fmt.Printf("%sCategory: %s\n", prefix, cat)
|
||||||
|
}
|
||||||
|
if code := e.Code(); code != 0 {
|
||||||
|
fmt.Printf("%sCode: %d\n", prefix, code)
|
||||||
|
}
|
||||||
|
if ctx := e.Context(); len(ctx) > 0 {
|
||||||
|
fmt.Printf("%sContext:\n", prefix)
|
||||||
|
for k, v := range ctx {
|
||||||
|
fmt.Printf("%s %s: %v\n", prefix, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stack := e.Stack(); len(stack) > 0 {
|
||||||
|
fmt.Printf("%sStack (Top 3):\n", prefix)
|
||||||
|
limit := 3
|
||||||
|
if len(stack) < limit {
|
||||||
|
limit = len(stack)
|
||||||
|
}
|
||||||
|
for i := 0; i < limit; i++ {
|
||||||
|
fmt.Printf("%s %s\n", prefix, stack[i])
|
||||||
|
}
|
||||||
|
if len(stack) > limit {
|
||||||
|
fmt.Printf("%s ... (%d more frames)\n", prefix, len(stack)-limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap using standard errors.Unwrap and handle *Error Unwrap
|
||||||
|
var nextErr error
|
||||||
|
// Prioritize *Error's Unwrap if available AND it returns non-nil
|
||||||
|
if e, ok := currentErr.(*Error); ok {
|
||||||
|
unwrapped := e.Unwrap()
|
||||||
|
if unwrapped != nil {
|
||||||
|
nextErr = unwrapped
|
||||||
|
} else {
|
||||||
|
// If *Error.Unwrap returns nil, fall back to standard unwrap
|
||||||
|
// This handles cases where *Error might wrap a non-standard error
|
||||||
|
// or where its internal cause is deliberately nil.
|
||||||
|
nextErr = stderrs.Unwrap(currentErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextErr = stderrs.Unwrap(currentErr) // Fall back to standard unwrap for non-*Error types
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent infinite loops if Unwrap returns the same error, or stop if no more unwrapping
|
||||||
|
if nextErr == currentErr || nextErr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentErr = nextErr
|
||||||
|
depth++
|
||||||
|
if depth > 10 { // Safety break for very deep or potentially cyclic chains
|
||||||
|
fmt.Printf("%s... (chain too deep or potential cycle)\n", strings.Repeat(" ", depth+1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorCode traverses the error chain to find the highest priority code.
|
||||||
|
// It uses errors.As to find the first *Error in the chain.
|
||||||
|
func getErrorCode(err error) int {
|
||||||
|
var code int = 0 // Default code
|
||||||
|
var target *Error
|
||||||
|
if As(err, &target) { // Use the package's As helper
|
||||||
|
if target != nil { // Add nil check for safety
|
||||||
|
code = target.Code()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the top-level error is *Error and has a code, it might take precedence.
|
||||||
|
// This depends on desired logic. Let's keep it simple for now: first code found by As.
|
||||||
|
if code == 0 { // Only check top-level if As didn't find one with a code
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
code = e.Code()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleError demonstrates using Inspect with additional handling logic
|
||||||
|
func handleError(err error) {
|
||||||
|
fmt.Println("\n=== Processing Failure ===")
|
||||||
|
Inspect(err) // Use the primary Inspect function
|
||||||
|
|
||||||
|
// Additional handling based on inspection
|
||||||
|
code := getErrorCode(err) // Use the helper
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case IsTimeout(err):
|
||||||
|
fmt.Println("\nAction: Check connectivity or increase timeout")
|
||||||
|
case code == 402: // Check code obtained via helper
|
||||||
|
fmt.Println("\nAction: Payment processing failed - notify billing")
|
||||||
|
default:
|
||||||
|
fmt.Println("\nAction: Generic failure handling")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processOrder demonstrates Chain usage with Inspect
|
||||||
|
func processOrder() error {
|
||||||
|
validateInput := func() error { return nil }
|
||||||
|
processPayment := func() error { return stderrs.New("credit card declined") }
|
||||||
|
sendNotification := func() error { fmt.Println("Notification sent."); return nil }
|
||||||
|
logOrder := func() error { fmt.Println("Order logged."); return nil }
|
||||||
|
|
||||||
|
chain := NewChain(ChainWithTimeout(2*time.Second)).
|
||||||
|
Step(validateInput).Tag("validation").
|
||||||
|
Step(processPayment).Tag("billing").Code(402).Retry(3, 100*time.Millisecond, WithRetryIf(IsRetryable)).
|
||||||
|
Step(sendNotification).Optional().
|
||||||
|
Step(logOrder)
|
||||||
|
|
||||||
|
err := chain.Run()
|
||||||
|
if err != nil {
|
||||||
|
handleError(err) // Call the unified error handler
|
||||||
|
return err // Propagate the error if needed
|
||||||
|
}
|
||||||
|
fmt.Println("Order processed successfully!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue