diff --git a/exerr/builder.go b/exerr/builder.go index 651b2e0..ab982fa 100644 --- a/exerr/builder.go +++ b/exerr/builder.go @@ -1 +1,416 @@ package exerr + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/rs/zerolog" + "go.mongodb.org/mongo-driver/bson/primitive" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "net/http" + "os" + "runtime/debug" + "strings" + "time" +) + +// +// ==== USAGE ===== +// +// If some method returns an error _always wrap it into an bmerror: +// value, err := do_something(..) +// if err != nil { +// return nil, bmerror.Wrap(err, "do something failed").Build() +// } +// +// If possible add metadata to the error (eg the id that was not found, ...), the methods are the same as in zerolog +// return nil, bmerror.Wrap(err, "do something failed").Str("someid", id).Int("count", in.Count).Build() +// +// You can change the errortype with `.User()` and `.System()` (User-errors are 400 and System-errors 500) +// You can also manually set the statuscode with `.WithStatuscode(http.NotFound)` +// You can set the type with `WithType(..)` +// +// New Errors (that don't wrap an existing err object) are created with New +// return nil, bmerror.New(bmerror.ErrInternal, "womethign wen horrible wrong").Build() +// You can eitehr use an existing ErrorType, the "catch-all" ErrInternal, or add you own ErrType in consts.go +// +// All errors should be handled one of the following four ways: +// - return the error to the caller and let him handle it: +// (also auto-prints the error to the log) +// => Wrap/New + Build +// - Print the error +// (also auto-sends it to the error-service) +// This is useful for errors that happen asynchron or are non-fatal for the current request +// => Wrap/New + Print +// - Return the error to the Rest-API caller +// (also auto-prints the error to the log) +// (also auto-sends it to the error-service) +// => Wrap/New + Output +// - Print and stop the service +// (also auto-sends it to the error-service) +// => Wrap/New + Fatal +// + +var stackSkipLogger zerolog.Logger + +func init() { + cw := zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: "2006-01-02 15:04:05 Z07:00", + } + + multi := zerolog.MultiLevelWriter(cw) + stackSkipLogger = zerolog.New(multi).With().Timestamp().CallerWithSkipFrameCount(4).Logger() +} + +type Builder struct { + bmerror *bringmanError + + containsGinData bool +} + +func Get(err error) *Builder { + return &Builder{bmerror: fromError(err)} +} + +func New(t ErrorType, msg string) *Builder { + return &Builder{bmerror: newBringmanErr(CatSystem, t, msg)} +} + +func Wrap(err error, msg string) *Builder { + return &Builder{bmerror: fromError(err).wrap(msg, CatWrap, 1)} +} + +// ---------------------------------------------------------------------------- + +func (b *Builder) WithType(t ErrorType) *Builder { + b.bmerror.Type = t + return b +} + +func (b *Builder) WithStatuscode(status int) *Builder { + b.bmerror.StatusCode = status + return b +} + +func (b *Builder) WithMessage(msg string) *Builder { + b.bmerror.Message = msg + return b +} + +// ---------------------------------------------------------------------------- + +// Err changes the Severity to ERROR (default) +// The error will be: +// +// - On Build(): +// +// - Short-Logged as Err +// +// - On Print(): +// +// - Logged as Err +// +// - Send to the error-service +// +// - On Output(): +// +// - Logged as Err +// +// - Send to the error-service +func (b *Builder) Err() *Builder { + b.bmerror.Severity = SevErr + return b +} + +// Warn changes the Severity to WARN +// The error will be: +// +// - On Build(): +// +// - -(nothing)- +// +// - On Print(): +// +// - Short-Logged as Warn +// +// - On Output(): +// +// - Logged as Warn +func (b *Builder) Warn() *Builder { + b.bmerror.Severity = SevWarn + return b +} + +// Info changes the Severity to INFO +// The error will be: +// +// - On Build(): +// +// - -(nothing)- +// +// - On Print(): +// +// - -(nothing)- +// +// - On Output(): +// +// - -(nothing)- +func (b *Builder) Info() *Builder { + b.bmerror.Severity = SevInfo + return b +} + +// ---------------------------------------------------------------------------- + +// User sets the Category to CatUser +// +// Errors with category +func (b *Builder) User() *Builder { + b.bmerror.Category = CatUser + return b +} + +func (b *Builder) System() *Builder { + b.bmerror.Category = CatSystem + return b +} + +// ---------------------------------------------------------------------------- + +func (b *Builder) Id(key string, val fmt.Stringer) *Builder { + return b.addMeta(key, MDTID, newIDWrap(val)) +} + +func (b *Builder) StrPtr(key string, val *string) *Builder { + return b.addMeta(key, MDTStringPtr, val) +} + +func (b *Builder) Str(key string, val string) *Builder { + return b.addMeta(key, MDTString, val) +} + +func (b *Builder) Int(key string, val int) *Builder { + return b.addMeta(key, MDTInt, val) +} + +func (b *Builder) Int8(key string, val int8) *Builder { + return b.addMeta(key, MDTInt8, val) +} + +func (b *Builder) Int16(key string, val int16) *Builder { + return b.addMeta(key, MDTInt16, val) +} + +func (b *Builder) Int32(key string, val int32) *Builder { + return b.addMeta(key, MDTInt32, val) +} + +func (b *Builder) Int64(key string, val int64) *Builder { + return b.addMeta(key, MDTInt64, val) +} + +func (b *Builder) Float32(key string, val float32) *Builder { + return b.addMeta(key, MDTFloat32, val) +} + +func (b *Builder) Float64(key string, val float64) *Builder { + return b.addMeta(key, MDTFloat64, val) +} + +func (b *Builder) Bool(key string, val bool) *Builder { + return b.addMeta(key, MDTBool, val) +} + +func (b *Builder) Bytes(key string, val []byte) *Builder { + return b.addMeta(key, MDTBytes, val) +} + +func (b *Builder) ObjectID(key string, val primitive.ObjectID) *Builder { + return b.addMeta(key, MDTObjectID, val) +} + +func (b *Builder) Time(key string, val time.Time) *Builder { + return b.addMeta(key, MDTTime, val) +} + +func (b *Builder) Dur(key string, val time.Duration) *Builder { + return b.addMeta(key, MDTDuration, val) +} + +func (b *Builder) Strs(key string, val []string) *Builder { + return b.addMeta(key, MDTStringArray, val) +} + +func (b *Builder) Ints(key string, val []int) *Builder { + return b.addMeta(key, MDTIntArray, val) +} + +func (b *Builder) Ints32(key string, val []int32) *Builder { + return b.addMeta(key, MDTInt32Array, val) +} + +func (b *Builder) Type(key string, cls interface{}) *Builder { + return b.addMeta(key, MDTString, fmt.Sprintf("%T", cls)) +} + +func (b *Builder) Interface(key string, val interface{}) *Builder { + return b.addMeta(key, MDTAny, newAnyWrap(val)) +} + +func (b *Builder) Any(key string, val any) *Builder { + return b.addMeta(key, MDTAny, newAnyWrap(val)) +} + +func (b *Builder) Stack() *Builder { + return b.addMeta("@Stack", MDTString, string(debug.Stack())) +} + +func (b *Builder) Errs(key string, val []error) *Builder { + for i, valerr := range val { + b.addMeta(fmt.Sprintf("%v[%v]", key, i), MDTString, Get(valerr).toBMError().FormatLog(LogPrintFull)) + } + return b +} + +func (b *Builder) GinReq(ctx context.Context, g *gin.Context, req *http.Request) *Builder { + if v := ctx.Value("start_timestamp"); v != nil { + if t, ok := v.(time.Time); ok { + b.Time("ctx.startTimestamp", t) + b.Time("ctx.endTimestamp", time.Now()) + } + } + b.Str("gin.method", req.Method) + b.Str("gin.path", g.FullPath()) + b.Str("gin.header", formatHeader(g.Request.Header)) + if req.URL != nil { + b.Str("gin.url", req.URL.String()) + } + if ctxVal := g.GetString("apiversion"); ctxVal != "" { + b.Str("gin.context.apiversion", ctxVal) + } + if ctxVal := g.GetString("uid"); ctxVal != "" { + b.Str("gin.context.uid", ctxVal) + } + if ctxVal := g.GetString("fcmId"); ctxVal != "" { + b.Str("gin.context.fcmid", ctxVal) + } + if ctxVal := g.GetString("reqid"); ctxVal != "" { + b.Str("gin.context.reqid", ctxVal) + } + if req.Method != "GET" && req.Body != nil && req.Header.Get("Content-Type") == "application/json" { + if brc, ok := req.Body.(langext.BufferedReadCloser); ok { + if bin, err := brc.BufferedAll(); err == nil { + if len(bin) < 16*1024 { + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, bin, "", " ") + if err == nil { + b.Str("gin.body", string(prettyJSON.Bytes())) + } else { + b.Bytes("gin.body", bin) + } + } else { + b.Str("gin.body", fmt.Sprintf("[[%v bytes]]", len(bin))) + } + } + } + } + + b.containsGinData = true + return b +} + +func formatHeader(header map[string][]string) string { + ml := 1 + for k, _ := range header { + if len(k) > ml { + ml = len(k) + } + } + r := "" + for k, v := range header { + if r != "" { + r += "\n" + } + for _, hval := range v { + value := hval + value = strings.ReplaceAll(value, "\n", "\\n") + value = strings.ReplaceAll(value, "\r", "\\r") + value = strings.ReplaceAll(value, "\t", "\\t") + r += langext.StrPadRight(k, " ", ml) + " := " + value + } + } + return r +} + +// ---------------------------------------------------------------------------- + +// Build creates a new error, ready to pass up the stack +// If the errors is not SevWarn or SevInfo it gets also logged (in short form, without stacktrace) onto stdout +func (b *Builder) Build() error { + if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { + b.bmerror.ShortLog(stackSkipLogger.Error()) + } + + b.CallListener(MethodBuild) + + return b.bmerror.ToGrpcError() +} + +// Output prints the error onto the gin stdout. +// The error also gets printed to stdout/stderr +// If the error is SevErr|SevFatal we also send it to the error-service +func (b *Builder) Output(ctx context.Context, g *gin.Context) { + if !b.containsGinData && g.Request != nil { + // Auto-Add gin metadata if the caller hasn't already done it + b.GinReq(ctx, g, g.Request) + } + + b.bmerror.Output(ctx, g) + + if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { + b.bmerror.Log(stackSkipLogger.Error()) + } else if b.bmerror.Severity == SevWarn { + b.bmerror.Log(stackSkipLogger.Warn()) + } + + b.CallListener(MethodOutput) +} + +// Print prints the error +// If the error is SevErr we also send it to the error-service +func (b *Builder) Print() { + if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { + b.bmerror.Log(stackSkipLogger.Error()) + } else if b.bmerror.Severity == SevWarn { + b.bmerror.ShortLog(stackSkipLogger.Warn()) + } + + b.CallListener(MethodPrint) +} + +func (b *Builder) Format(level LogPrintLevel) string { + return b.bmerror.FormatLog(level) +} + +// Fatal prints the error and terminates the program +// If the error is SevErr we also send it to the error-service +func (b *Builder) Fatal() { + b.bmerror.Severity = SevFatal + b.bmerror.Log(stackSkipLogger.WithLevel(zerolog.FatalLevel)) + + b.CallListener(MethodFatal) + + os.Exit(1) +} + +// ---------------------------------------------------------------------------- + +func (b *Builder) addMeta(key string, mdtype metaDataType, val interface{}) *Builder { + b.bmerror.Meta.add(key, mdtype, val) + return b +} + +func (b *Builder) toBMError() BMError { + return b.bmerror.ToBMError() +} diff --git a/exerr/data.go b/exerr/data.go new file mode 100644 index 0000000..d4996fa --- /dev/null +++ b/exerr/data.go @@ -0,0 +1,32 @@ +package exerr + +type ErrorCategory struct{ Category string } + +var ( + CatWrap = ErrorCategory{"Wrap"} // The error is simply wrapping another error (e.g. when a grpc call returns an error) + CatSystem = ErrorCategory{"System"} // An internal system error (e.g. connection to db failed) + CatUser = ErrorCategory{"User"} // The user (the API caller) did something wrong (e.g. he has no permissions to do this) + CatForeign = ErrorCategory{"Foreign"} // A foreign error that some component threw (e.g. an unknown mongodb error), happens if we call Wrap(..) on an non-bmerror value +) + +var AllCategories = []ErrorCategory{CatWrap, CatSystem, CatUser, CatForeign} + +type ErrorSeverity struct{ Severity string } + +var ( + SevTrace = ErrorSeverity{"Trace"} + SevDebug = ErrorSeverity{"Debug"} + SevInfo = ErrorSeverity{"Info"} + SevWarn = ErrorSeverity{"Warn"} + SevErr = ErrorSeverity{"Err"} + SevFatal = ErrorSeverity{"Fatal"} +) + +var AllSeverities = []ErrorSeverity{SevTrace, SevDebug, SevInfo, SevWarn, SevErr, SevFatal} + +type ErrorType struct{ Key string } + +var ( + TypeInternal = ErrorType{"Internal"} + // other values come from pkgconfig +) diff --git a/exerr/errinit.go b/exerr/errinit.go index fe7c38f..8330d25 100644 --- a/exerr/errinit.go +++ b/exerr/errinit.go @@ -1,17 +1,50 @@ package exerr type ErrorPackageConfig struct { - LogTraces bool - RecursiveErrors bool + ZeroLogTraces bool // autom print zerolog logs on CreateError + RecursiveErrors bool // errors contains their Origin-Error + Types []ErrorType // all available error-types } +type ErrorPackageConfigInit struct { + LogTraces bool + RecursiveErrors bool + InitTypes func(_ func(_ string) ErrorType) +} + +var initialized = false + var pkgconfig = ErrorPackageConfig{ - LogTraces: true, + ZeroLogTraces: true, + RecursiveErrors: true, + Types: []ErrorType{TypeInternal}, } // Init initializes the exerr packages // Must be called at the program start, before (!) any errors // Is not thread-safe -func Init(cfg ErrorPackageConfig) { - pkgconfig = cfg +func Init(cfg ErrorPackageConfigInit) { + if initialized { + panic("Cannot re-init error package") + } + + types := pkgconfig.Types + + fnAddType := func(v string) ErrorType { + et := ErrorType{v} + types = append(types, et) + return et + } + + if cfg.InitTypes != nil { + cfg.InitTypes(fnAddType) + } + + pkgconfig = ErrorPackageConfig{ + ZeroLogTraces: cfg.LogTraces, + RecursiveErrors: cfg.RecursiveErrors, + Types: types, + } + + initialized = true } diff --git a/exerr/exerr.go b/exerr/exerr.go index aac4581..bc3e832 100644 --- a/exerr/exerr.go +++ b/exerr/exerr.go @@ -11,11 +11,12 @@ type ExErr struct { Category ErrorCategory `json:"category"` Severity ErrorSeverity `json:"severity"` Type ErrorType `json:"type"` - Source ErrorSource `json:"source"` Message string `json:"message"` Caller string `json:"caller"` + OriginalError *ExErr + Meta MetaMap `json:"meta"` } diff --git a/exerr/listener.go b/exerr/listener.go index 67a4ae6..b996be4 100644 --- a/exerr/listener.go +++ b/exerr/listener.go @@ -1,6 +1,8 @@ package exerr -import "sync" +import ( + "sync" +) type Method string @@ -11,7 +13,7 @@ const ( MethodFatal Method = "FATAL" ) -type Listener = func(method Method, v BMError) +type Listener = func(method Method, v ExErr) var listenerLock = sync.Mutex{} var listener = make([]Listener, 0) diff --git a/exerr/meta.go b/exerr/meta.go index e2f2f4e..2eee44c 100644 --- a/exerr/meta.go +++ b/exerr/meta.go @@ -1,8 +1,6 @@ package exerr import ( - "bringman.de/common/shared/langext" - spbmodels "bringman.de/proto/common/models" "encoding/hex" "encoding/json" "errors" @@ -11,6 +9,7 @@ import ( "github.com/rs/zerolog/log" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" + "gogs.mikescher.com/BlackForestBytes/goext/langext" "strconv" "strings" "time" diff --git a/exerr/wrapper.go b/exerr/wrapper.go index 55dd1bb..0880fef 100644 --- a/exerr/wrapper.go +++ b/exerr/wrapper.go @@ -1,10 +1,10 @@ package exerr import ( - "bringman.de/common/shared/langext" "encoding/json" "fmt" "github.com/rs/zerolog/log" + "gogs.mikescher.com/BlackForestBytes/goext/langext" "strings" ) diff --git a/go.mod b/go.mod index 18a66a5..64de253 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,25 @@ module gogs.mikescher.com/BlackForestBytes/goext go 1.19 require ( - golang.org/x/sys v0.3.0 + golang.org/x/sys v0.5.0 golang.org/x/term v0.3.0 ) require ( + github.com/golang/snappy v0.0.1 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rs/zerolog v1.29.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.mongodb.org/mongo-driver v1.11.2 // indirect golang.org/x/crypto v0.4.0 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/text v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 894d7b9..a7e5151 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,78 @@ +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= +github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw= +go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/langext/array.go b/langext/array.go index 3a28739..0701cfd 100644 --- a/langext/array.go +++ b/langext/array.go @@ -282,3 +282,11 @@ func ArrSum[T NumberConstraint](arr []T) T { } return r } + +func ArrRemove[T comparable](arr []T, needle T) []T { + idx := ArrFirstIndex(arr, needle) + if idx >= 0 { + return append(arr[:idx], arr[idx+1:]...) + } + return arr +}