From c109b79434325653690a51cbe0d1c523655771b5 Mon Sep 17 00:00:00 2001 From: Mikhail Knyazhev Date: Sat, 27 Sep 2025 04:10:32 +0300 Subject: [PATCH] go 1.24 + refactoring --- .github/dependabot.yml | 0 .github/workflows/ci.yml | 2 +- .github/workflows/codeql.yml | 38 ---- .gitignore | 29 ++- .golangci.yml | 388 ++++------------------------------- LICENSE | 2 +- Makefile | 24 +-- errors.go | 115 ++--------- errors_test.go | 87 ++++---- go.mod | 2 +- operation.go | 87 ++++++++ queue.go | 15 ++ queue_test.go | 40 ++++ trace.go | 28 --- types.go | 14 ++ 15 files changed, 288 insertions(+), 583 deletions(-) mode change 100644 => 100755 .github/dependabot.yml mode change 100644 => 100755 .github/workflows/ci.yml delete mode 100644 .github/workflows/codeql.yml mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .golangci.yml mode change 100644 => 100755 Makefile create mode 100644 operation.go create mode 100644 queue.go create mode 100644 queue_test.go delete mode 100644 trace.go create mode 100644 types.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml old mode 100644 new mode 100755 index bd7e425..a76bd9e --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.17', '1.18', '1.19' ] + go: [ '1.24' ] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index ddee4f0..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,38 +0,0 @@ - -name: "CodeQL" - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - schedule: - - cron: '16 8 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index ae81252..2d09f4d --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,20 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins + +.tools/ +bin/ +vendor/ +build/ +.idea/ +.vscode/ +coverage.txt +coverage.out *.exe *.exe~ *.dll *.so *.dylib - -# Test binary, built with `go test -c` +*.db +*.db-journal +*.mmdb *.test - -# Output of the go coverage tool, specifically when used with LiteIDE *.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work -go.work.sum -.tools/ +.env \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml old mode 100644 new mode 100755 index 3228670..60f1183 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,363 +1,61 @@ +version: "2" -# options for analysis running run: - # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 5m - - # exit code when at least one issue was found, default is 1 + go: "1.24" + timeout: 5m + tests: false issues-exit-code: 1 - - # include test files or not, default is true - tests: true - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - easyjson + modules-download-mode: readonly + allow-parallel-runners: true issues: - # Independently from option 'exclude' we use default exclude patterns, - # it can be disabled by this option. To list all - # excluded by default patterns execute 'golangci-lint run --help'. - # Default value for this option is true. - exclude-use-default: false - # Excluding configuration per-path, per-linter, per-text and per-source - exclude-rules: - # Exclude some linters from running on tests files. - - path: _test\.go - linters: - - prealloc - - errcheck + max-issues-per-linter: 0 + max-same-issues: 0 + new: false + fix: false -# output configuration options output: - # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true + formats: + text: + print-linter-name: true + print-issued-lines: true -# all available settings of specific linters -linters-settings: - govet: - # report about shadowed variables - check-shadowing: true +formatters: + exclusions: + paths: + - vendors/ enable: - # report mismatches between assembly files and Go declarations - - asmdecl - # check for useless assignments - - assign - # check for common mistakes using the sync/atomic package - - atomic - # check for non-64-bits-aligned arguments to sync/atomic functions - - atomicalign - # check for common mistakes involving boolean operators - - bools - # check that +build tags are well-formed and correctly located - - buildtag - # detect some violations of the cgo pointer passing rules - - cgocall - # check for unkeyed composite literals - - composites - # check for locks erroneously passed by value - - copylocks - # check for calls of reflect.DeepEqual on error values - - deepequalerrors - # report passing non-pointer or non-error values to errors.As - - errorsas - # find calls to a particular function - - findcall - # report assembly that clobbers the frame pointer before saving it - - framepointer - # check for mistakes using HTTP responses - - httpresponse - # detect impossible interface-to-interface type assertions - - ifaceassert - # check references to loop variables from within nested functions - - loopclosure - # check cancel func returned by context.WithCancel is called - - lostcancel - # check for useless comparisons between functions and nil - - nilfunc - # check for redundant or impossible nil comparisons - - nilness - # check consistency of Printf format strings and arguments - - printf - # check for comparing reflect.Value values with == or reflect.DeepEqual - - reflectvaluecompare - # check for possible unintended shadowing of variables - - shadow - # check for shifts that equal or exceed the width of the integer - - shift - # check for unbuffered channel of os.Signal - - sigchanyzer - # check the argument type of sort.Slice - - sortslice - # check signature of methods of well-known interfaces - - stdmethods - # check for string(int) conversions - - stringintconv - # check that struct field tags conform to reflect.StructTag.Get - - structtag - # report calls to (*testing.T).Fatal from goroutines started by a test. - - testinggoroutine - # check for common mistaken usages of tests and examples - - tests - # report passing non-pointer or non-interface values to unmarshal - - unmarshal - # check for unreachable code - - unreachable - # check for invalid conversions of uintptr to unsafe.Pointer - - unsafeptr - # check for unused results of calls to some functions - - unusedresult - # checks for unused writes - - unusedwrite - disable: - # find structs that would use less memory if their fields were sorted - - fieldalignment - gofmt: - # simplify code: gofmt with '-s' option, true by default - simplify: true - errcheck: - # report about not checking of errors in type assetions: 'a := b.(MyStruct)'; - # default is false: such cases aren't reported by default. - check-type-assertions: true - # report about assignment of errors to blank identifier: 'num, _ := strconv.Atoi(numStr)'; - # default is false: such cases aren't reported by default. - check-blank: true - gocyclo: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 15 - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - prealloc: - # XXX: we don't recommend using this linter before doing performance profiling. - # For most programs usage of prealloc will be a premature optimization. - # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. - # True by default. - simple: true - range-loops: true # Report preallocation suggestions on range loops, true by default - for-loops: true # Report preallocation suggestions on for loops, false by default - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - gci: - # Section configuration to compare against. - # Section names are case-insensitive and may contain parameters in (). - # The default order of sections is 'standard > default > custom > blank > dot', - # If 'custom-order' is 'true', it follows the order of 'sections' option. - # Default: ["standard", "default"] - #sections: - #- standard # Standard section: captures all standard packages. - #- default # Default section: contains all imports that could not be matched to another section type. - #- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. - #- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. - # Skip generated files. - # Default: true - skip-generated: true - # Enable custom order of sections. - # If 'true', make the section order the same as the order of 'sections'. - # Default: false - custom-order: false - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - # Default: [] - means include all rules - includes: - - G101 # Look for hard coded credentials - - G102 # Bind to all interfaces - - G103 # Audit the use of unsafe block - - G104 # Audit errors not checked - - G106 # Audit the use of ssh.InsecureIgnoreHostKey - - G107 # Url provided to HTTP request as taint input - - G108 # Profiling endpoint automatically exposed on /debug/pprof - - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 - - G110 # Potential DoS vulnerability via decompression bomb - - G111 # Potential directory traversal - - G112 # Potential slowloris attack - - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) - - G114 # Use of net/http serve function that has no support for setting timeouts - - G201 # SQL query construction using format string - - G202 # SQL query construction using string concatenation - - G203 # Use of unescaped data in HTML templates - - G204 # Audit use of command execution - - G301 # Poor file permissions used when creating a directory - - G302 # Poor file permissions used with chmod - - G303 # Creating tempfile using a predictable path - - G304 # File path provided as taint input - - G305 # File traversal when extracting zip/tar archive - - G306 # Poor file permissions used when writing to a new file - - G307 # Deferring a method which returns an error - - G401 # Detect the usage of DES, RC4, MD5 or SHA1 - - G402 # Look for bad TLS connection settings - - G403 # Ensure minimum RSA key length of 2048 bits - - G404 # Insecure random number source (rand) - - G501 # Import blocklist: crypto/md5 - - G502 # Import blocklist: crypto/des - - G503 # Import blocklist: crypto/rc4 - - G504 # Import blocklist: net/http/cgi - - G505 # Import blocklist: crypto/sha1 - - G601 # Implicit memory aliasing of items from a range statement - # To specify a set of rules to explicitly exclude. - # Available rules: https://github.com/securego/gosec#available-rules - # Default: [] - excludes: - - G101 # Look for hard coded credentials - - G102 # Bind to all interfaces - - G103 # Audit the use of unsafe block - - G104 # Audit errors not checked - - G106 # Audit the use of ssh.InsecureIgnoreHostKey - - G107 # Url provided to HTTP request as taint input - - G108 # Profiling endpoint automatically exposed on /debug/pprof - - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 - - G110 # Potential DoS vulnerability via decompression bomb - - G111 # Potential directory traversal - - G112 # Potential slowloris attack - - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) - - G114 # Use of net/http serve function that has no support for setting timeouts - - G201 # SQL query construction using format string - - G202 # SQL query construction using string concatenation - - G203 # Use of unescaped data in HTML templates - - G204 # Audit use of command execution - - G301 # Poor file permissions used when creating a directory - - G302 # Poor file permissions used with chmod - - G303 # Creating tempfile using a predictable path - - G304 # File path provided as taint input - - G305 # File traversal when extracting zip/tar archive - - G306 # Poor file permissions used when writing to a new file - - G307 # Deferring a method which returns an error - - G401 # Detect the usage of DES, RC4, MD5 or SHA1 - - G402 # Look for bad TLS connection settings - - G403 # Ensure minimum RSA key length of 2048 bits - - G404 # Insecure random number source (rand) - - G501 # Import blocklist: crypto/md5 - - G502 # Import blocklist: crypto/des - - G503 # Import blocklist: crypto/rc4 - - G504 # Import blocklist: net/http/cgi - - G505 # Import blocklist: crypto/sha1 - - G601 # Implicit memory aliasing of items from a range statement - # Exclude generated files - # Default: false - exclude-generated: true - # Filter out the issues with a lower severity than the given value. - # Valid options are: low, medium, high. - # Default: low - severity: medium - # Filter out the issues with a lower confidence than the given value. - # Valid options are: low, medium, high. - # Default: low - confidence: medium - # Concurrency value. - # Default: the number of logical CPUs usable by the current process. - concurrency: 12 - # To specify the configuration of rules. - config: - # Globals are applicable to all rules. - global: - # If true, ignore #nosec in comments (and an alternative as well). - # Default: false - nosec: true - # Add an alternative comment prefix to #nosec (both will work at the same time). - # Default: "" - "#nosec": "#my-custom-nosec" - # Define whether nosec issues are counted as finding or not. - # Default: false - show-ignored: true - # Audit mode enables addition checks that for normal code analysis might be too nosy. - # Default: false - audit: true - G101: - # Regexp pattern for variables and constants to find. - # Default: "(?i)passwd|pass|password|pwd|secret|token|pw|apiKey|bearer|cred" - pattern: "(?i)example" - # If true, complain about all cases (even with low entropy). - # Default: false - ignore_entropy: false - # Maximum allowed entropy of the string. - # Default: "80.0" - entropy_threshold: "80.0" - # Maximum allowed value of entropy/string length. - # Is taken into account if entropy >= entropy_threshold/2. - # Default: "3.0" - per_char_threshold: "3.0" - # Calculate entropy for first N chars of the string. - # Default: "16" - truncate: "32" - # Additional functions to ignore while checking unhandled errors. - # Following functions always ignored: - # bytes.Buffer: - # - Write - # - WriteByte - # - WriteRune - # - WriteString - # fmt: - # - Print - # - Printf - # - Println - # - Fprint - # - Fprintf - # - Fprintln - # strings.Builder: - # - Write - # - WriteByte - # - WriteRune - # - WriteString - # io.PipeWriter: - # - CloseWithError - # hash.Hash: - # - Write - # os: - # - Unsetenv - # Default: {} - G104: - fmt: - - Fscanf - G111: - # Regexp pattern to find potential directory traversal. - # Default: "http\\.Dir\\(\"\\/\"\\)|http\\.Dir\\('\\/'\\)" - pattern: "custom\\.Dir\\(\\)" - # Maximum allowed permissions mode for os.Mkdir and os.MkdirAll - # Default: "0750" - G301: "0750" - # Maximum allowed permissions mode for os.OpenFile and os.Chmod - # Default: "0600" - G302: "0600" - # Maximum allowed permissions mode for os.WriteFile and ioutil.WriteFile - # Default: "0600" - G306: "0600" - - lll: - # Max line length, lines longer will be reported. - # '\t' is counted as 1 character by default, and can be changed with the tab-width option. - # Default: 120. - line-length: 120 - # Tab width in spaces. - # Default: 1 - tab-width: 1 + - gofmt + - goimports linters: - disable-all: true + settings: + staticcheck: + checks: + - all + - -S1023 + - -ST1000 + - -ST1003 + - -ST1020 + gosec: + excludes: + - G104 + - G115 + - G301 + - G304 + - G306 + - G501 + - G505 + exclusions: + paths: + - vendors/ + default: none enable: - govet - - gofmt - errcheck - misspell - gocyclo - ineffassign - - goimports - - nakedret - unparam - unused - prealloc @@ -367,8 +65,4 @@ linters: - nilerr - errorlint - bodyclose - - exportloopref - - gci - gosec - - lll - fast: false diff --git a/LICENSE b/LICENSE index da2fb54..93caddc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, Mikhail Knyazhev +Copyright (c) 2024-2025, Mikhail Knyazhev Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index 918d40d..03d4159 --- a/Makefile +++ b/Makefile @@ -1,31 +1,31 @@ +SHELL=/bin/bash + + .PHONY: install install: - go install github.com/osspkg/devtool@latest - -.PHONY: setup -setup: - devtool setup-lib + go install go.osspkg.com/goppy/v2/cmd/goppy@latest + goppy setup-lib .PHONY: lint lint: - devtool lint + goppy lint .PHONY: license license: - devtool license + goppy license .PHONY: build build: - devtool build --arch=amd64 + goppy build --arch=amd64 .PHONY: tests tests: - devtool test + goppy test -.PHONY: pre-commite -pre-commite: setup lint build tests +.PHONY: pre-commit +pre-commit: install license lint tests build .PHONY: ci -ci: install setup lint build tests +ci: pre-commit diff --git a/errors.go b/errors.go index 5a5b417..95ced73 100644 --- a/errors.go +++ b/errors.go @@ -1,122 +1,49 @@ /* - * Copyright (c) 2024 Mikhail Knyazhev . All rights reserved. + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ package errors -import ( - e "errors" - "fmt" -) +import "strings" -type err struct { +type errorEntity struct { cause error message string - trace string } func New(message string) error { - return &err{message: message} + return &errorEntity{message: message} } -func (v *err) Error() string { - switch true { - case len(v.message) > 0 && v.cause != nil: - return v.message + ": " + v.cause.Error() + v.trace - case v.cause != nil: - return v.cause.Error() + v.trace +func (v *errorEntity) Error() string { + if v.cause == nil && len(v.message) == 0 { + return "" } - return v.message + v.trace -} - -func (v *err) Cause() error { - return v.cause -} - -func (v *err) Unwrap() error { - return v.cause -} -func (v *err) WithTrace() { - v.trace = runtimeTrace(10) -} + var b strings.Builder -func Trace(cause error, message string, args ...interface{}) error { - v := Wrapf(cause, message, args...) - // nolint: errorlint - if vv, ok := v.(*err); ok { - vv.WithTrace() - return vv + var mw bool + if len(v.message) > 0 { + b.WriteString(v.message) + mw = true } - return v -} -func Wrapf(cause error, message string, args ...interface{}) error { - if cause == nil { - return nil - } - var err0 *err - if len(args) == 0 { - err0 = &err{ - cause: cause, - message: message, - } - } else { - err0 = &err{ - cause: cause, - message: fmt.Sprintf(message, args...), + if v.cause != nil { + if mw { + b.WriteString(": ") } - } - return err0 -} -func Wrap(msg ...error) error { - if len(msg) == 0 { - return nil - } - var err0 error - for _, v := range msg { - if v == nil { - continue - } - if err0 == nil { - err0 = &err{cause: v} - continue - } - err0 = &err{ - cause: v, - message: err0.Error(), - } + b.WriteString(v.cause.Error()) } - return err0 -} -func Unwrap(err error) error { - // nolint: errorlint - if v, ok := err.(interface { - Unwrap() error - }); ok { - return v.Unwrap() - } - return nil + return b.String() } -func Cause(err error) error { - for err != nil { - // nolint: errorlint - v, ok := err.(interface { - Cause() error - }) - if !ok { - return err - } - err = v.Cause() - } - - return nil +func (v *errorEntity) Cause() error { + return v.cause } -func Is(err, target error) bool { - return e.Is(err, target) +func (v *errorEntity) Unwrap() error { + return v.cause } diff --git a/errors_test.go b/errors_test.go index fd8add8..0fdb8f8 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Mikhail Knyazhev . All rights reserved. + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. */ @@ -7,25 +7,22 @@ package errors import ( e "errors" - "strings" + "fmt" "testing" ) func TestUnit_New(t *testing.T) { - type args struct { - message string - } tests := []struct { name string - args args + message string want string wantErr bool }{ - {name: "Case1", args: args{message: "hello"}, want: "hello", wantErr: true}, + {name: "Case1", message: "hello", want: "hello", wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := New(tt.args.message) + err := New(tt.message) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return @@ -39,64 +36,68 @@ func TestUnit_New(t *testing.T) { } func TestUnit_Wrap(t *testing.T) { - type args struct { - msg []error - } tests := []struct { name string - args args + msgs []error want string wantErr bool }{ { name: "Case1", - args: args{msg: nil}, + msgs: nil, want: "", wantErr: false, }, { name: "Case2", - args: args{msg: []error{New("hello"), e.New("world")}}, + msgs: []error{New("hello"), e.New("world")}, want: "hello: world", wantErr: true, }, { name: "Case3", - args: args{msg: []error{New("err1"), e.New("err2"), nil, e.New("err3")}}, + msgs: []error{New("err1"), e.New("err2"), nil, e.New("err3")}, want: "err1: err2: err3", wantErr: true, }, { name: "Case4", - args: args{msg: []error{Wrapf(New("err1"), "err1 message"), + msgs: []error{Wrapf(New("err1"), "err1 message"), Wrapf(e.New("err2"), "err2 message"), - Wrapf(e.New("err3"), "err3 message")}}, + Wrapf(e.New("err3"), "err3 message")}, want: "err1 message: err1: err2 message: err2: err3 message: err3", wantErr: true, }, { name: "Case5", - args: args{msg: []error{nil, nil, nil}}, + msgs: []error{nil, nil, nil}, want: "", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := Wrap(tt.args.msg...) - if (err != nil) != tt.wantErr { - t.Errorf("Wrap() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr && err.Error() != tt.want { - t.Errorf("Wrap() error = %v, want %v", err.Error(), tt.want) - return + err := Wrap(tt.msgs...) + + if tt.wantErr { + if err == nil { + t.Errorf("Wrap() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.want { + t.Errorf("Wrap() error = %v, want %v", err.Error(), tt.want) + } + } else { + if err != nil { + t.Errorf("Wrap() error = %v, wantErr %v", err, tt.wantErr) + } } }) } } -func TestUnit_WrapMessage(t *testing.T) { +func TestUnit_Wrapf(t *testing.T) { type args struct { cause error message string @@ -158,6 +159,7 @@ func TestUnit_CauseUnwrap(t *testing.T) { type fields struct { cause error message string + args []interface{} } tests := []struct { name string @@ -186,7 +188,7 @@ func TestUnit_CauseUnwrap(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := Wrapf(tt.fields.cause, tt.fields.message) + v := Wrapf(tt.fields.cause, tt.fields.message, tt.fields.args...) err := Cause(v) if (err != nil) != tt.wantErr { t.Errorf("Cause() error = %v, wantErr %v", err, tt.wantErr) @@ -235,23 +237,18 @@ func TestUnit_Is(t *testing.T) { } } -func TestUnit_Trace(t *testing.T) { - tests := []struct { - name string - err error - want string - }{ - { - name: "Case1", - err: New("test"), - want: "[trace] go.osspkg.com/errors.TestUnit_Trace.func1", - }, +func TestUnit_As(t *testing.T) { + err0 := New("err0") + err1 := fmt.Errorf("err1") + + var err2 *errorEntity + if !As(err0, &err2) { + t.Errorf("As() error = %v, wantErr %v", err0, err2) + return } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := Trace(tt.err, "msg"); got != nil && !strings.Contains(got.Error(), tt.want) { - t.Errorf("Trace() = %v, want %v", got.Error(), tt.want) - } - }) + + if As(err1, &err2) { + t.Errorf("As() error = %v, wantErr %v", err1, err2) + return } } diff --git a/go.mod b/go.mod index a7d1df5..7346093 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module go.osspkg.com/errors -go 1.21 +go 1.24 diff --git a/operation.go b/operation.go new file mode 100644 index 0000000..140f734 --- /dev/null +++ b/operation.go @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package errors + +import ( + e "errors" + "fmt" +) + +func Wrapf(cause error, message string, args ...interface{}) error { + if cause == nil { + return nil + } + + err := &errorEntity{ + cause: cause, + message: message, + } + + if len(args) > 0 { + err.message = fmt.Sprintf(message, args...) + } + + return err +} + +func Wrap(messages ...error) error { + if len(messages) == 0 { + return nil + } + + var err error + + for _, msg := range messages { + if msg == nil { + continue + } + if err == nil { + err = &errorEntity{cause: msg} + continue + } + err = &errorEntity{ + cause: msg, + message: err.Error(), + } + } + + return err +} + +func Unwrap(err error) error { + if err == nil { + return nil + } + + if v, ok := err.(Unwrapper); ok { + return v.Unwrap() + } + + return nil +} + +func Cause(err error) error { + if err == nil { + return nil + } + + for { + if c, ok := err.(Causer); ok && c != nil { + err = c.Cause() + continue + } + + return err + } +} + +func Is(err, target error) bool { + return e.Is(err, target) +} + +func As(err error, target any) bool { + return e.As(err, target) +} diff --git a/queue.go b/queue.go new file mode 100644 index 0000000..cac3b69 --- /dev/null +++ b/queue.go @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package errors + +func Queue(calls ...func() error) error { + for _, call := range calls { + if err := call(); err != nil { + return err + } + } + return nil +} diff --git a/queue_test.go b/queue_test.go new file mode 100644 index 0000000..81cb766 --- /dev/null +++ b/queue_test.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package errors_test + +import ( + "testing" + + "go.osspkg.com/errors" +) + +func TestUnit_Queue(t *testing.T) { + incr1 := 0 + incr2 := 0 + + err := errors.Queue( + func() error { + incr1++ + return errors.New("1") + }, + func() error { + incr2++ + return errors.New("2") + }, + ) + + if err == nil { + t.Fatalf("err = nil, want err") + } + + if incr1 != 1 { + t.Fatalf("incr1 = %v, want %v", incr1, 1) + } + + if incr2 != 0 { + t.Fatalf("incr2 = %v, want %v", incr2, 0) + } +} diff --git a/trace.go b/trace.go deleted file mode 100644 index 1ef1256..0000000 --- a/trace.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2024 Mikhail Knyazhev . All rights reserved. - * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. - */ - -package errors - -import ( - "fmt" - "runtime" -) - -func runtimeTrace(c int) string { - list := make([]uintptr, c) - - n := runtime.Callers(4, list) - frame := runtime.CallersFrames(list[:n]) - - result := "" - for { - v, ok := frame.Next() - if !ok { - break - } - result += fmt.Sprintf("\n\t[trace] %s:%d", v.Function, v.Line) - } - return result -} diff --git a/types.go b/types.go new file mode 100644 index 0000000..83c8307 --- /dev/null +++ b/types.go @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024-2025 Mikhail Knyazhev . All rights reserved. + * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. + */ + +package errors + +type Causer interface { + Cause() error +} + +type Unwrapper interface { + Unwrap() error +}