From ca5c1087ef8beea6d11546d5a78c753ac9d6aa11 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 22 Jan 2024 18:27:50 +0200 Subject: [PATCH 01/88] string Len assert benchmark --- assert/assert_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/assert/assert_test.go b/assert/assert_test.go index 06ec08d..897dd6c 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -260,6 +260,13 @@ func BenchmarkSLen(b *testing.B) { } } +func BenchmarkLen(b *testing.B) { + s := "len" + for n := 0; n < b.N; n++ { + assert.Len(s, 3) + } +} + func BenchmarkSLen_thatVersion(b *testing.B) { d := []byte{1, 2} for n := 0; n < b.N; n++ { From 10bd9be45b287b86e7a4ff20bf8e5b9b2acfbd36 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 25 Jan 2024 16:53:22 +0200 Subject: [PATCH 02/88] documentation about auto-generated messages, better layout, etc. --- assert/assert.go | 203 +++++++++++++++++++++++++++++++---------------- 1 file changed, 133 insertions(+), 70 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index d5d094d..988d5b5 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -107,9 +107,10 @@ var ( ) var ( - // These two are our indexing system for default asserter. Note also the - // mutex blew. All of this is done to keep client package race detector + // These two are our indexing system for default asserter. Note, also the + // mutex below. All of this is done to keep client package race detector // cool. + // // Plain // Production // Development @@ -170,12 +171,14 @@ const ( // } // // Because PushTester returns PopTester it allows us to merge these two calls to -// one line. See the first t.Run call. NOTE. More information in PopTester. +// one line. See the first t.Run call above. See more information in PopTester. // // PushTester allows you to change the current default asserter by accepting it -// as a second argument. NOTE. That it change the default asserter for whole -// package. The argument is mainly for temporary development use and isn't not -// preferrred API usage. +// as a second argument. +// +// Note, that the second argument, if given, changes the default asserter for +// whole package. The argument is mainly for temporary development use and isn't +// not preferred API usage. func PushTester(t testing.TB, a ...defInd) function { if len(a) > 0 { SetDefault(a[0]) @@ -235,7 +238,7 @@ func PopTester() { msg = "test panic catch" } - // First, print the call stack. Note. that we aren't support full error + // First, print the call stack. Note, that we aren't support full error // tracing with unit test logging. However, using it has proved the top // level error stack as more enough. Even so that we could consider using // it for normal error stack straces if it would be possible. @@ -243,7 +246,7 @@ func PopTester() { debug.PrintStackForTest(os.Stderr, stackLvl) // Now that call stack errors are printed, if any. Let's print the actual - // line that caused the error, i.e., was throwing the error. Note that we + // line that caused the error, i.e., was throwing the error. Note, that we // are here in the 'catch-function'. const framesToSkip = 4 // how many fn calls there is before FuncName call fatal("assertion catching: "+msg, framesToSkip) @@ -351,7 +354,8 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { } // NotEqual asserts that the values aren't equal. If they are it panics/errors -// (current Asserter) with the given message. +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. func NotEqual[T comparable](val, want T, a ...any) { if want == val { defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) @@ -359,8 +363,9 @@ func NotEqual[T comparable](val, want T, a ...any) { } } -// Equal asserts that the values are equal. If not it panics/errors (current -// Asserter) with the given message. +// Equal asserts that the values are equal. If not it panics/errors (according +// the current Asserter) with the auto-generated message. You can append the +// generated got-want message by using optional message arguments. func Equal[T comparable](val, want T, a ...any) { if want != val { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) @@ -369,7 +374,9 @@ func Equal[T comparable](val, want T, a ...any) { } // DeepEqual asserts that the (whatever) values are equal. If not it -// panics/errors (current Asserter) with the given message. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) @@ -378,8 +385,12 @@ func DeepEqual(val, want any, a ...any) { } // NotDeepEqual asserts that the (whatever) values are equal. If not it -// panics/errors (current Asserter) with the given message. NOTE, it uses -// reflect.DeepEqual which means that also the types must be the same: +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note, it uses reflect.DeepEqual which means that also the types must be the +// same: // // assert.DeepEqual(pubKey, ed25519.PublicKey(pubKeyBytes)) func NotDeepEqual(val, want any, a ...any) { @@ -390,9 +401,12 @@ func NotDeepEqual(val, want any, a ...any) { } // Len asserts that the length of the string is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func Len(obj string, length int, a ...any) { l := len(obj) @@ -403,9 +417,12 @@ func Len(obj string, length int, a ...any) { } // Longer asserts that the length of the string is longer to the given. If not -// it panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// it panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func Longer(obj string, length int, a ...any) { l := len(obj) @@ -416,9 +433,12 @@ func Longer(obj string, length int, a ...any) { } // Shorter asserts that the length of the string is shorter to the given. If not -// it panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// it panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func Shorter(obj string, length int, a ...any) { l := len(obj) @@ -429,9 +449,12 @@ func Shorter(obj string, length int, a ...any) { } // SLen asserts that the length of the slice is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func SLen[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -442,9 +465,12 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { } // SLonger asserts that the length of the slice is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func SLonger[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -454,10 +480,13 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { } } -// SShorter asserts that the length of the slice is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// SShorter asserts that the length of the slice is equal to the given. If not +// it panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func SShorter[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -468,9 +497,12 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { } // MLen asserts that the length of the map is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -481,9 +513,12 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // MLonger asserts that the length of the map is longer to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -494,9 +529,12 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // MShorter asserts that the length of the map is shorter to the given. If not -// it panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// it panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -507,9 +545,12 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // CLen asserts that the length of the chan is equal to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func CLen[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -520,9 +561,12 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { } // CLonger asserts that the length of the chan is longer to the given. If not it -// panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func CLonger[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -533,9 +577,12 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { } // CShorter asserts that the length of the chan is shorter to the given. If not -// it panics/errors (current Asserter) with the given message. Note! This is -// reasonably fast but not as fast as 'That' because of lacking inlining for the -// current implementation of Go's type parametric functions. +// it panics/errors (according the current Asserter) with the auto-generated +// message. You can append the generated got-want message by using optional +// message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func CShorter[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -559,7 +606,8 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) } // NotEmpty asserts that the string is not empty. If it is, it panics/errors -// (current Asserter) with the given message. +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. func NotEmpty(obj string, a ...any) { if obj == "" { defMsg := assertionMsg + ": string shouldn't be empty" @@ -568,7 +616,8 @@ func NotEmpty(obj string, a ...any) { } // Empty asserts that the string is empty. If it is NOT, it panics/errors -// (current Asserter) with the given message. +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. func Empty(obj string, a ...any) { if obj != "" { defMsg := assertionMsg + ": string should be empty" @@ -577,9 +626,11 @@ func Empty(obj string, a ...any) { } // SNotEmpty asserts that the slice is not empty. If it is, it panics/errors -// (current Asserter) with the given message. Note! This is reasonably fast but -// not as fast as 'That' because of lacking inlining for the current -// implementation of Go's type parametric functions. +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func SNotEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) @@ -590,9 +641,13 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { } // MNotEmpty asserts that the map is not empty. If it is, it panics/errors -// (current Asserter) with the given message. Note! This is reasonably fast but -// not as fast as 'That' because of lacking inlining for the current -// implementation of Go's type parametric functions. +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. +// You can append the generated got-want message by using optional message +// arguments. +// +// Note! This is reasonably fast but not as fast as 'That' because of lacking +// inlining for the current implementation of Go's type parametric functions. func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) @@ -604,9 +659,13 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { // NoError asserts that the error is nil. If is not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a -// single 'if-statement' that is almost nothing. Note. We recommend that you -// prefer try.To every case even in tests because they work exactly the same -// during the test runs and you can use same code for both: runtime and tests. +// single 'if-statement' that is almost nothing. +// +// Note. We recommend that you prefer try.To. They work exactly the same during +// the test runs and you can use the same code for both: runtime and tests. +// However, there are cases that you want assert that there is no error in cases +// where fast fail and immediate stop of execution is wanted at runtime. With +// asserts you get the file location as well. (See the asserters). func NoError(err error, a ...any) { if err != nil { defMsg := "NoError:" + assertionMsg + ": " + err.Error() @@ -662,13 +721,17 @@ func Default() Asserter { // SetDefault set the current default asserter for the package. For example, you // might set it to panic about every assertion fault, and in other cases, throw -// an error, and print the call stack immediately when assert occurs. Note, that -// if you are using tracers you might get two call stacks, so test what's best -// for your case. Tip. If our own packages (client packages for assert) have -// lots of parallel testing and race detection, please try to use same asserter -// for allo foot hem and do it only one in TestMain, or in init. +// an error, and print the call stack immediately when assert occurs. +// +// Note, that if you are using tracers you might get two call stacks, so test +// what's best for your case. +// +// Tip. If our own packages (client packages for assert) have lots of parallel +// testing and race detection, please try to use same asserter for all of them +// and do it only one in TestMain, or in init. // -// SetDefault(assert.TestFull) +// func TestMain(m *testing.M) { +// SetDefault(assert.TestFull) func SetDefault(i defInd) Asserter { // pkg lvl lock to allow only one pkg client call this at the time mu.Lock() @@ -699,7 +762,7 @@ var mapDefIndToString = map[defInd]string{ Debug: "Debug", } -func AsserterString() string { +func defaultAsserterString() string { return mapDefIndToString[def] } @@ -745,7 +808,7 @@ type Number interface { // String is part of the flag interfaces func (f *flagAsserter) String() string { - return AsserterString() + return defaultAsserterString() } // Get is part of the flag interfaces, getter. From d07765746872b47b782d7edf95a093109e9782d0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 25 Jan 2024 16:54:34 +0200 Subject: [PATCH 03/88] better documentation, rm not used function --- assert/asserter.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/assert/asserter.go b/assert/asserter.go index c085b49..fab0829 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "reflect" "github.com/lainio/err2/internal/debug" "github.com/lainio/err2/internal/str" @@ -47,6 +46,13 @@ const ( // every test log or result output has 4 spaces in them const officialTestOutputPrefix = " " +// reportAssertionFault reports assertion fault according the current asserter +// and its config. If extra argumnets are given (a ...any) and the first is +// string, it's treated as format string and following args as its parameters. +// +// Note. We use the pattern where we build defaultMsg argument reaady in cases +// like 'got: X, want: Y'. This hits two birds with one stone: we have automatic +// and correct assert messages, and we can add information to it if we want to. func (asserter Asserter) reportAssertionFault(defaultMsg string, a ...any) { if asserter.hasStackTrace() { if asserter.isUnitTesting() { @@ -65,6 +71,8 @@ func (asserter Asserter) reportAssertionFault(defaultMsg string, a ...any) { defaultMsg = asserter.callerInfo(defaultMsg) } if len(a) > 0 { + // we concat given format string to the in cases we + // have got: want: pattern in use, i.e. best from both worlds if format, ok := a[0].(string); ok { asserter.reportPanic(fmt.Sprintf(format, a[1:]...)) } else { @@ -75,16 +83,6 @@ func (asserter Asserter) reportAssertionFault(defaultMsg string, a ...any) { } } -func getLen(x any) (ok bool, length int) { - v := reflect.ValueOf(x) - defer func() { - if e := recover(); e != nil { - ok = false - } - }() - return true, v.Len() -} - func (asserter Asserter) reportPanic(s string) { if asserter.isUnitTesting() && asserter.hasCallerInfo() { fmt.Fprintln(os.Stderr, officialTestOutputPrefix+s) From fd0a95f72a932316145d8ba1f3d3c44718123067 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 26 Jan 2024 17:19:12 +0200 Subject: [PATCH 04/88] given assert msg is concat to auto-generated msg --- assert/assert.go | 4 +++- assert/assert_test.go | 17 +++++++++++++++-- assert/asserter.go | 8 ++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 988d5b5..e1531af 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -151,6 +151,8 @@ const ( gotWantFmt = ": got '%v', want '%v'" gotWantLongerFmt = ": got '%v', should be longer than '%v'" gotWantShorterFmt = ": got '%v', should be shorter than '%v'" + + conCatErrStr = ": " ) // PushTester sets the current testing context for default asserter. This must @@ -668,7 +670,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { // asserts you get the file location as well. (See the asserters). func NoError(err error, a ...any) { if err != nil { - defMsg := "NoError:" + assertionMsg + ": " + err.Error() + defMsg := "NoError:" + assertionMsg + conCatErrStr + err.Error() Default().reportAssertionFault(defMsg, a...) } } diff --git a/assert/assert_test.go b/assert/assert_test.go index 897dd6c..3314348 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -13,12 +13,12 @@ func ExampleThat() { sample := func() (err error) { defer err2.Handle(&err) - assert.That(false, "assertion test") + assert.That(false, "optional message") return err } err := sample() fmt.Printf("%v", err) - // Output: testing: run example: assertion test + // Output: testing: run example: assert_test.go:16: ExampleThat.func1(): That: assertion violation: optional message } func ExampleNotNil() { @@ -178,6 +178,19 @@ func ExampleMShorter() { // Output: sample: assert_test.go:172: ExampleMShorter.func1(): assertion violation: got '1', should be shorter than '1' } +func ExampleSShorter() { + sample := func(b []byte) (err error) { + defer err2.Handle(&err, "sample") + + assert.SShorter(b, 2) // ok + assert.SShorter(b, 0, "optional message (%s)", "test_str") // not ok + return err + } + err := sample([]byte{01}) // len = 1 + fmt.Printf("%v", err) + // Output: sample: assert_test.go:186: ExampleSShorter.func1(): assertion violation: got '1', should be shorter than '0': optional message (test_str) +} + func assertZero(i int) { assert.Zero(i) } diff --git a/assert/asserter.go b/assert/asserter.go index fab0829..62a9212 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -7,6 +7,7 @@ import ( "github.com/lainio/err2/internal/debug" "github.com/lainio/err2/internal/str" + "github.com/lainio/err2/internal/x" ) // Asserter is type for asserter object guided by its flags. @@ -71,12 +72,11 @@ func (asserter Asserter) reportAssertionFault(defaultMsg string, a ...any) { defaultMsg = asserter.callerInfo(defaultMsg) } if len(a) > 0 { - // we concat given format string to the in cases we - // have got: want: pattern in use, i.e. best from both worlds if format, ok := a[0].(string); ok { - asserter.reportPanic(fmt.Sprintf(format, a[1:]...)) + f := x.Whom(defaultMsg != "", defaultMsg+conCatErrStr+format, format) + asserter.reportPanic(fmt.Sprintf(f, a[1:]...)) } else { - asserter.reportPanic(fmt.Sprintln(a...)) + asserter.reportPanic(fmt.Sprintln(append([]any{defaultMsg}, a...))) } } else { asserter.reportPanic(defaultMsg) From 41cda29bef679ae67d4986d5b66756a7d5ba611e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 10 Feb 2024 11:49:37 +0200 Subject: [PATCH 05/88] better documentation INil and INotNil --- assert/assert.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assert/assert.go b/assert/assert.go index e1531af..7a2c5bb 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -303,6 +303,11 @@ func Nil[T any](p *T, a ...any) { // INil asserts that the interface value IS nil. If it is it panics/errors // (default Asserter) with the given message. +// +// Note, use this only for real interface types. Go's interface's has two values +// so this won't work e.g. slices! Read more information about interface type. +// +// https://go.dev/doc/faq#nil_error func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" @@ -312,6 +317,11 @@ func INil(i any, a ...any) { // INotNil asserts that the interface value is NOT nil. If it is it // panics/errors (default Asserter) with the given message. +// +// Note, use this only for real interface types. Go's interface's has two values +// so this won't work e.g. slices! Read more information about interface type. +// +// https://go.dev/doc/faq#nil_error func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" From 287377666f0f3cce72801920e958561d05db5539 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 10 Feb 2024 11:52:56 +0200 Subject: [PATCH 06/88] rm not used func args (linter) --- err2_test.go | 16 ++++++++-------- try/out_test.go | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/err2_test.go b/err2_test.go index 2d2c36b..aa1f7e6 100644 --- a/err2_test.go +++ b/err2_test.go @@ -292,7 +292,7 @@ func TestPanickingCatchAll(t *testing.T) { func() { defer err2.Catch( err2.Noop, - func(v any) {}, + func(any) {}, ) panic("panic") }, @@ -304,7 +304,7 @@ func TestPanickingCatchAll(t *testing.T) { func() { defer err2.Catch( err2.Err(func(error) {}), // Using simplifier - func(v any) {}, + func(any) {}, ) var b []byte b[0] = 0 @@ -453,7 +453,7 @@ func TestPanicking_Handle(t *testing.T) { func() (err error) { defer err2.Handle(&err, func(err error) error { return err }, - func(p any) {}, + func(any) {}, ) panic("panic") }, @@ -463,7 +463,7 @@ func TestPanicking_Handle(t *testing.T) { {"general panic stoped with handler", args{ func() (err error) { - defer err2.Handle(&err, func(p any) {}) + defer err2.Handle(&err, func(any) {}) panic("panic") }, }, @@ -472,7 +472,7 @@ func TestPanicking_Handle(t *testing.T) { {"general panic stoped with handler plus fmt string", args{ func() (err error) { - defer err2.Handle(&err, func(p any) {}, "string") + defer err2.Handle(&err, func(any) {}, "string") panic("panic") }, }, @@ -492,7 +492,7 @@ func TestPanicking_Handle(t *testing.T) { {"runtime.error panic stopped with handler", args{ func() (err error) { - defer err2.Handle(&err, func(p any) {}) + defer err2.Handle(&err, func(any) {}) var b []byte b[0] = 0 return nil @@ -600,12 +600,12 @@ func TestCatch_Panic(t *testing.T) { }() defer err2.Catch( - func(err error) error { + func(error) error { t.Log("it was panic, not an error") t.Fail() // we should not be here return nil }, - func(v any) { + func(any) { panicHandled = true }) diff --git a/try/out_test.go b/try/out_test.go index d032771..f588267 100644 --- a/try/out_test.go +++ b/try/out_test.go @@ -26,7 +26,7 @@ func ExampleOut1_copyFile() { // If you prefer immediate error handling for some reason. _ = try.Out1(io.Copy(w, r)). - Handle(io.EOF, func(err error) error { + Handle(io.EOF, func(error) error { fmt.Println("err == io.EOF") return nil // by returning nil we can reset the error // return err // fallthru to next check if err != nil @@ -136,7 +136,7 @@ func ExampleResult1_Handle() { callRead := func(in io.Reader, b []byte) (eof bool, n int) { // we should use try.To1, but this is sample of try.Out.Handle n = try.Out1(in.Read(b)). - Handle(io.EOF, func(err error) error { + Handle(io.EOF, func(error) error { eof = true return nil }). // our errors.Is == true, handler to get eof status From c6b877abbcd4143e7abd3d84cbfa170764629537 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 12 Feb 2024 11:34:39 +0200 Subject: [PATCH 07/88] *.vim --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c3ce8c0..912fe33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # IDEs, etc. .idea -Session.vim* +*.vim* coverage.* # Binaries for programs and plugins From a744f45db1bca99fcf4a9d9ae4a3e4667ef4698d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 13 Feb 2024 20:32:14 +0200 Subject: [PATCH 08/88] flag & TestMain info for SetDefault documentation --- assert/assert.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 7a2c5bb..8d29b22 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -731,16 +731,20 @@ func Default() Asserter { return defAsserter[def] } -// SetDefault set the current default asserter for the package. For example, you -// might set it to panic about every assertion fault, and in other cases, throw -// an error, and print the call stack immediately when assert occurs. +// SetDefault set the current default asserter for assert pkg. +// +// Note, that you should use this in TestMain function, and use Flag package to +// set it for the app. For the tests you can set it to panic about every +// assertion fault, or to throw an error, or/and print the call stack +// immediately when assert occurs. The err2 package helps you to catch and +// report all types of the asserts. // // Note, that if you are using tracers you might get two call stacks, so test // what's best for your case. // // Tip. If our own packages (client packages for assert) have lots of parallel // testing and race detection, please try to use same asserter for all of them -// and do it only one in TestMain, or in init. +// and set asserter only one in TestMain, or in init. // // func TestMain(m *testing.M) { // SetDefault(assert.TestFull) From 6bbd852cdd4184c6245d49e3e23215d2889787b0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 13 Feb 2024 20:34:03 +0200 Subject: [PATCH 09/88] remove assert.SetDefault --- samples/main-play.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/samples/main-play.go b/samples/main-play.go index 746ed98..4788bbe 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -112,13 +112,6 @@ func doPlayMain() { // Keep here that you can play without changing imports assert.That(true) - // If asserts are treated as panics instead of errors, you get the stack trace. - // you can try that by taking the next line out of the comment: - assert.SetDefault(assert.Development) - - // same thing but one line assert msg - //assert.SetDefault(assert.Production) - // To see how automatic stack tracing works. //err2.SetErrorTracer(os.Stderr) From b56f802ff231586215d59dc145a5074c75df1556 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 13 Feb 2024 20:34:31 +0200 Subject: [PATCH 10/88] move all handlers from err2.go and add Handlers helper to handlers.go --- err2.go | 54 ---------------------------------- handlers.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ handlers_test.go | 45 ++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 handlers.go create mode 100644 handlers_test.go diff --git a/err2.go b/err2.go index 02491a4..825f382 100644 --- a/err2.go +++ b/err2.go @@ -3,7 +3,6 @@ package err2 import ( "errors" "fmt" - "os" "github.com/lainio/err2/internal/handler" ) @@ -184,59 +183,6 @@ func Throwf(format string, args ...any) { panic(err) } -// Stderr is a built-in helper to use with Handle and Catch. It prints the -// error to stderr and it resets the current error value. It's a handy Catch -// handler in main function. -// -// You can use it like this: -// -// func main() { -// defer err2.Catch(err2.Stderr) -func Stderr(err error) error { - fmt.Fprintln(os.Stderr, err.Error()) - return nil -} - -// Stdout is a built-in helper to use with Handle and Catch. It prints the -// error to stdout and it resets the current error value. It's a handy Catch -// handler in main function. -// -// You can use it like this: -// -// func main() { -// defer err2.Catch(err2.Stdout) -func Stdout(err error) error { - fmt.Fprintln(os.Stdout, err.Error()) - return nil -} - -// Noop is a built-in helper to use with Handle and Catch. It keeps the current -// error value the same. You can use it like this: -// -// defer err2.Handle(&err, err2.Noop) -func Noop(err error) error { return err } - -// Reset is a built-in helper to use with Handle and Catch. It sets the current -// error value to nil. You can use it like this to reset the error: -// -// defer err2.Handle(&err, err2.Reset) -func Reset(error) error { return nil } - -// Err is a built-in helper to use with Handle and Catch. It offers simplifier -// for error handling function for cases where you don't need to change the -// current error value. For instance, if you want to just write error to stdout, -// and don't want to use SetLogTracer and keep it to write to your logs. -// -// defer err2.Catch(err2.Err(func(err error) { -// fmt.Println("ERROR:", err) -// })) -func Err(f func(err error)) func(error) error { - return func(err error) error { - f(err) - return err - } -} - type nullDev struct{} func (nullDev) Write([]byte) (int, error) { return 0, nil } diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..5974680 --- /dev/null +++ b/handlers.go @@ -0,0 +1,76 @@ +package err2 + +import ( + "fmt" + "os" +) + +// Handlers is a helper to call several error handlers in a sequence. +// +// defer err2.Handle(&err, err2.Handlers(err2.Log, MapToHTTPErr)) +func Handlers(f ...Handler) Handler { + return func(err error) error { + for _, handler := range f { + err = handler(err) + } + return err + } +} + +// Stderr is a built-in helper to use with Handle and Catch. It prints the +// error to stderr and it resets the current error value. It's a handy Catch +// handler in main function. +// +// You can use it like this: +// +// func main() { +// defer err2.Catch(err2.Stderr) +func Stderr(err error) error { + fmt.Fprintln(os.Stderr, err.Error()) + return nil +} + +// Stdout is a built-in helper to use with Handle and Catch. It prints the +// error to stdout and it resets the current error value. It's a handy Catch +// handler in main function. +// +// You can use it like this: +// +// func main() { +// defer err2.Catch(err2.Stdout) +func Stdout(err error) error { + fmt.Fprintln(os.Stdout, err.Error()) + return nil +} + +// Noop is a built-in helper to use with Handle and Catch. It keeps the current +// error value the same. You can use it like this: +// +// defer err2.Handle(&err, err2.Noop) +func Noop(err error) error { return err } + +// Reset is a built-in helper to use with Handle and Catch. It sets the current +// error value to nil. You can use it like this to reset the error: +// +// defer err2.Handle(&err, err2.Reset) +func Reset(error) error { return nil } + +// Err is a built-in helper to use with Handle and Catch. It offers simplifier +// for error handling function for cases where you don't need to change the +// current error value. For instance, if you want to just write error to stdout, +// and don't want to use SetLogTracer and keep it to write to your logs. +// +// defer err2.Catch(err2.Err(func(err error) { +// fmt.Println("ERROR:", err) +// })) +// +// Note, that since Err helper we have other helpers like Stdout that allows +// previous block be written as simple as: +// +// defer err2.Catch(err2.Stdout) +func Err(f func(err error)) Handler { + return func(err error) error { + f(err) + return err + } +} diff --git a/handlers_test.go b/handlers_test.go new file mode 100644 index 0000000..d1b805c --- /dev/null +++ b/handlers_test.go @@ -0,0 +1,45 @@ +package err2 + +import ( + "testing" + + "github.com/lainio/err2/internal/test" +) + +func TestHandlers(t *testing.T) { + t.Parallel() + type args struct { + f []Handler + } + tests := []struct { + name string + args args + want error + }{ + {"one", args{f: []Handler{Noop}}, ErrNotFound}, + {"two", args{f: []Handler{Noop, Noop}}, ErrNotFound}, + {"three", args{f: []Handler{Noop, Noop, Noop}}, ErrNotFound}, + {"reset", args{f: []Handler{Noop, Noop, Reset}}, nil}, + {"reset first", args{f: []Handler{Reset, Noop, Noop}}, nil}, + {"reset second", args{f: []Handler{Noop, Reset, Noop}}, nil}, + {"set new first", args{f: []Handler{ + func(error) error { return ErrAlreadyExist }, Noop}}, ErrAlreadyExist}, + {"set new second", args{f: []Handler{Noop, + func(error) error { return ErrAlreadyExist }, Noop}}, ErrAlreadyExist}, + {"set new first and reset", args{f: []Handler{ + func(error) error { return ErrAlreadyExist }, Reset}}, nil}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + errHandler := Handlers(tt.args.f...) + err := errHandler(ErrNotFound) + if err == nil { + test.Require(t, tt.want == nil) + } else { + test.RequireEqual(t, err.Error(), tt.want.Error()) + } + }) + } +} From ca7e9a8e359f96e32a464b22111026045fc303ac Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 14 Feb 2024 19:12:56 +0200 Subject: [PATCH 11/88] Handler type must be type alias --- err2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/err2.go b/err2.go index 825f382..4e80f7f 100644 --- a/err2.go +++ b/err2.go @@ -11,7 +11,7 @@ type ( // Handler is a function type used to process error values in Handle and // Catch. We currently have a few build-ins of the Handler: err2.Noop, // err2.Reset, etc. - Handler func(error) error + Handler = func(error) error ) var ( From bbf85b679312713be8adf31cb88ee7c8c09866f2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:00:25 +0200 Subject: [PATCH 12/88] Handler must be a type alias, and keep it to handler pkg --- err2.go | 2 +- handlers.go | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/err2.go b/err2.go index 4e80f7f..7719eb9 100644 --- a/err2.go +++ b/err2.go @@ -11,7 +11,7 @@ type ( // Handler is a function type used to process error values in Handle and // Catch. We currently have a few build-ins of the Handler: err2.Noop, // err2.Reset, etc. - Handler = func(error) error + Handler = handler.ErrorFn ) var ( diff --git a/handlers.go b/handlers.go index 5974680..8b5ce66 100644 --- a/handlers.go +++ b/handlers.go @@ -5,17 +5,8 @@ import ( "os" ) -// Handlers is a helper to call several error handlers in a sequence. -// -// defer err2.Handle(&err, err2.Handlers(err2.Log, MapToHTTPErr)) -func Handlers(f ...Handler) Handler { - return func(err error) error { - for _, handler := range f { - err = handler(err) - } - return err - } -} + "github.com/lainio/err2/internal/handler" +) // Stderr is a built-in helper to use with Handle and Catch. It prints the // error to stderr and it resets the current error value. It's a handy Catch From a0180d670221707ee7cc6c9954e75792cb943f2b Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:20:26 +0200 Subject: [PATCH 13/88] docs how to combine SetErrorTracer, etc. with Flags --- doc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc.go b/doc.go index 98bf61d..0184aff 100644 --- a/doc.go +++ b/doc.go @@ -101,6 +101,10 @@ And the following flags are supported (="default-value"): -err2-trace="nil" A name of the stream currently supported stderr, stdout or nil +Note, that you have called err2.SetErrorTracer and others, before you call +flag.Parse. This allows you set the defaults according your app's need and allow +end-user change them during the runtime. + # Error handling Package err2 relies on declarative control structures to achieve error and panic From c7fec262ea2dcc5fb180e3edbab63fe842f3fae7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:24:42 +0200 Subject: [PATCH 14/88] sample of using multiple error handlers in one err2.Handle --- samples/main-play.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/main-play.go b/samples/main-play.go index 4788bbe..16e19c7 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -144,7 +144,8 @@ func doDoMain() { } func doMain() (err error) { - defer err2.Handle(&err) + // Example of Handle/Catch API where we can have multiple handlers. + defer err2.Handle(&err, err2.Log, err2.Noop, err2.Noop, err2.Log) // You can select any one of the try.To(CopyFile lines to play with and see // how err2 works. Especially interesting is automatic stack tracing. From 1bff1fcc4756f44571a4934c8b117189e7745f7d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:25:26 +0200 Subject: [PATCH 15/88] sample how to combine SetLogTracer and flag.Parse to have runtime flags --- samples/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/samples/main.go b/samples/main.go index 6961bb8..4434b65 100644 --- a/samples/main.go +++ b/samples/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "log" + "os" "github.com/lainio/err2" ) @@ -12,6 +13,11 @@ var ( isErr = flag.Bool("err", false, "tells if we want to have an error") ) +func init() { + // highlight that this is before flag.Parse to allow it to work properly. + err2.SetLogTracer(os.Stderr) +} + func main() { defer err2.Catch(err2.Stderr) log.SetFlags(log.Lshortfile | log.LstdFlags) From 7920fdfdb9ddcaa32086f0c781e09b301460363f Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:26:36 +0200 Subject: [PATCH 16/88] documentation note that you can use Flag pkg with SetXxxTracers --- tracer.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tracer.go b/tracer.go index e13fdaa..585eca0 100644 --- a/tracer.go +++ b/tracer.go @@ -31,6 +31,9 @@ func LogTracer() io.Writer { // // func CopyFile(src, dst string) (err error) { // defer err2.Handle(&err) // <- error trace print decision is done here +// +// Remember that you can overwrite these with Flag package support. See +// documentation of err2 package's flag section. func SetErrorTracer(w io.Writer) { tracer.Error.SetTracer(w) } @@ -43,6 +46,9 @@ func SetErrorTracer(w io.Writer) { // // func CopyFile(src, dst string) (err error) { // defer err2.Handle(&err) // <- error trace print decision is done here +// +// Remember that you can overwrite these with Flag package support. See +// documentation of err2 package's flag section. func SetPanicTracer(w io.Writer) { tracer.Panic.SetTracer(w) } @@ -54,12 +60,18 @@ func SetPanicTracer(w io.Writer) { // glog, add this line at the beginning of your app: // // glog.CopyStandardLogTo("INFO") +// +// Remember that you can overwrite these with Flag package support. See +// documentation of err2 package's flag section. func SetLogTracer(w io.Writer) { tracer.Log.SetTracer(w) } // SetTracers a convenient helper to set a io.Writer for error and panic stack // tracing. More information see SetErrorTracer and SetPanicTracer functions. +// +// Remember that you can overwrite these with Flag package support. See +// documentation of err2 package's flag section. func SetTracers(w io.Writer) { tracer.Error.SetTracer(w) tracer.Panic.SetTracer(w) From 4a3068877a9abdb55a94fed727b56ebb82409941 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:29:22 +0200 Subject: [PATCH 17/88] HandlerFn renames & support for multiple handler functions --- internal/handler/handler.go | 107 ++++++++++++++++++++++--------- internal/handler/handler_test.go | 78 +++++++++++----------- 2 files changed, 117 insertions(+), 68 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 8a3c295..c457774 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -18,9 +18,9 @@ import ( type ( // we want these to be type aliases, so they are much nicer to use - PanicHandler = func(p any) - ErrorHandler = func(err error) error // this is only proper type that work - NilHandler = func(err error) error // these two are the same + PanicFn = func(p any) + ErrorFn = func(err error) error // this is only proper type that work + NilFn = func(err error) error // these two are the same //CheckHandler = func(noerr bool, err error) error CheckHandler = func(noerr bool) @@ -41,13 +41,13 @@ type Info struct { // These are called if handler.Process caller sets it. If they aren't set // default implementations are used. NOTE. We have to use both which means // that we get nilHandler call if recovery() is called by any other - // handler then we call still ErrorHandler and get the error from Any. It + // handler then we call still ErrorFn and get the error from Any. It // goes for other way around: we get error but nilHandler is only one to // set, we use that for the error (which is accessed from the closure). - ErrorHandler // If nil default implementation is used. - NilHandler // If nil default (pre-defined here) implementation is used. + ErrorFn // If nil default implementation is used. + NilFn // If nil default (pre-defined here) implementation is used. - PanicHandler // If nil panic() is called. + PanicFn // If nil panic() is called. CheckHandler // this would be for cases where there isn't any error, but // this should be the last defer. @@ -77,8 +77,8 @@ func (i *Info) callNilHandler() { if i.safeErr() != nil { i.checkErrorTracer() } - if i.NilHandler != nil { - *i.Err = i.NilHandler(i.werr) + if i.NilFn != nil { + *i.Err = i.NilFn(i.werr) i.werr = *i.Err // remember change both our errors! } else { i.defaultNilHandler() @@ -100,8 +100,8 @@ func (i *Info) checkErrorTracer() { func (i *Info) callErrorHandler() { i.checkErrorTracer() - if i.ErrorHandler != nil { - *i.Err = i.ErrorHandler(i.Any.(error)) + if i.ErrorFn != nil { + *i.Err = i.ErrorFn(i.Any.(error)) i.werr = *i.Err // remember change both our errors! } else { i.defaultErrorHandler() @@ -120,8 +120,8 @@ func (i *Info) checkPanicTracer() { func (i *Info) callPanicHandler() { i.checkPanicTracer() - if i.PanicHandler != nil { - i.PanicHandler(i.Any) + if i.PanicFn != nil { + i.PanicFn(i.Any) } else { panic(i.Any) } @@ -153,8 +153,8 @@ func (i *Info) buildFmtErr() { } func (i *Info) safeCallErrorHandler() { - if i.ErrorHandler != nil { - *i.Err = i.ErrorHandler(i.werr) + if i.ErrorFn != nil { + *i.Err = i.ErrorFn(i.werr) } } @@ -172,8 +172,8 @@ func (i *Info) defaultNilHandler() { } func (i *Info) safeCallNilHandler() { - if i.NilHandler != nil { - *i.Err = i.NilHandler(i.werr) + if i.NilFn != nil { + *i.Err = i.NilFn(i.werr) } } @@ -285,9 +285,9 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { } } } - defCatchCallMode := info.PanicHandler == nil && info.CallerName == "Catch" + defCatchCallMode := info.PanicFn == nil && info.CallerName == "Catch" if defCatchCallMode { - info.PanicHandler = PanicNoop + info.PanicFn = PanicNoop } Process(info) @@ -317,14 +317,26 @@ func _(a ...any) bool { } func subProcess(info *Info, a ...any) { + // not that switch cannot be 0: see call side switch len(a) { - case 2: // currently we support only this order of 2 handlers in Catch + case 0: + msg := `--- +programming error: subProcess: case 0: +---` + fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) + case 1: + processArg(info, 0, a...) + default: // case 2, 3, ... processArg(info, 0, a...) - if _, ok := a[1].(PanicHandler); ok { + if _, ok := a[1].(PanicFn); ok { processArg(info, 1, a...) + } else if _, ok := a[1].(ErrorFn); ok { + // check second ^ and then change the rest by combining them to + // one that we set to proper places: ErrorFn and NilFn + hfn := Pipeline(AssertErrHandlers(a)) + info.ErrorFn = hfn + info.NilFn = hfn } - default: - processArg(info, 0, a...) } } @@ -333,15 +345,15 @@ func processArg(info *Info, i int, a ...any) { case string: info.Format = first info.Args = a[i+1:] - case ErrorHandler: // err2.Catch uses this - info.ErrorHandler = first - info.NilHandler = first - case PanicHandler: // err2.Catch uses this - info.PanicHandler = first + case ErrorFn: // err2.Catch uses this + info.ErrorFn = first + info.NilFn = first + case PanicFn: // err2.Catch uses this + info.PanicFn = first case CheckHandler: info.CheckHandler = first case nil: - info.NilHandler = NilNoop + info.NilFn = NilNoop default: // we don't panic here because we can already be in recovery, but lets // try to show an RED error message at least. @@ -386,3 +398,40 @@ func LogOutput(lvl int, s string) (err error) { fmt.Fprintln(w, s) return nil } + +// Pipeline is a helper to call several error handlers in a sequence. +// +// defer err2.Handle(&err, err2.Pipeline(err2.Log, MapToHTTPErr)) +func Pipeline(f []ErrorFn) ErrorFn { + return func(err error) error { + for _, handler := range f { + err = handler(err) + } + return err + } +} + +func bugAssertErrHandlers(handlerFns ...any) (hs []ErrorFn) { + hs = make([]ErrorFn, 0, len(handlerFns)) + for _, a := range handlerFns { + if fn, ok := a.(ErrorFn); ok { + hs = append(hs, fn) + } else { + return nil + } + } + return hs +} + +func AssertErrHandlers(handlerFns []any) (hs []ErrorFn) { + count := len(handlerFns) + hs = make([]ErrorFn, 0, count) + for _, a := range handlerFns { + if fn, ok := a.(ErrorFn); ok { + hs = append(hs, fn) + } else { + return nil + } + } + return hs +} diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index 027e9d7..a1133fa 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -30,21 +30,21 @@ func TestProcess(t *testing.T) { }{ {"all nil and our handlers", args{Info: handler.Info{ - Any: nil, - Err: &nilError, - NilHandler: nilHandler, - ErrorHandler: errorHandler, - PanicHandler: panicHandler, + Any: nil, + Err: &nilError, + NilFn: nilHandler, + ErrorFn: errorHandler, + PanicFn: panicHandler, }}, want{ errStr: "error", }}, {"error is transported in panic", args{Info: handler.Info{ - Any: errors.New("error"), - Err: &nilError, - ErrorHandler: errorHandler, - PanicHandler: panicHandler, + Any: errors.New("error"), + Err: &nilError, + ErrorFn: errorHandler, + PanicFn: panicHandler, }}, want{ panicCalled: false, @@ -53,11 +53,11 @@ func TestProcess(t *testing.T) { }}, {"runtime.Error is transported in panic", args{Info: handler.Info{ - Any: myErrRT, - Err: &nilError, - NilHandler: nilHandler, - ErrorHandler: errorHandler, - PanicHandler: panicHandler, + Any: myErrRT, + Err: &nilError, + NilFn: nilHandler, + ErrorFn: errorHandler, + PanicFn: panicHandler, }}, want{ panicCalled: true, @@ -65,11 +65,11 @@ func TestProcess(t *testing.T) { }}, {"panic is transported in panic", args{Info: handler.Info{ - Any: "panic", - Err: &nilError, - NilHandler: nilHandler, - ErrorHandler: errorHandler, - PanicHandler: panicHandler, + Any: "panic", + Err: &nilError, + NilFn: nilHandler, + ErrorFn: errorHandler, + PanicFn: panicHandler, }}, want{ panicCalled: true, @@ -77,11 +77,11 @@ func TestProcess(t *testing.T) { }}, {"error in panic and default format print", args{Info: handler.Info{ - Any: errors.New("error"), - Err: &nilError, - Format: "format %v", - Args: []any{"test"}, - PanicHandler: panicHandler, + Any: errors.New("error"), + Err: &nilError, + Format: "format %v", + Args: []any{"test"}, + PanicFn: panicHandler, }}, want{ panicCalled: false, @@ -89,12 +89,12 @@ func TestProcess(t *testing.T) { }}, {"error transported in panic and our OWN handler", args{Info: handler.Info{ - Any: errors.New("error"), - Err: &nilError, - Format: "format %v", - Args: []any{"test"}, - ErrorHandler: errorHandlerForAnnotate, - PanicHandler: panicHandler, + Any: errors.New("error"), + Err: &nilError, + Format: "format %v", + Args: []any{"test"}, + ErrorFn: errorHandlerForAnnotate, + PanicFn: panicHandler, }}, want{ panicCalled: false, @@ -103,10 +103,10 @@ func TestProcess(t *testing.T) { }}, {"error is transported in error val", args{Info: handler.Info{ - Any: nil, - Err: &myErrVal, - ErrorHandler: errorHandler, - PanicHandler: panicHandler, + Any: nil, + Err: &myErrVal, + ErrorFn: errorHandler, + PanicFn: panicHandler, }}, want{ panicCalled: false, @@ -196,11 +196,11 @@ func TestPreProcess(t *testing.T) { {"all nil and our handlers", args{ Info: handler.Info{ - Any: nil, - Err: &nilError, - NilHandler: nilHandler, - ErrorHandler: errorHandlerForAnnotate, - PanicHandler: panicHandler, + Any: nil, + Err: &nilError, + NilFn: nilHandler, + ErrorFn: errorHandlerForAnnotate, + PanicFn: panicHandler, }, a: []any{"test"}}, // no affec because want{ From 314344a919dd91ef2a14ffba9c0ae5f7417bdcfe Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:30:39 +0200 Subject: [PATCH 18/88] Handler multi call moved to internal/handler pkg with tests --- handlers.go | 9 +++++- handlers_test.go | 45 -------------------------- internal/handler/handlers_test.go | 53 +++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 46 deletions(-) delete mode 100644 handlers_test.go create mode 100644 internal/handler/handlers_test.go diff --git a/handlers.go b/handlers.go index 8b5ce66..66f3f50 100644 --- a/handlers.go +++ b/handlers.go @@ -3,7 +3,6 @@ package err2 import ( "fmt" "os" -) "github.com/lainio/err2/internal/handler" ) @@ -65,3 +64,11 @@ func Err(f func(err error)) Handler { return err } } + +const lvl = 10 + +// Log prints error string to the current log that is set by SetLogTracer. +func Log(err error) error { + _ = handler.LogOutput(lvl, err.Error()) + return err +} diff --git a/handlers_test.go b/handlers_test.go deleted file mode 100644 index d1b805c..0000000 --- a/handlers_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package err2 - -import ( - "testing" - - "github.com/lainio/err2/internal/test" -) - -func TestHandlers(t *testing.T) { - t.Parallel() - type args struct { - f []Handler - } - tests := []struct { - name string - args args - want error - }{ - {"one", args{f: []Handler{Noop}}, ErrNotFound}, - {"two", args{f: []Handler{Noop, Noop}}, ErrNotFound}, - {"three", args{f: []Handler{Noop, Noop, Noop}}, ErrNotFound}, - {"reset", args{f: []Handler{Noop, Noop, Reset}}, nil}, - {"reset first", args{f: []Handler{Reset, Noop, Noop}}, nil}, - {"reset second", args{f: []Handler{Noop, Reset, Noop}}, nil}, - {"set new first", args{f: []Handler{ - func(error) error { return ErrAlreadyExist }, Noop}}, ErrAlreadyExist}, - {"set new second", args{f: []Handler{Noop, - func(error) error { return ErrAlreadyExist }, Noop}}, ErrAlreadyExist}, - {"set new first and reset", args{f: []Handler{ - func(error) error { return ErrAlreadyExist }, Reset}}, nil}, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - errHandler := Handlers(tt.args.f...) - err := errHandler(ErrNotFound) - if err == nil { - test.Require(t, tt.want == nil) - } else { - test.RequireEqual(t, err.Error(), tt.want.Error()) - } - }) - } -} diff --git a/internal/handler/handlers_test.go b/internal/handler/handlers_test.go new file mode 100644 index 0000000..0db73c5 --- /dev/null +++ b/internal/handler/handlers_test.go @@ -0,0 +1,53 @@ +package handler_test + +import ( + "testing" + + "github.com/lainio/err2" + "github.com/lainio/err2/internal/handler" + "github.com/lainio/err2/internal/test" +) + +func TestHandlers(t *testing.T) { + t.Parallel() + type args struct { + f []any // we use any because it's same as real-world case at start + } + tests := []struct { + name string + args args + want error + }{ + {"one", args{f: []any{err2.Noop}}, err2.ErrNotFound}, + {"two", args{f: []any{err2.Noop, err2.Noop}}, err2.ErrNotFound}, + {"three", args{f: []any{err2.Noop, err2.Noop, err2.Noop}}, err2.ErrNotFound}, + {"reset", args{f: []any{err2.Noop, err2.Noop, err2.Reset}}, nil}, + {"reset first", args{f: []any{err2.Reset, err2.Noop, err2.Noop}}, nil}, + {"reset second", args{f: []any{err2.Noop, err2.Reset, err2.Noop}}, nil}, + {"set new first", args{f: []any{ + func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist}, + {"set new second", args{f: []any{err2.Noop, + func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist}, + {"set new first and reset", args{f: []any{ + func(error) error { return err2.ErrAlreadyExist }, err2.Reset}}, nil}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + anys := tt.args.f + + test.Require(t, anys != nil, "cannot be nil") + fns := handler.AssertErrHandlers(anys) + test.Require(t, fns != nil, "cannot be nil") + + errHandler := handler.Pipeline(fns) + err := errHandler(err2.ErrNotFound) + if err == nil { + test.Require(t, tt.want == nil) + } else { + test.RequireEqual(t, err.Error(), tt.want.Error()) + } + }) + } +} From dfde0a44e84f7915e56adbb5485ec7acec53ba2b Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:57:53 +0200 Subject: [PATCH 19/88] documentation to sample --- samples/main-play.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/main-play.go b/samples/main-play.go index 16e19c7..d86899c 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -136,7 +136,7 @@ func doPlayMain() { doDoMain() //try.To(doMain()) - println("______===") + println("___ happy ending ===") } func doDoMain() { @@ -145,6 +145,9 @@ func doDoMain() { func doMain() (err error) { // Example of Handle/Catch API where we can have multiple handlers. + // Note that this is a silly sample where logging is done trice and noops + // are used without a purpose. All of this is that you get an idea how you + // could use the error handlers and chain them together. defer err2.Handle(&err, err2.Log, err2.Noop, err2.Noop, err2.Log) // You can select any one of the try.To(CopyFile lines to play with and see From 097ce056f20c076351ccea419dca88b174b2ef96 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Feb 2024 17:58:31 +0200 Subject: [PATCH 20/88] fixing bugs from prebuilt error handlers --- handlers.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/handlers.go b/handlers.go index 66f3f50..6ba268c 100644 --- a/handlers.go +++ b/handlers.go @@ -16,6 +16,9 @@ import ( // func main() { // defer err2.Catch(err2.Stderr) func Stderr(err error) error { + if err == nil { + return nil + } fmt.Fprintln(os.Stderr, err.Error()) return nil } @@ -29,6 +32,9 @@ func Stderr(err error) error { // func main() { // defer err2.Catch(err2.Stdout) func Stdout(err error) error { + if err == nil { + return nil + } fmt.Fprintln(os.Stdout, err.Error()) return nil } @@ -69,6 +75,9 @@ const lvl = 10 // Log prints error string to the current log that is set by SetLogTracer. func Log(err error) error { + if err == nil { + return nil + } _ = handler.LogOutput(lvl, err.Error()) return err } From ddb444c740b81f203759eff35309f8184adf746e Mon Sep 17 00:00:00 2001 From: lainio Date: Sat, 17 Feb 2024 15:19:56 +0200 Subject: [PATCH 21/88] no use variadic expanse in internals --- assert/assert.go | 74 +++++++++++++++++++++++----------------------- assert/asserter.go | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 8d29b22..a364e19 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -260,7 +260,7 @@ func tester() (t testing.TB) { // NotImplemented always panics with 'not implemented' assertion message. func NotImplemented(a ...any) { - Default().reportAssertionFault("not implemented", a...) + Default().reportAssertionFault("not implemented", a) } // ThatNot asserts that the term is NOT true. If is it panics with the given @@ -269,7 +269,7 @@ func NotImplemented(a ...any) { func ThatNot(term bool, a ...any) { if term { defMsg := "ThatNot: " + assertionMsg - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -279,7 +279,7 @@ func ThatNot(term bool, a ...any) { func That(term bool, a ...any) { if !term { defMsg := "That: " + assertionMsg - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -288,7 +288,7 @@ func That(term bool, a ...any) { func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { defMsg := assertionMsg + ": pointer shouldn't be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -297,7 +297,7 @@ func NotNil[P ~*T, T any](p P, a ...any) { func Nil[T any](p *T, a ...any) { if p != nil { defMsg := assertionMsg + ": pointer should be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -311,7 +311,7 @@ func Nil[T any](p *T, a ...any) { func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -325,7 +325,7 @@ func INil(i any, a ...any) { func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -334,7 +334,7 @@ func INotNil(i any, a ...any) { func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { defMsg := assertionMsg + ": slice should be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -343,7 +343,7 @@ func SNil[S ~[]T, T any](s S, a ...any) { func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { defMsg := assertionMsg + ": slice shouldn't be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -352,7 +352,7 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { defMsg := assertionMsg + ": channel shouldn't be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -361,7 +361,7 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { defMsg := assertionMsg + ": map shouldn't be nil" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -371,7 +371,7 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { func NotEqual[T comparable](val, want T, a ...any) { if want == val { defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -381,7 +381,7 @@ func NotEqual[T comparable](val, want T, a ...any) { func Equal[T comparable](val, want T, a ...any) { if want != val { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -392,7 +392,7 @@ func Equal[T comparable](val, want T, a ...any) { func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -408,7 +408,7 @@ func DeepEqual(val, want any, a ...any) { func NotDeepEqual(val, want any, a ...any) { if reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= '%v')", val, want) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -424,7 +424,7 @@ func Len(obj string, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -440,7 +440,7 @@ func Longer(obj string, length int, a ...any) { if l > length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -456,7 +456,7 @@ func Shorter(obj string, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -472,7 +472,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -488,7 +488,7 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -504,7 +504,7 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -520,7 +520,7 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -536,7 +536,7 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -552,7 +552,7 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -568,7 +568,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -584,7 +584,7 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -600,7 +600,7 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -612,7 +612,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) if !ok { defMsg := fmt.Sprintf(assertionMsg+": key '%v' doesn't exist", key) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } return val } @@ -623,7 +623,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) func NotEmpty(obj string, a ...any) { if obj == "" { defMsg := assertionMsg + ": string shouldn't be empty" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -633,7 +633,7 @@ func NotEmpty(obj string, a ...any) { func Empty(obj string, a ...any) { if obj != "" { defMsg := assertionMsg + ": string should be empty" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -648,7 +648,7 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { if l == 0 { defMsg := assertionMsg + ": slice shouldn't be empty" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -665,7 +665,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { if l == 0 { defMsg := assertionMsg + ": map shouldn't be empty" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -681,7 +681,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { func NoError(err error, a ...any) { if err != nil { defMsg := "NoError:" + assertionMsg + conCatErrStr + err.Error() - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -691,7 +691,7 @@ func NoError(err error, a ...any) { func Error(err error, a ...any) { if err == nil { defMsg := "Error:" + assertionMsg + ": missing error" - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -701,7 +701,7 @@ func Error(err error, a ...any) { func Zero[T Number](val T, a ...any) { if val != 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -711,7 +711,7 @@ func Zero[T Number](val T, a ...any) { func NotZero[T Number](val T, a ...any) { if val == 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) - Default().reportAssertionFault(defMsg, a...) + Default().reportAssertionFault(defMsg, a) } } @@ -793,7 +793,7 @@ func newDefInd(v string) defInd { func combineArgs(format string, a []any) []any { args := make([]any, 1, len(a)+1) args[0] = format - args = append(args, a...) + args = append(args, a) return args } diff --git a/assert/asserter.go b/assert/asserter.go index 62a9212..50beb53 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -54,7 +54,7 @@ const officialTestOutputPrefix = " " // Note. We use the pattern where we build defaultMsg argument reaady in cases // like 'got: X, want: Y'. This hits two birds with one stone: we have automatic // and correct assert messages, and we can add information to it if we want to. -func (asserter Asserter) reportAssertionFault(defaultMsg string, a ...any) { +func (asserter Asserter) reportAssertionFault(defaultMsg string, a []any) { if asserter.hasStackTrace() { if asserter.isUnitTesting() { // Note. that the assert in the test function is printed in From 6a2ea3596b1b892b3089ebafaf8af54a2d39aeca Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 17 Feb 2024 19:50:39 +0200 Subject: [PATCH 22/88] internal variadic -> slices --- err2.go | 6 ++-- internal/handler/handler.go | 49 +++++++++++-------------------- internal/handler/handler_test.go | 4 +-- internal/handler/handlers_test.go | 2 +- try/out.go | 8 ++--- 5 files changed, 27 insertions(+), 42 deletions(-) diff --git a/err2.go b/err2.go index 7719eb9..9c7729a 100644 --- a/err2.go +++ b/err2.go @@ -86,7 +86,7 @@ func Handle(err *error, a ...any) { // how how it works with defer. r := recover() - if !handler.WorkToDo(r, err) && !handler.NoerrCallToDo(a...) { + if !handler.WorkToDo(r, err) && !handler.NoerrCallToDo(a) { return } @@ -96,7 +96,7 @@ func Handle(err *error, a ...any) { *err = handler.PreProcess(err, &handler.Info{ CallerName: "Handle", Any: r, - }, a...) + }, a) } // Catch is a convenient helper to those functions that doesn't return errors. @@ -157,7 +157,7 @@ func Catch(a ...any) { err = handler.PreProcess(&err, &handler.Info{ CallerName: "Catch", Any: r, - }, a...) + }, a) doTrace(err) } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index c457774..23026b8 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -221,8 +221,10 @@ func WorkToDo(r any, err *error) bool { return (err != nil && *err != nil) || r != nil } -func NoerrCallToDo(a ...any) (yes bool) { - //var yes bool +// NoerrCallToDo returns if we have the _exception case_, aka, func (noerr bool) +// where these handlers are called even normally only error handlers are called, +// i.e. those which have error to handle. +func NoerrCallToDo(a []any) (yes bool) { if len(a) != 0 { _, yes = a[0].(CheckHandler) } @@ -249,7 +251,7 @@ func Process(info *Info) { // PreProcess is currently used for err2 API like err2.Handle and .Catch. // - replaces the Process -func PreProcess(errPtr *error, info *Info, a ...any) error { +func PreProcess(errPtr *error, info *Info, a []any) error { // Bug in Go? // start to use local error ptr only for optimization reasons. // We get 3x faster defer handlers without unsing ptr to original err @@ -265,7 +267,7 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { const lvl = -1 if len(a) > 0 { - subProcess(info, a...) + subProcess(info, a) } else { fnName := "Handle" // default if info.CallerName != "" { @@ -307,16 +309,7 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { return err } -// firstArgIsString not used any more. -func _(a ...any) bool { - if len(a) > 0 { - _, isStr := a[0].(string) - return isStr - } - return false -} - -func subProcess(info *Info, a ...any) { +func subProcess(info *Info, a []any) { // not that switch cannot be 0: see call side switch len(a) { case 0: @@ -325,22 +318,22 @@ programming error: subProcess: case 0: ---` fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) case 1: - processArg(info, 0, a...) + processArg(info, 0, a) default: // case 2, 3, ... - processArg(info, 0, a...) + processArg(info, 0, a) if _, ok := a[1].(PanicFn); ok { - processArg(info, 1, a...) + processArg(info, 1, a) } else if _, ok := a[1].(ErrorFn); ok { // check second ^ and then change the rest by combining them to // one that we set to proper places: ErrorFn and NilFn - hfn := Pipeline(AssertErrHandlers(a)) + hfn := Pipeline(ToErrorFns(a)) info.ErrorFn = hfn info.NilFn = hfn } } } -func processArg(info *Info, i int, a ...any) { +func processArg(info *Info, i int, a []any) { switch first := a[i].(type) { case string: info.Format = first @@ -411,25 +404,17 @@ func Pipeline(f []ErrorFn) ErrorFn { } } -func bugAssertErrHandlers(handlerFns ...any) (hs []ErrorFn) { - hs = make([]ErrorFn, 0, len(handlerFns)) - for _, a := range handlerFns { - if fn, ok := a.(ErrorFn); ok { - hs = append(hs, fn) - } else { - return nil - } - } - return hs -} - -func AssertErrHandlers(handlerFns []any) (hs []ErrorFn) { +func ToErrorFns(handlerFns []any) (hs []ErrorFn) { count := len(handlerFns) hs = make([]ErrorFn, 0, count) for _, a := range handlerFns { if fn, ok := a.(ErrorFn); ok { hs = append(hs, fn) } else { + msg := `--- +assertion vialation: your handlers should be 'func(erro) error' type +---` + fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) return nil } } diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index a1133fa..1214f2f 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -141,7 +141,7 @@ var Info = handler.Info{ func Handle() { a := []any{} Info.Err = &myErrVal - myErrVal = handler.PreProcess(&myErrVal, &Info, a...) + myErrVal = handler.PreProcess(&myErrVal, &Info, a) } func TestPreProcess_debug(t *testing.T) { @@ -241,7 +241,7 @@ func TestPreProcess(t *testing.T) { var err = x.Whom(tt.args.Info.Err != nil, *tt.args.Info.Err, nil) - err = handler.PreProcess(&err, &tt.args.Info, tt.args.a...) + err = handler.PreProcess(&err, &tt.args.Info, tt.args.a) test.RequireEqual(t, panicHandlerCalled, tt.want.panicCalled) test.RequireEqual(t, errorHandlerCalled, tt.want.errorCalled) diff --git a/internal/handler/handlers_test.go b/internal/handler/handlers_test.go index 0db73c5..d93feb7 100644 --- a/internal/handler/handlers_test.go +++ b/internal/handler/handlers_test.go @@ -38,7 +38,7 @@ func TestHandlers(t *testing.T) { anys := tt.args.f test.Require(t, anys != nil, "cannot be nil") - fns := handler.AssertErrHandlers(anys) + fns := handler.ToErrorFns(anys) test.Require(t, fns != nil, "cannot be nil") errHandler := handler.Pipeline(fns) diff --git a/try/out.go b/try/out.go index d51bf3b..0d5d5e4 100644 --- a/try/out.go +++ b/try/out.go @@ -43,7 +43,7 @@ type ( // // error sending response: UDP not listening func (o *Result) Logf(a ...any) *Result { - return o.logf(logfFrameLvl, a...) + return o.logf(logfFrameLvl, a) } // Logf prints a log line to pre-set logging stream (err2.SetLogWriter) @@ -57,7 +57,7 @@ func (o *Result) Logf(a ...any) *Result { // // error sending response: UDP not listening func (o *Result1[T]) Logf(a ...any) *Result1[T] { - o.Result.logf(logfFrameLvl, a...) + o.Result.logf(logfFrameLvl, a) return o } @@ -72,7 +72,7 @@ func (o *Result1[T]) Logf(a ...any) *Result1[T] { // // error sending response: UDP not listening func (o *Result2[T, U]) Logf(a ...any) *Result2[T, U] { - o.Result.logf(logfFrameLvl, a...) + o.Result.logf(logfFrameLvl, a) return o } @@ -262,7 +262,7 @@ func wrapStr() string { return ": %w" } -func (o *Result) logf(lvl int, a ...any) *Result { +func (o *Result) logf(lvl int, a []any) *Result { if o.Err == nil { return o } From 7087dd693529e0e9be96266de49dd8ec3e2a7200 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Feb 2024 09:39:36 +0200 Subject: [PATCH 23/88] new stream Copy benchmark --- try/copy_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/try/copy_test.go b/try/copy_test.go index 3978c7d..c11389c 100644 --- a/try/copy_test.go +++ b/try/copy_test.go @@ -3,6 +3,7 @@ package try_test import ( "bufio" "bytes" + "errors" "io" "os" "testing" @@ -13,6 +14,19 @@ import ( const dataFile = "./try.go" +func Benchmark_CopyBufferMy(b *testing.B) { + all, err := os.ReadFile(dataFile) + test.Requiref(b, err == nil, "error: %v", err) + test.Require(b, all != nil) + + buf := make([]byte, 4) + dst := bufio.NewWriter(bytes.NewBuffer(make([]byte, 0, len(all)))) + src := bytes.NewReader(all) + for n := 0; n < b.N; n++ { + try.To1(myCopyBuffer(dst, src, buf)) + } +} + func Benchmark_CopyBufferStd(b *testing.B) { all, err := os.ReadFile(dataFile) test.Requiref(b, err == nil, "error: %v", err) @@ -40,3 +54,36 @@ func Benchmark_CopyBufferOur(b *testing.B) { } } } + +// myCopyBuffer is copy/paste from Go std lib to remove noice and measure only a +// loop +func myCopyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { + for { + nr, er := src.Read(buf) + if nr > 0 { + nw, ew := dst.Write(buf[0:nr]) + if nw < 0 || nr < nw { + nw = 0 + if ew == nil { + ew = errors.New("invalid write result") + } + } + written += int64(nw) + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err +} From d808c9beab2095dda8a26d4ab737468a606d87ff Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Feb 2024 09:43:07 +0200 Subject: [PATCH 24/88] typos in assert message --- internal/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 23026b8..ef78fb6 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -412,7 +412,7 @@ func ToErrorFns(handlerFns []any) (hs []ErrorFn) { hs = append(hs, fn) } else { msg := `--- -assertion vialation: your handlers should be 'func(erro) error' type +assertion violation: your handlers should be 'func(error) error' type ---` fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) return nil From a8e9077a4c709c0ec478c7846df28d7eb4f34f32 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Feb 2024 19:20:20 +0200 Subject: [PATCH 25/88] multiple error handlers documentation to Handle/Catch --- err2.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/err2.go b/err2.go index 9c7729a..8ba5a95 100644 --- a/err2.go +++ b/err2.go @@ -74,6 +74,13 @@ var ( // return err // }) // +// You can have unlimited amount of error handlers. They are called if error +// happens and they are called in the same order as they are given or until one +// of them resets the error like Reset (notice other predefined error handlers) +// in next sample: +// +// defer err2.Handle(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! +// // If you need to stop general panics in handler, you can do that by giving a // panic handler function: // @@ -131,7 +138,8 @@ func Handle(err *error, a ...any) { // error handling function, i.e., err2.Handler. By returning nil resets the // error, which allows e.g. prevent automatic error logs to happening. // Otherwise, the output results depends on the current Tracer and assert -// settings. Default setting print call stacks for panics but not for errors: +// settings. The default trace setting prints call stacks for panics but not for +// errors: // // defer err2.Catch(func(err error) error { return err} ) // @@ -139,8 +147,14 @@ func Handle(err *error, a ...any) { // // defer err2.Catch(err2.Noop) // -// The last one calls your error handler, and you have an explicit panic -// handler too, where you can e.g. continue panicking to propagate it for above +// You can have unlimited amount of error handlers. They are called if error +// happens and they are called in the same order as they are given or until one +// of them resets the error like Reset in next sample: +// +// defer err2.Catch(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! +// +// The last one calls your error handler, and you have an explicit panic handler +// as well, where you can e.g. continue panicking to propagate it for above // callers or stop it like below: // // defer err2.Catch(func(err error) error { return err }, func(p any) {}) From 7dc62ccea1cecbeee8c6c0e6205527c933992322 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Feb 2024 19:33:38 +0200 Subject: [PATCH 26/88] update README files for 1.0.0 --- CHANGELOG.md | 6 ++++++ README.md | 20 ++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bdfa4..12f6dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ### Version history +##### 0.9.52 +- `err2.Stderr` helpers for `Catch/Handle` to direct auto-logging + snippets +- `assert` package `Shorter` `Longer` helpers for automatic messages +- `asserter` package remove deprecated slow reflection based funcs +- cleanup and refactoring for sample apps + ##### 0.9.51 - `flag` package support to set `err2` and `assert` package configuration - `err2.Catch` default mode is to log error diff --git a/README.md b/README.md index e30a13e..6cce1ce 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,9 @@ func doSomething() (err error) { See more information from `err2.Handle`'s documentation. It supports several error-handling scenarios. And remember that you can have as many error handlers -per function as you need. +per function as you need, as well as you can chain error handling functions per +`err2.Handle` that allows you to build new error handling middleware for your +own purposes. #### Error Stack Tracing @@ -527,14 +529,8 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release -##### 0.9.52 -- `err2.Stderr` helpers for `Catch/Handle` to direct auto-logging + snippets -- `assert` package `Shorter` `Longer` helpers for automatic messages -- `asserter` package remove deprecated slow reflection based funcs -- cleanup and refactoring for sample apps - -### Upcoming releases - -##### 0.9.6 -- Continue removing unused parts and repairing for 1.0.0 release. -- Always more and better documentation +##### 1.0.0 +- Documentation updates and cleanups +- `Catch/Handle` take unlimited amount error handler functions + - allows building e.g. error handling middleware +- technical refactoring like variadic function calls only in API level From 598fd738b07ba6c661e51b960cdd7ae64f203e17 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Feb 2024 14:41:56 +0200 Subject: [PATCH 27/88] ioutil -> io --- doc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index 0184aff..34d79ba 100644 --- a/doc.go +++ b/doc.go @@ -53,14 +53,14 @@ them. The CopyFile example shows how it works: The try package provides convenient helpers to check the errors. For example, instead of - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { return err } we can write - b := try.To1(ioutil.ReadAll(r)) + b := try.To1(io.ReadAll(r)) Note that try.ToX functions are as fast as if err != nil statements. Please see the try package documentation for more information about the error checks. From 8f4e835ab9589e52a418663aeaad21e90344468e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Feb 2024 16:30:44 +0200 Subject: [PATCH 28/88] not used ascii removed --- internal/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index ef78fb6..28d7fa4 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -350,7 +350,7 @@ func processArg(info *Info, i int, a []any) { default: // we don't panic here because we can already be in recovery, but lets // try to show an RED error message at least. - const msg = `err2 fatal error: + const msg = `err2 fatal error: --- unsupported handler function type: err2.Handle/Catch: see 'err2/scripts/README.md' and run auto-migration scripts for your repo From dee81826eca50fa1ba4d5a981984f4ea15763aa5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Feb 2024 16:31:03 +0200 Subject: [PATCH 29/88] string Longer/Shorter cross bug fixed --- assert/assert.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index a364e19..8edd61a 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -435,10 +435,10 @@ func Len(obj string, length int, a ...any) { // // Note! This is reasonably fast but not as fast as 'That' because of lacking // inlining for the current implementation of Go's type parametric functions. -func Longer(obj string, length int, a ...any) { - l := len(obj) +func Longer(s string, length int, a ...any) { + l := len(s) - if l > length { + if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) Default().reportAssertionFault(defMsg, a) } @@ -451,10 +451,10 @@ func Longer(obj string, length int, a ...any) { // // Note! This is reasonably fast but not as fast as 'That' because of lacking // inlining for the current implementation of Go's type parametric functions. -func Shorter(obj string, length int, a ...any) { - l := len(obj) +func Shorter(str string, length int, a ...any) { + l := len(str) - if l <= length { + if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) Default().reportAssertionFault(defMsg, a) } From 9c7c414f72b660fed529805658f992fd13e09ddb Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Feb 2024 18:34:30 +0200 Subject: [PATCH 30/88] pkg lvl documentations for err2, assert, and try --- assert/doc.go | 39 +++++++++++++++++++++++++-------------- doc.go | 9 ++++++--- try/try.go | 2 +- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 766635b..90e5691 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -10,23 +10,30 @@ add following two lines at the beginning of your unit tests: alice.Node = root1.Invite(alice.Node, root1.Key, alice.PubKey, 1) assert.Equal(alice.Len(), 1) // assert anything normally -# Merge Runtime And Test Assertions +# Merge Runtime And Unit Test Assertions Especially powerful feature is that even if some assertion violation happens -during the execution of called functions not the test it self. See the above -example. If assertion failure happens inside of the Invite() function instead of -the actual Test function, TestInvite, it's still reported correctly as normal -test failure when TestInvite is executed. It doesn't matter how deep the -recursion is, or if parallel test runs are performed. It works just as you -hoped. +during the execution of called functions not the test function itself. See the +above example. If assertion failure happens inside of the Invite() function +instead of the actual test function, TestInvite, it's still reported correctly +as normal test failure when TestInvite unit test is executed. It doesn't matter +how deep the recursion is, or if parallel test runs are performed. It works just +as you hoped. + +This is the actual Invite function implementation's first two lines. Even the +assertion line is written more for runtime detection and active comment, it +catches all unit test errors as well: + + func (c Chain) Invite(...) { + assert.That(c.isLeaf(invitersKey), "only leaf can invite") # Call Stack Traversal During tests The asserter package has super powerful feature. It allows us track assertion violations over package and even module boundaries. When using err2 assert -package for runtime Asserts and assert violation happens in what ever package -and module, the whole call stack is brougth to unit test logs. Naturally this is -optinal. Only thing you need to do is set proper asserter and call PushTester. +package for runtime Asserts and assert violation happens in whatever package +and module, the whole call stack is brought to unit test logs. Naturally this is +optional. Only thing you need to do is set proper asserter and call PushTester. // use unit testing asserter assert.SetDefault(assert.TestFull) @@ -66,15 +73,19 @@ And assert package's configuration flags are inserted. # Performance assert.That's performance is equal to the if-statement thanks for inlining. And -the most of the generics-based versions are about the equally fast. +the most of the generics-based versions are about the equally fast. Practice has +thought that we should prefer other than assert.That because by using detailed +version like assert.Shorter we get precise error messages automatically. Some +also prefer readability of specific asserters. If your algorithm is performance-critical please run `make bench` in the err2 repo and decide case by case. Also you can make an issue or even PR if you would like to have something similar like glog.V() function. -# Technical Notes +# Naming -Format string functions need to be own instances because of Go's vet and test -tool integration. +Because performance has been number one requirement and Go's generics cannot +discrete slices, maps and channels we have used naming prefixes accordingly: S = +slice, M = map, C = channel. No prefix is (currently) for the string type. */ package assert diff --git a/doc.go b/doc.go index 34d79ba..f92ed7f 100644 --- a/doc.go +++ b/doc.go @@ -67,9 +67,12 @@ the try package documentation for more information about the error checks. # Automatic Stack Tracing -err2 offers optional stack tracing. And yes, it's fully automatic. Just set the -tracers at the beginning your app, e.g. main function, to the stream you want -traces to be written: +err2 offers optional stack tracing. And yes, it's fully automatic. Just call + + flag.Parse() # this is enough for err2 pkg to add its flags + +at the beginning your app, e.g. main function, or set the tracers +programmatically (before flag.Parse if you are using that): err2.SetErrorTracer(os.Stderr) // write error stack trace to stderr or diff --git a/try/try.go b/try/try.go index 07e5137..21b63a6 100644 --- a/try/try.go +++ b/try/try.go @@ -10,7 +10,7 @@ about err2 and try packager roles can be seen in the FileCopy example: w := try.To1(os.Create(dst)) defer err2.Handle(&err, func(error) error { - try.To(os.Remove(dst)).Logf() + try.Out(os.Remove(dst)).Logf() return nil }) defer w.Close() From fb152fa2d9d2ea3bfe36aca946ba39b13a1bb33d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 16:12:06 +0200 Subject: [PATCH 31/88] real multi-handler call stack parsing test added --- internal/debug/debug_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/debug/debug_test.go b/internal/debug/debug_test.go index 638486a..cef1010 100644 --- a/internal/debug/debug_test.go +++ b/internal/debug/debug_test.go @@ -335,6 +335,7 @@ func TestFuncName(t *testing.T) { {"basic lvl 3", args{input2, StackInfo{"", "Handle", 3, nil, nil}}, "err2.ReturnW", 214, 6}, {"basic lvl 2", args{input2, StackInfo{"lainio/err2", "Handle", 1, nil, nil}}, "err2.ReturnW", 214, 6}, {"method", args{inputFromTest, StackInfo{"", "Handle", 1, nil, nil}}, "ssi.(*DIDAgent).AssertWallet", 146, 8}, + {"pipeline", args{inputPipelineStack, StackInfo{"", "Handle", -1, nil, nil}}, "CopyFile", 29, 9}, } for _, ttv := range tests { tt := ttv @@ -571,4 +572,35 @@ main.test0() main.main() /home/god/go/src/github.com/lainio/ic/main.go:74 +0x1d0 ` + + inputPipelineStack = `goroutine 1 [running]: +runtime/debug.Stack() + /usr/local/go/src/runtime/debug/stack.go:24 +0x64 +github.com/lainio/err2/internal/debug.FuncName({{0x0, 0x0}, {0x12f04a, 0x6}, 0xffffffffffffffff, 0x0, {0x0, 0x0, 0x0}}) + /home/parallels/go/src/github.com/lainio/err2/internal/debug/debug.go:162 +0x44 +github.com/lainio/err2/internal/handler.doBuildFormatStr(0x4000121b58?, 0x9bc5c?) + /home/parallels/go/src/github.com/lainio/err2/internal/handler/handler.go:317 +0x7c +github.com/lainio/err2/internal/handler.buildFormatStr(...) + /home/parallels/go/src/github.com/lainio/err2/internal/handler/handler.go:305 +github.com/lainio/err2/internal/handler.PreProcess(0x4000121d88, 0x4000121ba0, {0x0, 0x0, 0x0}) + /home/parallels/go/src/github.com/lainio/err2/internal/handler/handler.go:280 +0xf8 +github.com/lainio/err2.Handle(0x4000121d88, {0x0, 0x0, 0x0}) + /home/parallels/go/src/github.com/lainio/err2/err2.go:103 +0xd4 +panic({0x115f20?, 0x4000036660?}) + /usr/local/go/src/runtime/panic.go:770 +0x124 +github.com/lainio/err2/try.To(...) + /home/parallels/go/src/github.com/lainio/err2/try/try.go:82 +github.com/lainio/err2/try.To1[...](...) + /home/parallels/go/src/github.com/lainio/err2/try/try.go:97 +main.CopyFile({0x12f23c?, 0x1609c?}, {0x132cef, 0x17}) + /home/parallels/go/src/github.com/lainio/err2/samples/main-play.go:29 +0x254 +main.doMain() + /home/parallels/go/src/github.com/lainio/err2/samples/main-play.go:159 +0x68 +main.doDoMain(...) + /home/parallels/go/src/github.com/lainio/err2/samples/main-play.go:143 +main.doPlayMain() + /home/parallels/go/src/github.com/lainio/err2/samples/main-play.go:136 +0x68 +main.main() + /home/parallels/go/src/github.com/lainio/err2/samples/main.go:38 +0x15c +` ) From 12db281b8db1016954182463902bdca3c66579a5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 16:12:48 +0200 Subject: [PATCH 32/88] split FuncName to allow easy fast debugging --- internal/debug/debug.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/debug/debug.go b/internal/debug/debug.go index 56bfad7..eb94254 100644 --- a/internal/debug/debug.go +++ b/internal/debug/debug.go @@ -159,7 +159,9 @@ func FprintStack(w io.Writer, si StackInfo) { // frames we should go back (Level), and other fields tell how to find the // actual line where calculation should be started. func FuncName(si StackInfo) (n string, ln int, frame int, ok bool) { - stackBuf := bytes.NewBuffer(debug.Stack()) + stack := debug.Stack() + //println(string(stack)) + stackBuf := bytes.NewBuffer(stack) return funcName(stackBuf, si) } From 67cff842992a90d9d8e959be769f13d41d46be3b Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 16:18:41 +0200 Subject: [PATCH 33/88] implement&test for auto-annotation for multi-handler API --- internal/handler/handler.go | 96 ++++++++++++++++++++++--------- internal/handler/handlers_test.go | 27 +++++---- 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 28d7fa4..19dd9f1 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -55,6 +55,8 @@ type Info struct { CallerName string werr error + + needErrorAnnotation bool } const ( @@ -101,7 +103,15 @@ func (i *Info) checkErrorTracer() { func (i *Info) callErrorHandler() { i.checkErrorTracer() if i.ErrorFn != nil { - *i.Err = i.ErrorFn(i.Any.(error)) + // we want to auto-annotate error first and exec ErrorFn then + i.werr = i.workError() + if i.needErrorAnnotation && i.werr != nil { + i.buildFmtErr() + *i.Err = i.ErrorFn(*i.Err) + } else { + *i.Err = i.ErrorFn(i.Any.(error)) + } + i.werr = *i.Err // remember change both our errors! } else { i.defaultErrorHandler() @@ -269,23 +279,7 @@ func PreProcess(errPtr *error, info *Info, a []any) error { if len(a) > 0 { subProcess(info, a) } else { - fnName := "Handle" // default - if info.CallerName != "" { - fnName = info.CallerName - } - funcName, _, _, ok := debug.FuncName(debug.StackInfo{ - PackageName: "", - FuncName: fnName, - Level: lvl, - }) - if ok { - setFmter := fmtstore.Formatter() - if setFmter != nil { - info.Format = setFmter.Format(funcName) - } else { - info.Format = str.Decamel(funcName) - } - } + buildFormatStr(info, lvl) } defCatchCallMode := info.PanicFn == nil && info.CallerName == "Catch" if defCatchCallMode { @@ -309,6 +303,32 @@ func PreProcess(errPtr *error, info *Info, a []any) error { return err } +func buildFormatStr(info *Info, lvl int) { + if fs, ok := doBuildFormatStr(info, lvl); ok { + info.Format = fs + } +} + +func doBuildFormatStr(info *Info, lvl int) (fs string, ok bool) { + fnName := "Handle" + if info.CallerName != "" { + fnName = info.CallerName + } + funcName, _, _, ok := debug.FuncName(debug.StackInfo{ + PackageName: "", + FuncName: fnName, + Level: lvl, + }) + if ok { + setFmter := fmtstore.Formatter() + if setFmter != nil { + return setFmter.Format(funcName), true + } + return str.Decamel(funcName), true + } + return +} + func subProcess(info *Info, a []any) { // not that switch cannot be 0: see call side switch len(a) { @@ -326,13 +346,30 @@ programming error: subProcess: case 0: } else if _, ok := a[1].(ErrorFn); ok { // check second ^ and then change the rest by combining them to // one that we set to proper places: ErrorFn and NilFn - hfn := Pipeline(ToErrorFns(a)) + errorFns, dis := ToErrorFns(a) + autoOn := !dis + hfn := Pipeline(errorFns) info.ErrorFn = hfn info.NilFn = hfn + + if fs, ok := doBuildFormatStr(info, -1); autoOn && ok { + //println("fmt:", fs) + info.Format = fs + info.needErrorAnnotation = true + } } } } +func isAutoAnnotationFn(errorFns []ErrorFn) bool { + for _, f := range errorFns { + if f == nil { + return false + } + } + return true +} + func processArg(info *Info, i int, a []any) { switch first := a[i].(type) { case string: @@ -404,19 +441,24 @@ func Pipeline(f []ErrorFn) ErrorFn { } } -func ToErrorFns(handlerFns []any) (hs []ErrorFn) { +func ToErrorFns(handlerFns []any) (hs []ErrorFn, dis bool) { count := len(handlerFns) hs = make([]ErrorFn, 0, count) for _, a := range handlerFns { - if fn, ok := a.(ErrorFn); ok { - hs = append(hs, fn) - } else { - msg := `--- + autoAnnotationDisabling := a == nil + if !autoAnnotationDisabling { + if fn, ok := a.(ErrorFn); ok { + hs = append(hs, fn) + } else { + msg := `--- assertion violation: your handlers should be 'func(error) error' type ---` - fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) - return nil + fmt.Fprintln(os.Stderr, color.Red()+msg+color.Reset()) + return nil, true + } + } else { + dis = autoAnnotationDisabling } } - return hs + return hs, dis } diff --git a/internal/handler/handlers_test.go b/internal/handler/handlers_test.go index d93feb7..2e5b700 100644 --- a/internal/handler/handlers_test.go +++ b/internal/handler/handlers_test.go @@ -17,19 +17,25 @@ func TestHandlers(t *testing.T) { name string args args want error + dis bool }{ - {"one", args{f: []any{err2.Noop}}, err2.ErrNotFound}, - {"two", args{f: []any{err2.Noop, err2.Noop}}, err2.ErrNotFound}, - {"three", args{f: []any{err2.Noop, err2.Noop, err2.Noop}}, err2.ErrNotFound}, - {"reset", args{f: []any{err2.Noop, err2.Noop, err2.Reset}}, nil}, - {"reset first", args{f: []any{err2.Reset, err2.Noop, err2.Noop}}, nil}, - {"reset second", args{f: []any{err2.Noop, err2.Reset, err2.Noop}}, nil}, + {"one", args{f: []any{err2.Noop}}, err2.ErrNotFound, false}, + {"one disabled NOT real case", args{f: []any{nil}}, err2.ErrNotFound, true}, + {"two", args{f: []any{err2.Noop, err2.Noop}}, err2.ErrNotFound, false}, + {"three", args{f: []any{err2.Noop, err2.Noop, err2.Noop}}, err2.ErrNotFound, false}, + {"three last disabled", args{f: []any{err2.Noop, err2.Noop, nil}}, err2.ErrNotFound, true}, + {"three 2nd disabled", args{f: []any{err2.Noop, nil, err2.Noop}}, err2.ErrNotFound, true}, + {"three all disabled", args{f: []any{nil, nil, nil}}, err2.ErrNotFound, true}, + {"reset", args{f: []any{err2.Noop, err2.Noop, err2.Reset}}, nil, false}, + {"reset and disabled", args{f: []any{nil, err2.Noop, err2.Reset}}, nil, true}, + {"reset first", args{f: []any{err2.Reset, err2.Noop, err2.Noop}}, nil, false}, + {"reset second", args{f: []any{err2.Noop, err2.Reset, err2.Noop}}, nil, false}, {"set new first", args{f: []any{ - func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist}, + func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist, false}, {"set new second", args{f: []any{err2.Noop, - func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist}, + func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist, false}, {"set new first and reset", args{f: []any{ - func(error) error { return err2.ErrAlreadyExist }, err2.Reset}}, nil}, + func(error) error { return err2.ErrAlreadyExist }, err2.Reset}}, nil, false}, } for _, tt := range tests { tt := tt @@ -38,8 +44,9 @@ func TestHandlers(t *testing.T) { anys := tt.args.f test.Require(t, anys != nil, "cannot be nil") - fns := handler.ToErrorFns(anys) + fns, dis := handler.ToErrorFns(anys) test.Require(t, fns != nil, "cannot be nil") + test.Require(t, dis == tt.dis, "disabled wanted") errHandler := handler.Pipeline(fns) err := errHandler(err2.ErrNotFound) From c9da7218c02201dc6824649750e05c5e4ef88786 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 16:20:56 +0200 Subject: [PATCH 34/88] sample how to use init() with flag defaults for err2 --- samples/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/main.go b/samples/main.go index 4434b65..afd933d 100644 --- a/samples/main.go +++ b/samples/main.go @@ -15,7 +15,8 @@ var ( func init() { // highlight that this is before flag.Parse to allow it to work properly. - err2.SetLogTracer(os.Stderr) + err2.SetLogTracer(os.Stderr) // for import + err2.SetLogTracer(nil) } func main() { From 45fa92103bb3add37e2736060f927f3d62cae5d5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 17:53:09 +0200 Subject: [PATCH 35/88] multi-handler & auto-annotation documentation to Handle --- err2.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/err2.go b/err2.go index 8ba5a95..7174d5a 100644 --- a/err2.go +++ b/err2.go @@ -79,7 +79,8 @@ var ( // of them resets the error like Reset (notice other predefined error handlers) // in next sample: // -// defer err2.Handle(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! +// defer err2.Handle(err2.Noop, err2.Log) // 2 handlers ==> annotated by err2 +// defer err2.Handle(nil, err2.Noop, err2.Log) // nil disables auto-annotation // // If you need to stop general panics in handler, you can do that by giving a // panic handler function: From 313e26dd438900055c4c0d773154efb5cff66ae7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 17:53:55 +0200 Subject: [PATCH 36/88] Handle examples for multi-handler and annotation --- err2_test.go | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/err2_test.go b/err2_test.go index aa1f7e6..262bd40 100644 --- a/err2_test.go +++ b/err2_test.go @@ -655,9 +655,22 @@ func ExampleHandle_errThrow() { // Output: testing: run example: our error } +func ExampleHandle_annotatedErrReturn() { + normalReturn := func() (err error) { + defer err2.Handle(&err) // automatic annotation + return fmt.Errorf("our error") + } + err := normalReturn() + fmt.Printf("%v", err) + + // ------- automatic in Go example/test + // ------- v ------------------ v -------- + // Output: testing: run example: our error +} + func ExampleHandle_errReturn() { normalReturn := func() (err error) { - defer err2.Handle(&err, "") + defer err2.Handle(&err, nil) // nil disables automatic annotation return fmt.Errorf("our error") } err := normalReturn() @@ -729,7 +742,9 @@ func ExampleHandle_handlerFn() { doSomething := func(a, b int) (err error) { defer err2.Handle(&err, func(err error) error { // Example for just annotating current err. Normally Handle is - // used for cleanup. See CopyFile example for more information. + // used for e.g. cleanup, not annotation that can be left for + // err2 automatic annotation. See CopyFile example for more + // information. return fmt.Errorf("error with (%d, %d): %v", a, b, err) }) try.To1(throw()) @@ -740,6 +755,26 @@ func ExampleHandle_handlerFn() { // Output: error with (1, 2): this is an ERROR } +func ExampleHandle_multipleHandlerFns() { + doSomething := func(a, b int) (err error) { + defer err2.Handle(&err, + // cause automatic annotation <== 2 error handlers do the trick + err2.Noop, + func(err error) error { + // Example for just annotating current err. Normally Handle + // is used for e.g. cleanup, not annotation that can be left + // for err2 automatic annotation. See CopyFile example for + // more information. + return fmt.Errorf("%w error with (%d, %d)", err, a, b) + }) + try.To1(throw()) + return err + } + err := doSomething(1, 2) + fmt.Printf("%v", err) + // Output: testing: run example: this is an ERROR error with (1, 2) +} + func ExampleHandle_noThrow() { doSomething := func(a, b int) (err error) { defer err2.Handle(&err, func(err error) error { From 4f2b63f5f653161a7ac61511306ad9c50e589275 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 20 Feb 2024 17:54:39 +0200 Subject: [PATCH 37/88] Std streams output funcs NoReset-versions --- handlers.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/handlers.go b/handlers.go index 6ba268c..4bc4a23 100644 --- a/handlers.go +++ b/handlers.go @@ -81,3 +81,33 @@ func Log(err error) error { _ = handler.LogOutput(lvl, err.Error()) return err } + +// StderrNoReset is a built-in helper to use with Handle and Catch. It prints +// the error to stderr. If you need to reset err value use Stderr instead. +// +// You can use it like this: +// +// func myFunction() { +// defer err2.Handle(err2.Noop, err2.StderrNoReset) +func StderrNoReset(err error) error { + if err == nil { + return nil + } + fmt.Fprintln(os.Stderr, err.Error()) + return nil +} + +// StdoutNoReset is a built-in helper to use with Handle and Catch. It prints +// the error to stdout. +// +// You can use it like this: +// +// func main() { +// defer err2.Catch(err2.StdoutNoReset) +func StdoutNoReset(err error) error { + if err == nil { + return nil + } + fmt.Fprintln(os.Stdout, err.Error()) + return nil +} From 874d11f7324a7e0e10cf537d049f7a833e185ddb Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 28 Feb 2024 16:37:50 +0200 Subject: [PATCH 38/88] comment of control flow assertions --- assert/doc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/assert/doc.go b/assert/doc.go index 90e5691..982eb7c 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -7,6 +7,7 @@ add following two lines at the beginning of your unit tests: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test + // v-----v Invite control flow includes assertions alice.Node = root1.Invite(alice.Node, root1.Key, alice.PubKey, 1) assert.Equal(alice.Len(), 1) // assert anything normally From 4ee44c42d0f20c110d1e7dbc6da3afec17802181 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 1 Mar 2024 18:45:51 +0200 Subject: [PATCH 39/88] bug fixed, reset the error, now doesn't --- handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers.go b/handlers.go index 4bc4a23..fc2303a 100644 --- a/handlers.go +++ b/handlers.go @@ -94,7 +94,7 @@ func StderrNoReset(err error) error { return nil } fmt.Fprintln(os.Stderr, err.Error()) - return nil + return err } // StdoutNoReset is a built-in helper to use with Handle and Catch. It prints @@ -109,5 +109,5 @@ func StdoutNoReset(err error) error { return nil } fmt.Fprintln(os.Stdout, err.Error()) - return nil + return err } From ad705477aedb48c58acd5247327131b0bfddbbae Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 1 Mar 2024 18:47:07 +0200 Subject: [PATCH 40/88] fix documentation --- assert/doc.go | 2 +- err2.go | 4 ++-- err2_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 982eb7c..8257f4a 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -7,7 +7,7 @@ add following two lines at the beginning of your unit tests: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test - // v-----v Invite control flow includes assertions + // v-----v Invite's control flow includes assertions alice.Node = root1.Invite(alice.Node, root1.Key, alice.PubKey, 1) assert.Equal(alice.Len(), 1) // assert anything normally diff --git a/err2.go b/err2.go index 7174d5a..0b75ad2 100644 --- a/err2.go +++ b/err2.go @@ -79,8 +79,8 @@ var ( // of them resets the error like Reset (notice other predefined error handlers) // in next sample: // -// defer err2.Handle(err2.Noop, err2.Log) // 2 handlers ==> annotated by err2 -// defer err2.Handle(nil, err2.Noop, err2.Log) // nil disables auto-annotation +// defer err2.Handle(&err, err2.Noop, err2.Log) // handlers > 1: err annotated +// defer err2.Handle(&err, nil, err2.Log) // nil disables auto-annotation // // If you need to stop general panics in handler, you can do that by giving a // panic handler function: diff --git a/err2_test.go b/err2_test.go index 262bd40..ce2cda9 100644 --- a/err2_test.go +++ b/err2_test.go @@ -663,7 +663,7 @@ func ExampleHandle_annotatedErrReturn() { err := normalReturn() fmt.Printf("%v", err) - // ------- automatic in Go example/test + // ------- func name comes from Go example/test harness // ------- v ------------------ v -------- // Output: testing: run example: our error } From a7a9a13778bba593f8abd2b3690c4eeb540ec288 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 1 Mar 2024 18:47:28 +0200 Subject: [PATCH 41/88] use org file copy and more samples --- samples/main-play.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/main-play.go b/samples/main-play.go index d86899c..3f6e39d 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -148,19 +148,23 @@ func doMain() (err error) { // Note that this is a silly sample where logging is done trice and noops // are used without a purpose. All of this is that you get an idea how you // could use the error handlers and chain them together. - defer err2.Handle(&err, err2.Log, err2.Noop, err2.Noop, err2.Log) + + //defer err2.Handle(&err, err2.Noop, err2.Log, err2.Log) + //defer err2.Handle(&err, nil, err2.Noop, err2.Log) + //defer err2.Handle(&err, nil, err2.Log) + defer err2.Handle(&err) // You can select any one of the try.To(CopyFile lines to play with and see // how err2 works. Especially interesting is automatic stack tracing. // // source file exists, but the destination is not in high probability - try.To(CopyFile("main.go", "/notfound/path/file.bak")) + //try.To(OrgCopyFile("main.go", "/notfound/path/file.bak")) // Both source and destination don't exist - //try.To(CopyFile("/notfound/path/file.go", "/notfound/path/file.bak")) + //try.To(OrgCopyFile("/notfound/path/file.go", "/notfound/path/file.bak")) // 2nd argument is empty - //try.To(CopyFile("main.go", "")) + try.To(OrgCopyFile("main.go", "")) // Next fn demonstrates how error and panic traces work, comment out all // above CopyFile calls to play with: From abdb34ec394aa56026fe0f39a7c0be216befc0aa Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 9 Mar 2024 12:55:04 +0200 Subject: [PATCH 42/88] english --- err2.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/err2.go b/err2.go index 0b75ad2..e3b4226 100644 --- a/err2.go +++ b/err2.go @@ -33,9 +33,10 @@ var ( ErrNotRecoverable = errors.New("cannot recover") ErrRecoverable = errors.New("recoverable") - // Stdnull is helper variable for io.Writer need e.g. err2.SetLogTracer in - // cases you don't want to use automatic log writer, i.e. LogTracer == nil. - // It's usually used to change how the Catch works, e.g., in CLI apps. + // Stdnull implements io.Writer that writes nothing, e.g., + // err2.SetLogTracer in cases you don't want to use automatic log writer, + // i.e., LogTracer == /dev/null. It can be used to change how the Catch + // works, e.g., in CLI apps. Stdnull = &nullDev{} ) @@ -79,11 +80,12 @@ var ( // of them resets the error like Reset (notice other predefined error handlers) // in next sample: // +// defer err2.Handle(&err, err2.Reset, err2.Log) // Log not called // defer err2.Handle(&err, err2.Noop, err2.Log) // handlers > 1: err annotated // defer err2.Handle(&err, nil, err2.Log) // nil disables auto-annotation // // If you need to stop general panics in handler, you can do that by giving a -// panic handler function: +// panic handler function. See the second handler below: // // defer err2.Handle(&err, // err2.Err( func(error) { os.Remove(dst) }), // err2.Err() keeps it short @@ -130,7 +132,7 @@ func Handle(err *error, a ...any) { // error message about the error source (from where the error was thrown) to the // currently set log. Note, when log stream isn't set, the standard log is used. // It can be bound to, e.g., glog. And if you want to suppress automatic logging -// use the following setup: +// entirely use the following setup: // // err2.SetLogTracer(err2.Stdnull) // From 1b8c72d818f358fe2cc8b8aeb19357c32f4b7522 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 9 Mar 2024 12:55:35 +0200 Subject: [PATCH 43/88] Result2 Def1 & Def2 are two separated now --- try/out.go | 13 +++++++++++-- try/result2_test.go | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/try/out.go b/try/out.go index 0d5d5e4..6ea7fdd 100644 --- a/try/out.go +++ b/try/out.go @@ -204,13 +204,22 @@ func (o *Result1[T]) Def1(v T) *Result1[T] { return o } -// Def2 sets default value for Result.Val2. The value is returned in case of +// Def1 sets default value for Result.Val1. The value is returned in case of // Result.Err != nil. -func (o *Result2[T, U]) Def2(v T, v2 U) *Result2[T, U] { +func (o *Result2[T, U]) Def1(v T) *Result2[T, U] { if o.Err == nil { return o } o.Val1 = v + return o +} + +// Def2 sets default value for Result.Val2. The value is returned in case of +// Result.Err != nil. +func (o *Result2[T, U]) Def2(v2 U) *Result2[T, U] { + if o.Err == nil { + return o + } o.Val2 = v2 return o } diff --git a/try/result2_test.go b/try/result2_test.go index 15a4954..098fe26 100644 --- a/try/result2_test.go +++ b/try/result2_test.go @@ -21,7 +21,7 @@ func ExampleResult2_Logf() { err2.SetLogTracer(os.Stdout) countSomething := func(s1, s2 string) (int, int) { - r := try.Out2(convTwoStr(s1, s2)).Logf().Def2(10, 10) + r := try.Out2(convTwoStr(s1, s2)).Logf().Def1(10).Def2(10) v1, v2 := r.Val1, r.Val2 return v1 + v2, v2 } From ff0f65c5bfff160263b3aae340618734c9f2194c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 9 Mar 2024 12:56:11 +0200 Subject: [PATCH 44/88] SetLogTrace docs: setting affects to all logging supporting function --- tracer.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tracer.go b/tracer.go index 585eca0..22ae29b 100644 --- a/tracer.go +++ b/tracer.go @@ -19,8 +19,9 @@ func PanicTracer() io.Writer { return tracer.Panic.Tracer() } -// LogTracer returns current io.Writer for try.Out().Logf(). -// The default value is nil. +// LogTracer returns a current io.Writer for the explicit try.Result.Logf +// function and automatic logging used in err2.Handle and err2.Catch. The +// default value is nil. func LogTracer() io.Writer { return tracer.Log.Tracer() } @@ -32,7 +33,7 @@ func LogTracer() io.Writer { // func CopyFile(src, dst string) (err error) { // defer err2.Handle(&err) // <- error trace print decision is done here // -// Remember that you can overwrite these with Flag package support. See +// Remember that you can reset these with Flag package support. See // documentation of err2 package's flag section. func SetErrorTracer(w io.Writer) { tracer.Error.SetTracer(w) @@ -45,32 +46,35 @@ func SetErrorTracer(w io.Writer) { // handler, e.g: // // func CopyFile(src, dst string) (err error) { -// defer err2.Handle(&err) // <- error trace print decision is done here +// defer err2.Handle(&err) // <- panic trace print decision is done here // -// Remember that you can overwrite these with Flag package support. See +// Remember that you can reset these with Flag package support. See // documentation of err2 package's flag section. func SetPanicTracer(w io.Writer) { tracer.Panic.SetTracer(w) } -// SetLogTracer sets a io.Writer for try.Out().Logf() function. The default is -// nil and then err2 uses std log package for logging. +// SetLogTracer sets a current io.Writer for the explicit try.Result.Logf +// function and automatic logging used in err2.Handle and err2.Catch. The +// default is nil and then err2 uses std log package for logging. // -// You can use that to redirect packages like glog and have proper logging. For -// glog, add this line at the beginning of your app: +// You can use the std log package to redirect other logging packages like glog +// to automatically work with the err2 package. For the glog, add this line at +// the beginning of your app: // // glog.CopyStandardLogTo("INFO") // -// Remember that you can overwrite these with Flag package support. See +// Remember that you can reset these with Flag package support. See // documentation of err2 package's flag section. func SetLogTracer(w io.Writer) { tracer.Log.SetTracer(w) } -// SetTracers a convenient helper to set a io.Writer for error and panic stack -// tracing. More information see SetErrorTracer and SetPanicTracer functions. +// SetTracers a helper to set a io.Writer for error and panic stack tracing, the +// log tracer is set as well. More information see SetErrorTracer, +// SetPanicTracer, and SetLogTracer functions. // -// Remember that you can overwrite these with Flag package support. See +// Remember that you can reset these with Flag package support. See // documentation of err2 package's flag section. func SetTracers(w io.Writer) { tracer.Error.SetTracer(w) From 1aeaa326584eb36c39b13ae11e06db877931e330 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 10 Mar 2024 09:51:00 +0200 Subject: [PATCH 45/88] fix: Plain assert allows override assert msg --- assert/asserter.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assert/asserter.go b/assert/asserter.go index 50beb53..555f3f1 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -73,7 +73,8 @@ func (asserter Asserter) reportAssertionFault(defaultMsg string, a []any) { } if len(a) > 0 { if format, ok := a[0].(string); ok { - f := x.Whom(defaultMsg != "", defaultMsg+conCatErrStr+format, format) + allowDefMsg := !asserter.isErrorOnly() && defaultMsg != "" + f := x.Whom(allowDefMsg, defaultMsg+conCatErrStr+format, format) asserter.reportPanic(fmt.Sprintf(f, a[1:]...)) } else { asserter.reportPanic(fmt.Sprintln(append([]any{defaultMsg}, a...))) @@ -141,6 +142,10 @@ func (asserter Asserter) callerInfo(msg string) (info string) { return } +func (asserter Asserter) isErrorOnly() bool { + return asserter == AsserterToError +} + func (asserter Asserter) hasToError() bool { return asserter&AsserterToError != 0 } From 8ab9b400f0e2501e077b4950a1d39b192fec574c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 10 Mar 2024 10:03:35 +0200 Subject: [PATCH 46/88] rm 'That: ' from That and ThatNot asserter functions --- assert/assert.go | 4 ++-- assert/assert_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 8edd61a..e97f18b 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -268,7 +268,7 @@ func NotImplemented(a ...any) { // single 'if-statement' that is almost nothing. func ThatNot(term bool, a ...any) { if term { - defMsg := "ThatNot: " + assertionMsg + defMsg := assertionMsg Default().reportAssertionFault(defMsg, a) } } @@ -278,7 +278,7 @@ func ThatNot(term bool, a ...any) { // single 'if-statement' that is almost nothing. func That(term bool, a ...any) { if !term { - defMsg := "That: " + assertionMsg + defMsg := assertionMsg Default().reportAssertionFault(defMsg, a) } } diff --git a/assert/assert_test.go b/assert/assert_test.go index 3314348..2db8b34 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -18,7 +18,7 @@ func ExampleThat() { } err := sample() fmt.Printf("%v", err) - // Output: testing: run example: assert_test.go:16: ExampleThat.func1(): That: assertion violation: optional message + // Output: testing: run example: assert_test.go:16: ExampleThat.func1(): assertion violation: optional message } func ExampleNotNil() { From 5a5806e567012ac2e9d16641c04f3e1c0f59186c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 11 Mar 2024 15:13:04 +0200 Subject: [PATCH 47/88] use handler pkg's ErrorFn --- try/out.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/try/out.go b/try/out.go index 6ea7fdd..7926b04 100644 --- a/try/out.go +++ b/try/out.go @@ -9,7 +9,7 @@ import ( type ( // ErrFn is function type for try.OutX handlers. - ErrFn = func(err error) error + ErrFn = handler.ErrorFn // Result is the base of our error handling language for try.Out functions. Result struct { From c2eec00f717fdc7caf937df711dc328f99e2b0c9 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 11 Mar 2024 15:13:27 +0200 Subject: [PATCH 48/88] documentation of using isErrorOnly --- assert/asserter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assert/asserter.go b/assert/asserter.go index 555f3f1..219f32e 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -54,6 +54,8 @@ const officialTestOutputPrefix = " " // Note. We use the pattern where we build defaultMsg argument reaady in cases // like 'got: X, want: Y'. This hits two birds with one stone: we have automatic // and correct assert messages, and we can add information to it if we want to. +// If asserter is Plain (isErrorOnly()) user wants to override automatic assert +// messgages with our given, usually simple message. func (asserter Asserter) reportAssertionFault(defaultMsg string, a []any) { if asserter.hasStackTrace() { if asserter.isUnitTesting() { From 7d66b3d88abfb31e3f4b505462bde6f6f57e32ed Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 11 Mar 2024 15:14:07 +0200 Subject: [PATCH 49/88] better docs for panic handler in Handle --- err2.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/err2.go b/err2.go index e3b4226..0700168 100644 --- a/err2.go +++ b/err2.go @@ -78,18 +78,19 @@ var ( // You can have unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one // of them resets the error like Reset (notice other predefined error handlers) -// in next sample: +// in the next samples: // // defer err2.Handle(&err, err2.Reset, err2.Log) // Log not called // defer err2.Handle(&err, err2.Noop, err2.Log) // handlers > 1: err annotated // defer err2.Handle(&err, nil, err2.Log) // nil disables auto-annotation // -// If you need to stop general panics in handler, you can do that by giving a -// panic handler function. See the second handler below: +// If you need to stop general panics in a handler, you can do that by declaring +// a panic handler. See the second handler below: // // defer err2.Handle(&err, // err2.Err( func(error) { os.Remove(dst) }), // err2.Err() keeps it short -// func(p any) {} // <- handler stops panics, re-throw or not +// // below handler catches panics, but you can re-throw if needed +// func(p any) {} // ) func Handle(err *error, a ...any) { // This and others are similar but we need to call `recover` here because @@ -152,7 +153,7 @@ func Handle(err *error, a ...any) { // // You can have unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one -// of them resets the error like Reset in next sample: +// of them resets the error like Reset in the next sample: // // defer err2.Catch(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! // From e0eec58e9ac57d272b3daaed7cd488653d8c4770 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 11 Mar 2024 15:14:45 +0200 Subject: [PATCH 50/88] release comments of API changes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6cce1ce..cfb4004 100644 --- a/README.md +++ b/README.md @@ -533,4 +533,6 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). - Documentation updates and cleanups - `Catch/Handle` take unlimited amount error handler functions - allows building e.g. error handling middleware +- automatic outputs aren't overwritten by given args, only with `assert.Plain` +- Minor API fixes: `Result2.Def2()`, etc. - technical refactoring like variadic function calls only in API level From 809b9aaa3ef04128dcffba980df4af49e52d258c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 13 Mar 2024 08:22:04 +0200 Subject: [PATCH 51/88] wrong number strings to bad number --- try/out.go | 2 +- try/out_test.go | 12 ++++-------- try/result2_test.go | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/try/out.go b/try/out.go index 7926b04..aebbe4e 100644 --- a/try/out.go +++ b/try/out.go @@ -261,7 +261,7 @@ func Out1[T any](v T, err error) *Result1[T] { // // or in some other cases, some of these would be desired action: // -// x, y := try.Out2(convTwoStr(s1, s2)).Logf("wrong number").Catch(1, 2) +// x, y := try.Out2(convTwoStr(s1, s2)).Logf("bad number").Catch(1, 2) // y := try.Out2(convTwoStr(s1, s2)).Handle().Val2 func Out2[T any, U any](v1 T, v2 U, err error) *Result2[T, U] { return &Result2[T, U]{Val2: v2, Result1: Result1[T]{Val1: v1, Result: Result{Err: err}}} diff --git a/try/out_test.go b/try/out_test.go index f588267..454bfdf 100644 --- a/try/out_test.go +++ b/try/out_test.go @@ -81,19 +81,16 @@ func ExampleResult1_Logf() { return try.Out1(strconv.Atoi(s)).Logf("not number").Catch(100) } num1 := countSomething("1") - num2 := countSomething("WRONG") + num2 := countSomething("BAD") fmt.Printf("results: %d, %d", num1, num2) err2.SetLogTracer(nil) - // Output: not number: strconv.Atoi: parsing "WRONG": invalid syntax + // Output: not number: strconv.Atoi: parsing "BAD": invalid syntax // results: 1, 100 } func TestResult2_Logf(t *testing.T) { t.Parallel() - // Set log tracing to stdout that we can see it in Example output. In - // normal cases that would be a Logging stream or stderr. - err2.SetLogTracer(os.Stdout) convTwoStr := func(s1, s2 string) (_ int, _ int, err error) { defer err2.Handle(&err, nil) @@ -101,11 +98,10 @@ func TestResult2_Logf(t *testing.T) { return try.To1(strconv.Atoi(s1)), try.To1(strconv.Atoi(s2)), nil } countSomething := func(s1, s2 string) (int, int) { - v1, v2 := try.Out2(convTwoStr(s1, s2)).Logf("wrong number").Catch(1, 2) + v1, v2 := try.Out2(convTwoStr(s1, s2)).Logf("bad number").Catch(1, 2) return v1 + v2, v2 } - num1, num2 := countSomething("1", "err") - fmt.Printf("results: %d, %d\n", num1, num2) + num1, num2 := countSomething("1", "bad") test.RequireEqual(t, num2, 2) test.RequireEqual(t, num1, 3) } diff --git a/try/result2_test.go b/try/result2_test.go index 098fe26..a05b536 100644 --- a/try/result2_test.go +++ b/try/result2_test.go @@ -26,9 +26,9 @@ func ExampleResult2_Logf() { return v1 + v2, v2 } _, _ = countSomething("1", "2") - num1, num2 := countSomething("WRONG", "2") + num1, num2 := countSomething("BAD", "2") fmt.Printf("results: %d, %d", num1, num2) err2.SetLogTracer(nil) - // Output: testing: run example: strconv.Atoi: parsing "WRONG": invalid syntax + // Output: testing: run example: strconv.Atoi: parsing "BAD": invalid syntax // results: 20, 10 } From bbf72e73bb374113401dc167a7d9a818ed56e107 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 13 Mar 2024 08:28:38 +0200 Subject: [PATCH 52/88] update documentation --- assert/doc.go | 18 +++++++++--------- err2.go | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 8257f4a..f225ba7 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -2,7 +2,7 @@ Package assert includes runtime assertion helpers both for normal execution as well as a assertion package for Go's testing. What makes solution unique is its capable to support both modes with same API. Only thing you need to do is to -add following two lines at the beginning of your unit tests: +add a PushTester line at the beginning of your unit tests: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test @@ -14,14 +14,14 @@ add following two lines at the beginning of your unit tests: # Merge Runtime And Unit Test Assertions Especially powerful feature is that even if some assertion violation happens -during the execution of called functions not the test function itself. See the -above example. If assertion failure happens inside of the Invite() function -instead of the actual test function, TestInvite, it's still reported correctly -as normal test failure when TestInvite unit test is executed. It doesn't matter -how deep the recursion is, or if parallel test runs are performed. It works just -as you hoped. - -This is the actual Invite function implementation's first two lines. Even the +during the execution of called functions, and not the test function itself, they +are catched. See the above example. If assertion failure happens inside the +Invite() function instead of the actual test function, TestInvite, it's still +reported correctly as normal test failure. It doesn't matter how deep the +recursion is, or if parallel test runs are performed. It works just as you +hoped. + +This is the actual Invite function implementation's first two lines. Even if the assertion line is written more for runtime detection and active comment, it catches all unit test errors as well: diff --git a/err2.go b/err2.go index 0700168..911df06 100644 --- a/err2.go +++ b/err2.go @@ -77,8 +77,8 @@ var ( // // You can have unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one -// of them resets the error like Reset (notice other predefined error handlers) -// in the next samples: +// of them resets the error like Reset (notice the other predefined error +// handlers) in the next samples: // // defer err2.Handle(&err, err2.Reset, err2.Log) // Log not called // defer err2.Handle(&err, err2.Noop, err2.Log) // handlers > 1: err annotated From a308c3e0e0f9260c16b9ef100a4b9f34f0f1ffab Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 13 Mar 2024 08:58:13 +0200 Subject: [PATCH 53/88] rm exported asserters + rename, some documentation update --- assert/assert.go | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index e97f18b..71eb93a 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -72,8 +72,7 @@ const ( // panic object's type is string, i.e., err2 package treats it as a normal // panic, not an error. // - // The pattern that e.g. Go's standard - // library uses: + // For example, the pattern that e.g. Go's standard library uses: // // if p == nil { // panic("pkg: ptr cannot be nil") @@ -87,23 +86,14 @@ const ( type flagAsserter struct{} -// Deprecated: use e.g. assert.That(), only default asserter is used. +// Asserters var ( - PL = AsserterToError - - // P is a production Asserter that sets panic objects to errors which - // allows err2 handlers to catch them. - P = AsserterToError | AsserterCallerInfo - - B = AsserterToError | AsserterFormattedCallerInfo - - T = AsserterUnitTesting - TF = AsserterUnitTesting | AsserterStackTrace | AsserterCallerInfo - - // D is a development Asserter that sets panic objects to strings that - // doesn't by caught by err2 handlers. - // Deprecated: use e.g. assert.That(), only default asserter is used. - D = AsserterDebug + plain = AsserterToError + prod = AsserterToError | AsserterCallerInfo + dev = AsserterToError | AsserterFormattedCallerInfo + test = AsserterUnitTesting + testFull = AsserterUnitTesting | AsserterStackTrace | AsserterCallerInfo + dbg = AsserterDebug ) var ( @@ -117,7 +107,7 @@ var ( // Test // TestFull // Debug - defAsserter = []Asserter{PL, P, B, T, TF, D} + defAsserter = []Asserter{plain, prod, dev, test, testFull, dbg} def defInd // mu is package lvl Mutex that is used to cool down race detector of From 4202c60c370030e9808d85483bb8791eddad36b9 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 13 Mar 2024 09:38:34 +0200 Subject: [PATCH 54/88] rm exported types and fix documentation according & export fns --- assert/assert.go | 110 +++++++++++++++++++++------------------------ assert/asserter.go | 58 ++++++++++++------------ 2 files changed, 81 insertions(+), 87 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 71eb93a..eeb7d06 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -88,12 +88,12 @@ type flagAsserter struct{} // Asserters var ( - plain = AsserterToError - prod = AsserterToError | AsserterCallerInfo - dev = AsserterToError | AsserterFormattedCallerInfo - test = AsserterUnitTesting - testFull = AsserterUnitTesting | AsserterStackTrace | AsserterCallerInfo - dbg = AsserterDebug + plain = asserterToError + prod = asserterToError | asserterCallerInfo + dev = asserterToError | asserterFormattedCallerInfo + test = asserterUnitTesting + testFull = asserterUnitTesting | asserterStackTrace | asserterCallerInfo + dbg = asserterDebug ) var ( @@ -107,7 +107,7 @@ var ( // Test // TestFull // Debug - defAsserter = []Asserter{plain, prod, dev, test, testFull, dbg} + defAsserter = []asserter{plain, prod, dev, test, testFull, dbg} def defInd // mu is package lvl Mutex that is used to cool down race detector of @@ -174,7 +174,7 @@ const ( func PushTester(t testing.TB, a ...defInd) function { if len(a) > 0 { SetDefault(a[0]) - } else if Default()&AsserterUnitTesting == 0 { + } else if current()&asserterUnitTesting == 0 { // if this is forgotten or tests don't have proper place to set it // it's good to keep the API as simple as possible SetDefault(TestFull) @@ -250,7 +250,7 @@ func tester() (t testing.TB) { // NotImplemented always panics with 'not implemented' assertion message. func NotImplemented(a ...any) { - Default().reportAssertionFault("not implemented", a) + current().reportAssertionFault("not implemented", a) } // ThatNot asserts that the term is NOT true. If is it panics with the given @@ -259,7 +259,7 @@ func NotImplemented(a ...any) { func ThatNot(term bool, a ...any) { if term { defMsg := assertionMsg - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -269,7 +269,7 @@ func ThatNot(term bool, a ...any) { func That(term bool, a ...any) { if !term { defMsg := assertionMsg - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -278,7 +278,7 @@ func That(term bool, a ...any) { func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { defMsg := assertionMsg + ": pointer shouldn't be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -287,7 +287,7 @@ func NotNil[P ~*T, T any](p P, a ...any) { func Nil[T any](p *T, a ...any) { if p != nil { defMsg := assertionMsg + ": pointer should be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -301,7 +301,7 @@ func Nil[T any](p *T, a ...any) { func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -315,7 +315,7 @@ func INil(i any, a ...any) { func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -324,7 +324,7 @@ func INotNil(i any, a ...any) { func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { defMsg := assertionMsg + ": slice should be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -333,7 +333,7 @@ func SNil[S ~[]T, T any](s S, a ...any) { func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { defMsg := assertionMsg + ": slice shouldn't be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -342,7 +342,7 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { defMsg := assertionMsg + ": channel shouldn't be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -351,7 +351,7 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { defMsg := assertionMsg + ": map shouldn't be nil" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -361,7 +361,7 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { func NotEqual[T comparable](val, want T, a ...any) { if want == val { defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -371,7 +371,7 @@ func NotEqual[T comparable](val, want T, a ...any) { func Equal[T comparable](val, want T, a ...any) { if want != val { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -382,7 +382,7 @@ func Equal[T comparable](val, want T, a ...any) { func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -398,7 +398,7 @@ func DeepEqual(val, want any, a ...any) { func NotDeepEqual(val, want any, a ...any) { if reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= '%v')", val, want) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -414,7 +414,7 @@ func Len(obj string, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -430,7 +430,7 @@ func Longer(s string, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -446,7 +446,7 @@ func Shorter(str string, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -462,7 +462,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -478,7 +478,7 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -494,7 +494,7 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -510,7 +510,7 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -526,7 +526,7 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -542,7 +542,7 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -558,7 +558,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -574,7 +574,7 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -590,7 +590,7 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -602,7 +602,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) if !ok { defMsg := fmt.Sprintf(assertionMsg+": key '%v' doesn't exist", key) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } return val } @@ -613,7 +613,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) func NotEmpty(obj string, a ...any) { if obj == "" { defMsg := assertionMsg + ": string shouldn't be empty" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -623,7 +623,7 @@ func NotEmpty(obj string, a ...any) { func Empty(obj string, a ...any) { if obj != "" { defMsg := assertionMsg + ": string should be empty" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -638,7 +638,7 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { if l == 0 { defMsg := assertionMsg + ": slice shouldn't be empty" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -655,7 +655,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { if l == 0 { defMsg := assertionMsg + ": map shouldn't be empty" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -671,7 +671,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { func NoError(err error, a ...any) { if err != nil { defMsg := "NoError:" + assertionMsg + conCatErrStr + err.Error() - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -681,7 +681,7 @@ func NoError(err error, a ...any) { func Error(err error, a ...any) { if err == nil { defMsg := "Error:" + assertionMsg + ": missing error" - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -691,7 +691,7 @@ func Error(err error, a ...any) { func Zero[T Number](val T, a ...any) { if val != 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } @@ -701,23 +701,17 @@ func Zero[T Number](val T, a ...any) { func NotZero[T Number](val T, a ...any) { if val == 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) - Default().reportAssertionFault(defMsg, a) + current().reportAssertionFault(defMsg, a) } } -// Default returns a current default asserter used for package-level -// functions like assert.That(). The package sets the default asserter as -// follows: +// current returns a current default asserter used for package-level +// functions like assert.That(). // -// SetDefaultAsserter(AsserterToError | AsserterFormattedCallerInfo) -// -// Which means that it is treats assert failures as Go errors, but in addition -// to that, it formats the assertion message properly. Naturally, only if err2 -// handlers are found in the call stack, these errors are caught. - -// You are free to set it according to your current preferences with the -// SetDefault function. -func Default() Asserter { +// Note, this indexing stuff is done because of race detection to work on client +// packages. And, yes, we have tested it. This is fastest way to make it without +// locks HERE. Only the setting the index is secured with the mutex. +func current() asserter { return defAsserter[def] } @@ -738,7 +732,7 @@ func Default() Asserter { // // func TestMain(m *testing.M) { // SetDefault(assert.TestFull) -func SetDefault(i defInd) Asserter { +func SetDefault(i defInd) defInd { // pkg lvl lock to allow only one pkg client call this at the time mu.Lock() defer mu.Unlock() @@ -746,7 +740,7 @@ func SetDefault(i defInd) Asserter { // to make this fully thread safe the def var should be atomic, BUT it // would be owerkill. We need only defaults to be set at once. def = i - return defAsserter[i] + return def } // mapDefInd runtime asserters, that's why test asserts are removed for now. diff --git a/assert/asserter.go b/assert/asserter.go index 219f32e..803213d 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -10,38 +10,38 @@ import ( "github.com/lainio/err2/internal/x" ) -// Asserter is type for asserter object guided by its flags. -type Asserter uint32 +// asserter is type for asserter object guided by its flags. +type asserter uint32 const ( - // AsserterDebug is the default mode where all asserts are treaded as + // asserterDebug is the default mode where all asserts are treaded as // panics - AsserterDebug Asserter = 0 + asserterDebug asserter = 0 - // AsserterToError is Asserter flag to guide asserter to use Go's error + // asserterToError is Asserter flag to guide asserter to use Go's error // type for panics. - AsserterToError Asserter = 1 << iota + asserterToError asserter = 1 << iota - // AsserterStackTrace is Asserter flag to print call stack to stdout OR if + // asserterStackTrace is Asserter flag to print call stack to stdout OR if // in AsserterUnitTesting mode the call stack is printed to test result // output if there is any assertion failures. - AsserterStackTrace + asserterStackTrace - // AsserterCallerInfo is an asserter flag to add info of the function + // asserterCallerInfo is an asserter flag to add info of the function // asserting. It includes filename, line number and function name. // This is especially powerful with AsserterUnitTesting where it allows get // information where the assertion violation happens even over modules! - AsserterCallerInfo + asserterCallerInfo - // AsserterFormattedCallerInfo is an asserter flag to add info of the function + // asserterFormattedCallerInfo is an asserter flag to add info of the function // asserting. It includes filename, line number and function name in // multi-line formatted string output. - AsserterFormattedCallerInfo + asserterFormattedCallerInfo - // AsserterUnitTesting is an asserter only for unit testing. It can be + // asserterUnitTesting is an asserter only for unit testing. It can be // compined with AsserterCallerInfo and/or AsserterStackTrace. There is // variable T which have all of these three asserters. - AsserterUnitTesting + asserterUnitTesting ) // every test log or result output has 4 spaces in them @@ -56,7 +56,7 @@ const officialTestOutputPrefix = " " // and correct assert messages, and we can add information to it if we want to. // If asserter is Plain (isErrorOnly()) user wants to override automatic assert // messgages with our given, usually simple message. -func (asserter Asserter) reportAssertionFault(defaultMsg string, a []any) { +func (asserter asserter) reportAssertionFault(defaultMsg string, a []any) { if asserter.hasStackTrace() { if asserter.isUnitTesting() { // Note. that the assert in the test function is printed in @@ -86,7 +86,7 @@ func (asserter Asserter) reportAssertionFault(defaultMsg string, a []any) { } } -func (asserter Asserter) reportPanic(s string) { +func (asserter asserter) reportPanic(s string) { if asserter.isUnitTesting() && asserter.hasCallerInfo() { fmt.Fprintln(os.Stderr, officialTestOutputPrefix+s) tester().FailNow() @@ -126,7 +126,7 @@ Assertion Fault at: var shortFmtStr = `%s:%d: %s(): %s` -func (asserter Asserter) callerInfo(msg string) (info string) { +func (asserter asserter) callerInfo(msg string) (info string) { ourFmtStr := shortFmtStr if asserter.hasFormattedCallerInfo() { ourFmtStr = longFmtStr @@ -144,28 +144,28 @@ func (asserter Asserter) callerInfo(msg string) (info string) { return } -func (asserter Asserter) isErrorOnly() bool { - return asserter == AsserterToError +func (asserter asserter) isErrorOnly() bool { + return asserter == asserterToError } -func (asserter Asserter) hasToError() bool { - return asserter&AsserterToError != 0 +func (asserter asserter) hasToError() bool { + return asserter&asserterToError != 0 } -func (asserter Asserter) hasStackTrace() bool { - return asserter&AsserterStackTrace != 0 +func (asserter asserter) hasStackTrace() bool { + return asserter&asserterStackTrace != 0 } -func (asserter Asserter) hasCallerInfo() bool { - return asserter&AsserterCallerInfo != 0 || asserter.hasFormattedCallerInfo() +func (asserter asserter) hasCallerInfo() bool { + return asserter&asserterCallerInfo != 0 || asserter.hasFormattedCallerInfo() } -func (asserter Asserter) hasFormattedCallerInfo() bool { - return asserter&AsserterFormattedCallerInfo != 0 +func (asserter asserter) hasFormattedCallerInfo() bool { + return asserter&asserterFormattedCallerInfo != 0 } // isUnitTesting is expensive because it calls tester(). think carefully where // to use it -func (asserter Asserter) isUnitTesting() bool { - return asserter&AsserterUnitTesting != 0 && tester() != nil +func (asserter asserter) isUnitTesting() bool { + return asserter&asserterUnitTesting != 0 && tester() != nil } From 4e2a07a719808592b6318b6daf195ad2889b01b8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 14 Mar 2024 18:14:57 +0200 Subject: [PATCH 55/88] update v1.0.0 release comments --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfb4004..2e92cf8 100644 --- a/README.md +++ b/README.md @@ -530,9 +530,12 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release ##### 1.0.0 -- Documentation updates and cleanups +- Finally! We are very happy, and thanks to all. +- Lots of documentation updates and cleanups for version 1 - `Catch/Handle` take unlimited amount error handler functions - - allows building e.g. error handling middleware + - allows building e.g. error handling middlewares - automatic outputs aren't overwritten by given args, only with `assert.Plain` -- Minor API fixes: `Result2.Def2()`, etc. -- technical refactoring like variadic function calls only in API level +- Minor API fixes to simplify it: + - remove exported vars, obsolete types and funcs from `assert` pkg + - `Result2.Def2()` sets only Val2 +- technical refactorings: variadic function calls only in API level From a9a9c89e42a78b47b522ddfd294a4020289a05ad Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 14 Mar 2024 18:15:37 +0200 Subject: [PATCH 56/88] docs cleaned and simplify, remove wordiness --- assert/doc.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index f225ba7..d30fdc6 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -1,7 +1,7 @@ /* Package assert includes runtime assertion helpers both for normal execution as well as a assertion package for Go's testing. What makes solution unique is its -capable to support both modes with same API. Only thing you need to do is to +capable to support both modes with the same API. Only thing you need to do is to add a PushTester line at the beginning of your unit tests: func TestInvite(t *testing.T) { @@ -13,13 +13,13 @@ add a PushTester line at the beginning of your unit tests: # Merge Runtime And Unit Test Assertions -Especially powerful feature is that even if some assertion violation happens -during the execution of called functions, and not the test function itself, they -are catched. See the above example. If assertion failure happens inside the -Invite() function instead of the actual test function, TestInvite, it's still -reported correctly as normal test failure. It doesn't matter how deep the -recursion is, or if parallel test runs are performed. It works just as you -hoped. +If some assertion violation happens in the deep call stack, they are still +reported as a test failure. See the above example. If assertion failure happens +somewhere inside the Invite() functions call stack, it's still reported +correctly as a test failure of the TestInvite. It doesn't matter how deep the +recursion is, or if parallel test runs are performed. The failure report +includes all the locations of the meaningful call stack steps. See the chapter +Call Stack Traveral During Tests. This is the actual Invite function implementation's first two lines. Even if the assertion line is written more for runtime detection and active comment, it @@ -28,16 +28,12 @@ catches all unit test errors as well: func (c Chain) Invite(...) { assert.That(c.isLeaf(invitersKey), "only leaf can invite") -# Call Stack Traversal During tests +# Call Stack Traversal During Tests -The asserter package has super powerful feature. It allows us track assertion -violations over package and even module boundaries. When using err2 assert -package for runtime Asserts and assert violation happens in whatever package -and module, the whole call stack is brought to unit test logs. Naturally this is -optional. Only thing you need to do is set proper asserter and call PushTester. - - // use unit testing asserter - assert.SetDefault(assert.TestFull) +Assert package allows us track assertion violations over package and even module +boundaries. When an assertion fails during the unit testing, the whole call +stack is brought to unit test logs. And some help with your IDE, that output can +be tranferred to a location list, for examplem in Neovim/Vim. With large multi repo environment this has proven to be valuable. From 4550b2ee86dca2870fea2ed40d2cfc3ccd232765 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 15 Mar 2024 16:37:00 +0200 Subject: [PATCH 57/88] readability of the assert pkg main doc --- assert/doc.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index d30fdc6..841c60b 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -44,14 +44,15 @@ raise up quality of our software. "Assertions are active comments" -The package offers a convenient way to set preconditions to code which allow us -detect programming errors and API violations faster. Still allowing +The assert package offers a convenient way to set preconditions to code which +allow us detect programming errors and API violations faster. Still allowing production-time error handling if needed. And everything is automatic. You can -set proper asserter according to flag or environment variable. This allows -developer, operator and every-day user share the exact same binary but get the -error messages and diagnostic they need. +set asserter with SetDefault function or --asserter flag if Go's flag package is +in use. This allows developer, operator and every-day user share the exact same +binary but get the error messages and diagnostic they need. - // add formatted caller info for normal errors coming from assertions + // Production asserter adds formatted caller info to normal errors. + // Information is transported thru error values when err2.Handle is in use. assert.SetDefault(assert.Production) Please see the code examples for more information. From efed678dac2e310b6f85f34426cec300a8a2a0d1 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 15 Mar 2024 16:37:47 +0200 Subject: [PATCH 58/88] more docs to Plain, Prod, and Dev --- assert/assert.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index eeb7d06..2a47037 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -18,22 +18,28 @@ type defInd = uint32 const ( // Plain converts asserts just plain K&D error messages without extra - // information. + // information. That's useful for apps that want to use assert package to + // validate e.g. command fields: + // + // assert.NotEmpty(c.PoolName, "pool name cannot be empty") + // + // Note, that Plain is only asserter that override auto-generated assertion + // messages with given arguments like 'pool name cannot be empty'. Others + // add given arguments at the end of the auto-generated assert message. Plain defInd = 0 + iota - // Production (pkg default) is the best asserter for most uses. The + // Production (pkg default) is the best asserter for most cases. The // assertion violations are treated as Go error values. And only a - // pragmatic caller info is automatically included into the error message - // like file name, line number, and caller function, all in one line: + // pragmatic caller info is included into the error values like source + // filename, line number, and caller function, all in one line: // // copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty Production - // Development is the best asserter for most development uses. The - // assertion violations are treated as Go error values. And a formatted - // caller info is automatically included to the error message like file - // name, line number, and caller function. Everything in a beautiful - // multi-line message: + // Development is the best asserter for development use. The assertion + // violations are treated as Go error values. And a formatted caller info + // is included to the error message like source filename , line number, and + // caller function. Everything in a noticeable multi-line message: // // -------------------------------- // Assertion Fault at: From aafd5de992dac07913bb627e4ab5d2c37d23ca53 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 15 Mar 2024 16:53:51 +0200 Subject: [PATCH 59/88] move asserter constant's documentation above of section: go doc way --- assert/assert.go | 131 ++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 2a47037..bc9b2f9 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -16,77 +16,78 @@ import ( type defInd = uint32 +// Asserters are the way to set what kind of messages assert package outputs if +// assertion is violated. +// +// [Plain] converts asserts just plain K&D error messages without extra +// information. That's useful for apps that want to use assert package to +// validate e.g. command fields: +// +// assert.NotEmpty(c.PoolName, "pool name cannot be empty") +// +// Note, that Plain is only asserter that override auto-generated assertion +// messages with given arguments like 'pool name cannot be empty'. Others add +// given arguments at the end of the auto-generated assert message. +// +// [Production] (pkg's default) is the best asserter for most cases. The +// assertion violations are treated as Go error values. And only a pragmatic +// caller info is included into the error values like source filename, line +// number, and caller function, all in one line: +// +// copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty +// +// [Development] is the best asserter for development use. The assertion +// violations are treated as Go error values. And a formatted caller info is +// included to the error message like source filename , line number, and caller +// function. Everything in a noticeable multi-line message: +// +// -------------------------------- +// Assertion Fault at: +// main.go:37 CopyFile(): +// assertion violation: string shouldn't be empty +// -------------------------------- +// +// [Test] minimalistic asserter for unit test use. More pragmatic is the +// TestFull asserter (test default). +// +// Use this asserter if your IDE/editor doesn't support full file names and it +// relies a relative path (Go standard). You can use this also if you need +// temporary problem solving for your programming environment. +// +// [TestFull] asserter (test default). The TestFull asserter includes the caller +// info and the call stack for unit testing, similarly like err2's error traces. +// +// The call stack produced by the test asserts can be used over Go module +// boundaries. For example, if your app and it's sub packages both use +// err2/assert for unit testing and runtime checks, the runtime assertions will +// be automatically converted to test asserts. If any of the runtime asserts of +// the sub packages fails during the app tests, the app test fails as well. +// +// Note, that the cross-module assertions produce long file names (path +// included), and some of the Go test result parsers cannot handle that. A +// proper test result parser like 'github.com/lainio/nvim-go' (fork) works very +// well. Also most of the make result parsers can process the output properly +// and allow traverse of locations of the error trace. +// +// [Debug] asserter transforms assertion violations to panic calls where panic +// object's type is string, i.e., err2 package treats it as a normal panic, not +// an error. +// +// For example, the pattern that e.g. Go's standard library uses: +// +// if p == nil { +// panic("pkg: ptr cannot be nil") +// } +// +// is equal to: +// +// assert.NotNil(p) const ( - // Plain converts asserts just plain K&D error messages without extra - // information. That's useful for apps that want to use assert package to - // validate e.g. command fields: - // - // assert.NotEmpty(c.PoolName, "pool name cannot be empty") - // - // Note, that Plain is only asserter that override auto-generated assertion - // messages with given arguments like 'pool name cannot be empty'. Others - // add given arguments at the end of the auto-generated assert message. Plain defInd = 0 + iota - - // Production (pkg default) is the best asserter for most cases. The - // assertion violations are treated as Go error values. And only a - // pragmatic caller info is included into the error values like source - // filename, line number, and caller function, all in one line: - // - // copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty Production - - // Development is the best asserter for development use. The assertion - // violations are treated as Go error values. And a formatted caller info - // is included to the error message like source filename , line number, and - // caller function. Everything in a noticeable multi-line message: - // - // -------------------------------- - // Assertion Fault at: - // main.go:37 CopyFile(): - // assertion violation: string shouldn't be empty - // -------------------------------- Development - - // Test minimalistic asserter for unit test use. More pragmatic is the - // TestFull asserter (test default). - // - // Use this asserter if your IDE/editor doesn't support full file names and - // it relies a relative path (Go standard). You can use this also if you - // need temporary problem solving for your programming environment. Test - - // TestFull asserter (test default). The TestFull asserter includes the - // caller info and the call stack for unit testing, similarly like err2's - // error traces. - // - // The call stack produced by the test asserts can be used over Go module - // boundaries. For example, if your app and it's sub packages both use - // err2/assert for unit testing and runtime checks, the runtime assertions - // will be automatically converted to test asserts. If any of the runtime - // asserts of the sub packages fails during the app tests, the app test - // fails as well. - // - // Note, that the cross-module assertions produce long file names (path - // included), and some of the Go test result parsers cannot handle that. - // A proper test result parser like 'github.com/lainio/nvim-go' (fork) - // works very well. Also most of the make result parsers can process the - // output properly and allow traverse of locations of the error trace. TestFull - - // Debug asserter transforms assertion violations to panic calls where - // panic object's type is string, i.e., err2 package treats it as a normal - // panic, not an error. - // - // For example, the pattern that e.g. Go's standard library uses: - // - // if p == nil { - // panic("pkg: ptr cannot be nil") - // } - // - // is equal to: - // - // assert.NotNil(p) Debug ) From 316ba86faa2fb6c7a067e9fded6d078ca715cbc3 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 15 Mar 2024 17:22:55 +0200 Subject: [PATCH 60/88] adding links to assert pkg --- assert/assert.go | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index bc9b2f9..e803542 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -169,8 +169,9 @@ const ( // }) // } // -// Because PushTester returns PopTester it allows us to merge these two calls to -// one line. See the first t.Run call above. See more information in PopTester. +// Because PushTester returns [PopTester] it allows us to merge these two calls +// to one line. See the first t.Run call above. See more information in +// [PopTester]. // // PushTester allows you to change the current default asserter by accepting it // as a second argument. @@ -200,7 +201,7 @@ func PushTester(t testing.TB, a ...defInd) function { // memory cleanup and adding similar to err2.Catch error/panic safety for tests. // By using PopTester you get error logs tuned for unit testing. // -// You have two ways to call PopTester. With defer right after PushTester: +// You have two ways to call [PopTester]. With defer right after [PushTester]: // // for _, tt := range tests { // t.Run(tt.name, func(t *testing.T) { @@ -211,7 +212,7 @@ func PushTester(t testing.TB, a ...defInd) function { // }) // } // -// If you want to have one liner to combine Push/PopTester: +// If you want, you can combine [PushTester] and PopTester to one-liner: // // defer assert.PushTester(t)() func PopTester() { @@ -240,7 +241,7 @@ func PopTester() { // First, print the call stack. Note, that we aren't support full error // tracing with unit test logging. However, using it has proved the top // level error stack as more enough. Even so that we could consider using - // it for normal error stack straces if it would be possible. + // it for normal error stack traces if it would be possible. const stackLvl = 6 // amount of functions before we're here debug.PrintStackForTest(os.Stderr, stackLvl) @@ -302,9 +303,8 @@ func Nil[T any](p *T, a ...any) { // (default Asserter) with the given message. // // Note, use this only for real interface types. Go's interface's has two values -// so this won't work e.g. slices! Read more information about interface type. -// -// https://go.dev/doc/faq#nil_error +// so this won't work e.g. slices! Read more information about interface type: +// https://go.dev/doc/faq#nil_error. func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" @@ -316,9 +316,8 @@ func INil(i any, a ...any) { // panics/errors (default Asserter) with the given message. // // Note, use this only for real interface types. Go's interface's has two values -// so this won't work e.g. slices! Read more information about interface type. -// -// https://go.dev/doc/faq#nil_error +// so this won't work e.g. slices! Read more information about interface type: +// https://go.dev/doc/faq#nil_error. func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" @@ -414,7 +413,7 @@ func NotDeepEqual(val, want any, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Len(obj string, length int, a ...any) { l := len(obj) @@ -430,7 +429,7 @@ func Len(obj string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Longer(s string, length int, a ...any) { l := len(s) @@ -446,7 +445,7 @@ func Longer(s string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Shorter(str string, length int, a ...any) { l := len(str) @@ -462,7 +461,7 @@ func Shorter(str string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SLen[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -478,7 +477,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SLonger[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -494,7 +493,7 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SShorter[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) @@ -510,7 +509,7 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -526,7 +525,7 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -542,7 +541,7 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) @@ -558,7 +557,7 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CLen[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -574,7 +573,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CLonger[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -590,7 +589,7 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CShorter[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) @@ -638,7 +637,7 @@ func Empty(obj string, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SNotEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) @@ -655,7 +654,7 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { // You can append the generated got-want message by using optional message // arguments. // -// Note! This is reasonably fast but not as fast as 'That' because of lacking +// Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) @@ -670,14 +669,15 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note. We recommend that you prefer try.To. They work exactly the same during +// Note. We recommend that you prefer [try.To]. They work exactly the same during // the test runs and you can use the same code for both: runtime and tests. // However, there are cases that you want assert that there is no error in cases // where fast fail and immediate stop of execution is wanted at runtime. With -// asserts you get the file location as well. (See the asserters). +// asserts ([Production], [Development], [Debug]) you get the file location as +// well. func NoError(err error, a ...any) { if err != nil { - defMsg := "NoError:" + assertionMsg + conCatErrStr + err.Error() + defMsg := assertionMsg + conCatErrStr + err.Error() current().reportAssertionFault(defMsg, a) } } From 8ab23ac772ffbd06c4e2b85ec2cca09a199dc455 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 10:33:20 +0200 Subject: [PATCH 61/88] use hyperlinks in go docs --- handlers.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/handlers.go b/handlers.go index fc2303a..2bdd89f 100644 --- a/handlers.go +++ b/handlers.go @@ -7,7 +7,7 @@ import ( "github.com/lainio/err2/internal/handler" ) -// Stderr is a built-in helper to use with Handle and Catch. It prints the +// Stderr is a built-in helper to use with [Handle] and [Catch]. It prints the // error to stderr and it resets the current error value. It's a handy Catch // handler in main function. // @@ -51,10 +51,10 @@ func Noop(err error) error { return err } // defer err2.Handle(&err, err2.Reset) func Reset(error) error { return nil } -// Err is a built-in helper to use with Handle and Catch. It offers simplifier +// Err is a built-in helper to use with [Handle] and [Catch]. It offers simplifier // for error handling function for cases where you don't need to change the // current error value. For instance, if you want to just write error to stdout, -// and don't want to use SetLogTracer and keep it to write to your logs. +// and don't want to use [SetLogTracer] and keep it to write to your logs. // // defer err2.Catch(err2.Err(func(err error) { // fmt.Println("ERROR:", err) @@ -73,7 +73,7 @@ func Err(f func(err error)) Handler { const lvl = 10 -// Log prints error string to the current log that is set by SetLogTracer. +// Log prints error string to the current log that is set by [SetLogTracer]. func Log(err error) error { if err == nil { return nil @@ -82,7 +82,7 @@ func Log(err error) error { return err } -// StderrNoReset is a built-in helper to use with Handle and Catch. It prints +// StderrNoReset is a built-in helper to use with [Handle] and [Catch]. It prints // the error to stderr. If you need to reset err value use Stderr instead. // // You can use it like this: @@ -97,7 +97,7 @@ func StderrNoReset(err error) error { return err } -// StdoutNoReset is a built-in helper to use with Handle and Catch. It prints +// StdoutNoReset is a built-in helper to use with [Handle] and [Catch]. It prints // the error to stdout. // // You can use it like this: From 089db8740302596fea27ad4cb352f3c0ee60777d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 10:35:24 +0200 Subject: [PATCH 62/88] hyperlinks in docs --- try/try.go | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/try/try.go b/try/try.go index 21b63a6..98e7d8b 100644 --- a/try/try.go +++ b/try/try.go @@ -1,6 +1,6 @@ /* -Package try is a package for try.ToX functions that implement the error -checking. try.ToX functions check 'if err != nil' and if it throws the err to the +Package try is a package for [To], [To1], and [To2] functions that implement the error +checking. [To] functions check 'if err != nil' and if it throws the err to the error handlers, which are implemented by the err2 package. More information about err2 and try packager roles can be seen in the FileCopy example: @@ -20,31 +20,33 @@ about err2 and try packager roles can be seen in the FileCopy example: # try.To — Fast Checking -All of the try.To functions are as fast as the simple 'if err != nil {' +All of the [To] functions are as fast as the simple 'if err != nil {' statement, thanks to the compiler inlining and optimization. -Note that try.ToX function names end to a number (x) because: +We have three error check functions: [To], [To1], and [To2] because: "No variadic type parameters. There is no support for variadic type parameters, which would permit writing a single generic function that takes different numbers of both type parameters and regular parameters." - Go Generics -The leading number at the end of the To2 tells that To2 takes two different -non-error arguments, and the third one must be an error value. +For example, the leading number at the end of the [To2] tells that [To2] takes +two different non-error arguments, and the third one must be an error value. -Looking at the FileCopy example again, you see that all the functions -are directed to try.To1 are returning (type1, error) tuples. All of these -tuples are the correct input to try.To1. However, if you have a function that -returns (type1, type2, error), you must use try.To2 function to check the error. -Currently the try.To3 takes (3 + 1) return values which is the greatest amount. +Looking at the [CopyFile] example again, you see that all the functions +are directed to [To1] are returning (type1, error) tuples. All of these +tuples are the correct input to [To1]. However, if you have a function that +returns (type1, type2, error), you must use [To2] function to check the error. +Currently the [To3] takes (3 + 1) return values which is the greatest amount. If more is needed, let us know. # try.Out — Error Handling Language -The try package offers an error handling DSL. It's for cases where you want to -do something specific after error returing function call. For example, you might -want to ignore the specific error and use a default value. That's possible with -the following code: +The try package offers an error handling DSL that's based on [Out], [Out1], and +[Out2] functions and their corresponding return values [Result], [Result1], and +[Result2]. DSL is for the cases where you want to do something specific after +error returning function call. Those cases are rare. But you might want, for +example, to ignore the specific error and use a default value without any +special error handling. That's possible with the following code: number := try.Out1(strconv.Atoi(str)).Catch(100) @@ -71,7 +73,7 @@ import ( // check the value. If an error occurs, it panics the error so that err2 // handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist // in the call stack and To panics an error, the error is not handled, and the -// app will crash. When using try.To functions you should always have proper +// app will crash. When using To function you should always have proper // err2.Handle or err2.Catch statements in the call stack. // // defer err2.Handle(&err) @@ -87,7 +89,7 @@ func To(err error) { // and check the error value. If an error occurs, it panics the error so that // err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch // exist in the call stack and To1 panics an error, the error is not handled, -// and the app will crash. When using try.To1 functions you should always have +// and the app will crash. When using To1 function you should always have // proper err2.Handle or err2.Catch statements in the call stack. // // defer err2.Handle(&err) @@ -102,7 +104,7 @@ func To1[T any](v T, err error) T { // and check the error value. If an error occurs, it panics the error so that // err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch // exist in the call stack and To2 panics an error, the error is not handled, -// and the app will crash. When using try.To2 functions you should always have +// and the app will crash. When using To2 function you should always have // proper err2.Handle or err2.Catch statements in the call stack. // // defer err2.Handle(&err) @@ -117,7 +119,7 @@ func To2[T, U any](v1 T, v2 U, err error) (T, U) { // error) and check the error value. If an error occurs, it panics the error so // that err2 handlers can catch it if needed. Note! If no err2.Handle or // err2.Catch exist in the call stack and To3 panics an error, the error is -// not handled, and the app will crash. When using try.To3 functions you should +// not handled, and the app will crash. When using To3 function you should // always have proper err2.Handle or err2.Catch statements in the call stack. func To3[T, U, V any](v1 T, v2 U, v3 V, err error) (T, U, V) { To(err) From 474ff2cdc80718ae64e8d069e7859fe38e770977 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 10:36:03 +0200 Subject: [PATCH 63/88] start hyperlinks for docs --- err2.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/err2.go b/err2.go index 911df06..d9f2e2f 100644 --- a/err2.go +++ b/err2.go @@ -8,9 +8,9 @@ import ( ) type ( - // Handler is a function type used to process error values in Handle and - // Catch. We currently have a few build-ins of the Handler: err2.Noop, - // err2.Reset, etc. + // Handler is a function type used to process error values in [Handle] and + // [Catch]. We currently have a few build-ins of the Handler: [Noop], + // [Reset], etc. Handler = handler.ErrorFn ) @@ -34,8 +34,8 @@ var ( ErrRecoverable = errors.New("recoverable") // Stdnull implements io.Writer that writes nothing, e.g., - // err2.SetLogTracer in cases you don't want to use automatic log writer, - // i.e., LogTracer == /dev/null. It can be used to change how the Catch + // [SetLogTracer] in cases you don't want to use automatic log writer, + // i.e., [LogTracer] == /dev/null. It can be used to change how the Catch // works, e.g., in CLI apps. Stdnull = &nullDev{} ) @@ -66,7 +66,7 @@ var ( // defer err2.Handle(&err, nil) // nil arg disable automatic annotation. // // In case of the actual error handling, the handler function should be given as -// an second argument: +// a second argument: // // defer err2.Handle(&err, func(err error) error { // if rmErr := os.Remove(dst); rmErr != nil { @@ -77,7 +77,7 @@ var ( // // You can have unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one -// of them resets the error like Reset (notice the other predefined error +// of them resets the error like [Reset] (notice the other predefined error // handlers) in the next samples: // // defer err2.Handle(&err, err2.Reset, err2.Log) // Log not called @@ -118,9 +118,10 @@ func Handle(err *error, a ...any) { // the defer. // // The deferred Catch is very convenient, because it makes your current -// goroutine panic and error-safe, one line only! You can fine tune its -// behavior with functions like err2.SetErrorTrace, assert.SetDefault, and -// logging settings. Start with the defaults and simplest version of Catch: +// goroutine panic and error-safe. You can fine tune its 'global' behavior with +// functions like [SetErrorTracer], [SetPanicTracer], and [SetLogTracer]. Its +// 'local' behavior depends the arguments you give it. Let's start with the +// defaults and simplest version of Catch: // // defer err2.Catch() // @@ -139,9 +140,9 @@ func Handle(err *error, a ...any) { // // The next one stops errors and panics, but allows you handle errors, like // cleanups, etc. The error handler function has same signature as Handle's -// error handling function, i.e., err2.Handler. By returning nil resets the +// error handling function [Handler]. By returning nil resets the // error, which allows e.g. prevent automatic error logs to happening. -// Otherwise, the output results depends on the current Tracer and assert +// Otherwise, the output results depends on the current [Tracer] and assert // settings. The default trace setting prints call stacks for panics but not for // errors: // From c9d57647841dfde0d9e4f24f848fc0703327bbdf Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 11:56:43 +0200 Subject: [PATCH 64/88] sentinel errors docs, and hyperlinks --- err2.go | 58 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/err2.go b/err2.go index d9f2e2f..8316ce5 100644 --- a/err2.go +++ b/err2.go @@ -14,40 +14,42 @@ type ( Handler = handler.ErrorFn ) +// Sentinel error value helpers. They are convenient thanks to [try.IsNotFound] +// and similar functions. +// +// [ErrNotFound] ... [ErrNotEnabled] are similar no-error like [io.EOF] for +// those who really want to use error return values to transport non errors. +// It's far better to have discriminated unions as errors for function calls. +// But if you insist the related helpers are in they [try] package: +// try.IsNotFound, ... +// +// [ErrRecoverable] and [ErrNotRecoverable] since Go 1.20 wraps multiple errors +// same time, i.e. wrapped errors aren't list anymore but tree. This allows mark +// multiple semantics to same error. These error are mainly for that purpose. var ( - // ErrNotFound is similar *no-error* like io.EOF for those who really want to - // use error return values to transport non errors. It's far better to have - // discriminated unions as errors for function calls. But if you insist the - // related helpers are in they try package: try.IsNotFound(), ... These - // 'global' errors and their helper functions in try package are for - // experimenting now. - ErrNotFound = errors.New("not found") - ErrNotExist = errors.New("not exist") - ErrAlreadyExist = errors.New("already exist") - ErrNotAccess = errors.New("permission denied") - ErrNotEnabled = errors.New("not enabled") - - // Since Go 1.20 wraps multiple errors same time, i.e. wrapped errors - // aren't list anymore but tree. This allows mark multiple semantics to - // same error. These error are mainly for that purpose. + ErrNotFound = errors.New("not found") + ErrNotExist = errors.New("not exist") + ErrAlreadyExist = errors.New("already exist") + ErrNotAccess = errors.New("permission denied") + ErrNotEnabled = errors.New("not enabled") ErrNotRecoverable = errors.New("cannot recover") ErrRecoverable = errors.New("recoverable") - - // Stdnull implements io.Writer that writes nothing, e.g., - // [SetLogTracer] in cases you don't want to use automatic log writer, - // i.e., [LogTracer] == /dev/null. It can be used to change how the Catch - // works, e.g., in CLI apps. - Stdnull = &nullDev{} ) +// Stdnull implements [io.Writer] that writes nothing, e.g., +// [SetLogTracer] in cases you don't want to use automatic log writer, +// i.e., [LogTracer] == /dev/null. It can be used to change how the [Catch] +// works, e.g., in CLI apps. +var Stdnull = &nullDev{} + // Handle is the general purpose error handling function. What makes it so // convenient is its ability to handle all error handling cases: // - just return the error value to caller // - annotate the error value // - execute real error handling like cleanup and releasing resources. // -// There is no performance penalty. The handler is called only when err != nil. -// There is no limit how many Handle functions can be added to defer stack. They +// There's no performance penalty. The handler is called only when err != nil. +// There's no limit how many Handle functions can be added to defer stack. They // all are called if an error has occurred. // // The function has an automatic mode where errors are annotated by function @@ -58,7 +60,7 @@ var ( // // Note. If you are still using sentinel errors you must be careful with the // automatic error annotation because it uses wrapping. If you must keep the -// error value got from error checks: 'try.To(..)', you must disable automatic +// error value got from error checks: [try.To], you must disable automatic // error annotation (%w), or set the returned error values in the handler // function. Disabling can be done by setting second argument nil: // @@ -112,10 +114,10 @@ func Handle(err *error, a ...any) { // Catch is a convenient helper to those functions that doesn't return errors. // Note, that Catch always catch the panics. If you don't want to stop them -// (recover) you should add panic handler and continue panicking there. There -// can be only one deferred Catch function per non error returning function like -// main(). There is several ways to use the Catch function. And always remember -// the defer. +// (i.e., use of [recover]) you should add panic handler and continue panicking +// there. There can be only one deferred Catch function per non error returning +// function like main(). There is several ways to use the Catch function. And +// always remember the [defer]. // // The deferred Catch is very convenient, because it makes your current // goroutine panic and error-safe. You can fine tune its 'global' behavior with From 9ec0e644c7d1c4bb0505e394b6f1af277484677a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 12:07:13 +0200 Subject: [PATCH 65/88] err2 main doc's links --- doc.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc.go b/doc.go index f92ed7f..0b27e4b 100644 --- a/doc.go +++ b/doc.go @@ -62,7 +62,7 @@ we can write b := try.To1(io.ReadAll(r)) -Note that try.ToX functions are as fast as if err != nil statements. Please see +Note that [try.To] functions are as fast as if err != nil statements. Please see the try package documentation for more information about the error checks. # Automatic Stack Tracing @@ -72,13 +72,13 @@ err2 offers optional stack tracing. And yes, it's fully automatic. Just call flag.Parse() # this is enough for err2 pkg to add its flags at the beginning your app, e.g. main function, or set the tracers -programmatically (before flag.Parse if you are using that): +programmatically (before [flag.Parse] if you are using that): err2.SetErrorTracer(os.Stderr) // write error stack trace to stderr or err2.SetPanicTracer(log.Writer()) // panic stack trace to std logger -Note. Since err2.Catch's default mode is to catch panics, the panic tracer's +Note. Since [Catch]'s default mode is to catch panics, the panic tracer's default values is os.Stderr. The default error tracer is nil. err2.SetPanicTracer(os.Stderr) // panic stack tracer's default is stderr @@ -86,8 +86,8 @@ default values is os.Stderr. The default error tracer is nil. # Automatic Logging -Same err2 capablities support automatic logging like the err2.Catch and -try.Result.Logf functions. To be able to tune up how logging behaves we offer a +Same err2 capablities support automatic logging like the [Catch] and +[try.Result.Logf] functions. To be able to tune up how logging behaves we offer a tracer API: err2.SetLogTracer(nil) // the default is nil where std log pkg is used. @@ -104,7 +104,7 @@ And the following flags are supported (="default-value"): -err2-trace="nil" A name of the stream currently supported stderr, stdout or nil -Note, that you have called err2.SetErrorTracer and others, before you call +Note, that you have called [SetErrorTracer] and others, before you call flag.Parse. This allows you set the defaults according your app's need and allow end-user change them during the runtime. @@ -115,7 +115,7 @@ safety. In every function which uses err2 or try package for error-checking has to have at least one declarative error handler if it returns error value. If there are no error handlers and error occurs it panics. We think that panicking for the errors is much better than not checking errors at all. Nevertheless, if -the call stack includes any err2 error handlers like err2.Handle the error is +the call stack includes any err2 error handlers like [Handle] the error is handled where the handler is saved to defer-stack. (defer is not lexically scoped) From 1790aa0e0f41db62b63907b43555f9bc595c7d76 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 12:13:02 +0200 Subject: [PATCH 66/88] more links --- try/try.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/try/try.go b/try/try.go index 98e7d8b..781d129 100644 --- a/try/try.go +++ b/try/try.go @@ -58,7 +58,8 @@ Or you might just want to change it later to error return: try.Out(os.Remove(dst)).Handle("file cleanup fail") -Please see the documentation and examples of ResultX types and their methods. +Please see the documentation and examples of [Result], [Result1], and [Result2] +types and their methods. */ package try From cc31822efb6d4e391969f056b38e6b5726715dc2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 15:01:19 +0200 Subject: [PATCH 67/88] better message in example --- try/try_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/try/try_test.go b/try/try_test.go index 4240ad1..6aaba1e 100644 --- a/try/try_test.go +++ b/try/try_test.go @@ -105,7 +105,7 @@ func ExampleIsEOF1() { func Example_copyFile() { copyFile := func(src, dst string) (err error) { - defer err2.Handle(&err, "copy %s %s", src, dst) + defer err2.Handle(&err, "copy file %s %s", src, dst) // These try package helpers are as fast as Check() calls which is as // fast as `if err != nil {}` @@ -126,5 +126,5 @@ func Example_copyFile() { if err != nil { fmt.Println(err) } - // Output: copy /notfound/path/file.go /notfound/path/file.bak: open /notfound/path/file.go: no such file or directory + // Output: copy file /notfound/path/file.go /notfound/path/file.bak: open /notfound/path/file.go: no such file or directory } From 28018f45f0f380bb446392eb63b9bbe17e10d5d1 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 16 Mar 2024 16:09:21 +0200 Subject: [PATCH 68/88] shitload of links to docs --- assert/doc.go | 4 +-- doc.go | 6 ++-- err2.go | 13 +++---- handlers.go | 12 +++---- tracer.go | 40 ++++++++++----------- try/out.go | 96 +++++++++++++++++++++++++-------------------------- try/try.go | 67 +++++++++++++++++------------------ 7 files changed, 120 insertions(+), 118 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 841c60b..0782af5 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -47,7 +47,7 @@ raise up quality of our software. The assert package offers a convenient way to set preconditions to code which allow us detect programming errors and API violations faster. Still allowing production-time error handling if needed. And everything is automatic. You can -set asserter with SetDefault function or --asserter flag if Go's flag package is +set asserter with [SetDefault] function or --asserter flag if Go's flag package is in use. This allows developer, operator and every-day user share the exact same binary but get the error messages and diagnostic they need. @@ -73,7 +73,7 @@ And assert package's configuration flags are inserted. assert.That's performance is equal to the if-statement thanks for inlining. And the most of the generics-based versions are about the equally fast. Practice has thought that we should prefer other than assert.That because by using detailed -version like assert.Shorter we get precise error messages automatically. Some +version like [assert.Shorter] we get precise error messages automatically. Some also prefer readability of specific asserters. If your algorithm is performance-critical please run `make bench` in the err2 diff --git a/doc.go b/doc.go index 0b27e4b..7d260c3 100644 --- a/doc.go +++ b/doc.go @@ -50,7 +50,7 @@ them. The CopyFile example shows how it works: # Error checks and Automatic Error Propagation -The try package provides convenient helpers to check the errors. For example, +The [github.com/lainio/err2/try] package provides convenient helpers to check the errors. For example, instead of b, err := io.ReadAll(r) @@ -94,7 +94,7 @@ tracer API: # Flag Package Support -The err2 package supports Go's flags. All you need to do is to call flag.Parse. +The err2 package supports Go's flags. All you need to do is to call [flag.Parse]. And the following flags are supported (="default-value"): -err2-log="nil" @@ -105,7 +105,7 @@ And the following flags are supported (="default-value"): A name of the stream currently supported stderr, stdout or nil Note, that you have called [SetErrorTracer] and others, before you call -flag.Parse. This allows you set the defaults according your app's need and allow +[flag.Parse]. This allows you set the defaults according your app's need and allow end-user change them during the runtime. # Error handling diff --git a/err2.go b/err2.go index 8316ce5..3b43ca7 100644 --- a/err2.go +++ b/err2.go @@ -20,8 +20,9 @@ type ( // [ErrNotFound] ... [ErrNotEnabled] are similar no-error like [io.EOF] for // those who really want to use error return values to transport non errors. // It's far better to have discriminated unions as errors for function calls. -// But if you insist the related helpers are in they [try] package: -// try.IsNotFound, ... +// But if you insist the related helpers are in they +// [github.com/lainio/err2/try] package: +// [github.com/lainio/err2/try.IsNotFound], ... // // [ErrRecoverable] and [ErrNotRecoverable] since Go 1.20 wraps multiple errors // same time, i.e. wrapped errors aren't list anymore but tree. This allows mark @@ -60,9 +61,9 @@ var Stdnull = &nullDev{} // // Note. If you are still using sentinel errors you must be careful with the // automatic error annotation because it uses wrapping. If you must keep the -// error value got from error checks: [try.To], you must disable automatic -// error annotation (%w), or set the returned error values in the handler -// function. Disabling can be done by setting second argument nil: +// error value got from error checks: [github.com/lainio/err2/try.To], you must +// disable automatic error annotation (%w), or set the returned error values in +// the handler function. Disabling can be done by setting second argument nil: // // func SaveData(...) (err error) { // defer err2.Handle(&err, nil) // nil arg disable automatic annotation. @@ -183,7 +184,7 @@ func Catch(a ...any) { } // Throwf builds and throws (panics) an error. For creation it's similar to -// fmt.Errorf. Because panic is used to transport the error instead of error +// [fmt.Errorf]. Because panic is used to transport the error instead of error // return value, it's called only if you want to non-local control structure for // error handling, i.e. your current function doesn't have error return value. // diff --git a/handlers.go b/handlers.go index 2bdd89f..864cbdf 100644 --- a/handlers.go +++ b/handlers.go @@ -8,7 +8,7 @@ import ( ) // Stderr is a built-in helper to use with [Handle] and [Catch]. It prints the -// error to stderr and it resets the current error value. It's a handy Catch +// error to stderr and it resets the current error value. It's a handy [Catch] // handler in main function. // // You can use it like this: @@ -23,8 +23,8 @@ func Stderr(err error) error { return nil } -// Stdout is a built-in helper to use with Handle and Catch. It prints the -// error to stdout and it resets the current error value. It's a handy Catch +// Stdout is a built-in helper to use with [Handle] and [Catch]. It prints the +// error to stdout and it resets the current error value. It's a handy [Catch] // handler in main function. // // You can use it like this: @@ -39,13 +39,13 @@ func Stdout(err error) error { return nil } -// Noop is a built-in helper to use with Handle and Catch. It keeps the current +// Noop is a built-in helper to use with [Handle] and [Catch]. It keeps the current // error value the same. You can use it like this: // // defer err2.Handle(&err, err2.Noop) func Noop(err error) error { return err } -// Reset is a built-in helper to use with Handle and Catch. It sets the current +// Reset is a built-in helper to use with [Handle] and [Catch]. It sets the current // error value to nil. You can use it like this to reset the error: // // defer err2.Handle(&err, err2.Reset) @@ -60,7 +60,7 @@ func Reset(error) error { return nil } // fmt.Println("ERROR:", err) // })) // -// Note, that since Err helper we have other helpers like Stdout that allows +// Note, that since Err helper we have other helpers like [Stdout] that allows // previous block be written as simple as: // // defer err2.Catch(err2.Stdout) diff --git a/tracer.go b/tracer.go index 22ae29b..3c14e55 100644 --- a/tracer.go +++ b/tracer.go @@ -6,41 +6,41 @@ import ( "github.com/lainio/err2/internal/tracer" ) -// ErrorTracer returns current io.Writer for automatic error stack tracing. +// ErrorTracer returns current [io.Writer] for automatic error stack tracing. // The default value is nil. func ErrorTracer() io.Writer { return tracer.Error.Tracer() } -// PanicTracer returns current io.Writer for automatic panic stack tracing. Note -// that runtime.Error types which are transported by panics are controlled by -// this. The default value is os.Stderr. +// PanicTracer returns current [io.Writer] for automatic panic stack tracing. Note +// that [runtime.Error] types which are transported by panics are controlled by +// this. The default value is [os.Stderr]. func PanicTracer() io.Writer { return tracer.Panic.Tracer() } -// LogTracer returns a current io.Writer for the explicit try.Result.Logf -// function and automatic logging used in err2.Handle and err2.Catch. The +// LogTracer returns a current [io.Writer] for the explicit [try.Result.Logf] +// function and automatic logging used in [Handle] and [Catch]. The // default value is nil. func LogTracer() io.Writer { return tracer.Log.Tracer() } -// SetErrorTracer sets a io.Writer for automatic error stack tracing. The err2 +// SetErrorTracer sets a [io.Writer] for automatic error stack tracing. The err2 // default is nil. Note that the current function is capable to print error // stack trace when the function has at least one deferred error handler, e.g: // // func CopyFile(src, dst string) (err error) { // defer err2.Handle(&err) // <- error trace print decision is done here // -// Remember that you can reset these with Flag package support. See +// Remember that you can reset these with [flag] package support. See // documentation of err2 package's flag section. func SetErrorTracer(w io.Writer) { tracer.Error.SetTracer(w) } -// SetPanicTracer sets a io.Writer for automatic panic stack tracing. The err2 -// default is os.Stderr. Note that runtime.Error types which are transported by +// SetPanicTracer sets a [io.Writer] for automatic panic stack tracing. The err2 +// default is [os.Stderr]. Note that [runtime.Error] types which are transported by // panics are controlled by this. Note also that the current function is capable // to print panic stack trace when the function has at least one deferred error // handler, e.g: @@ -48,33 +48,33 @@ func SetErrorTracer(w io.Writer) { // func CopyFile(src, dst string) (err error) { // defer err2.Handle(&err) // <- panic trace print decision is done here // -// Remember that you can reset these with Flag package support. See +// Remember that you can reset these with [flag] package support. See // documentation of err2 package's flag section. func SetPanicTracer(w io.Writer) { tracer.Panic.SetTracer(w) } -// SetLogTracer sets a current io.Writer for the explicit try.Result.Logf -// function and automatic logging used in err2.Handle and err2.Catch. The +// SetLogTracer sets a current [io.Writer] for the explicit [try.Result.Logf] +// function and automatic logging used in [Handle] and [Catch]. The // default is nil and then err2 uses std log package for logging. // -// You can use the std log package to redirect other logging packages like glog -// to automatically work with the err2 package. For the glog, add this line at +// You can use the std log package to redirect other logging packages like [glog] +// to automatically work with the err2 package. For the [glog], add this line at // the beginning of your app: // // glog.CopyStandardLogTo("INFO") // -// Remember that you can reset these with Flag package support. See +// Remember that you can reset these with [flag] package support. See // documentation of err2 package's flag section. func SetLogTracer(w io.Writer) { tracer.Log.SetTracer(w) } -// SetTracers a helper to set a io.Writer for error and panic stack tracing, the -// log tracer is set as well. More information see SetErrorTracer, -// SetPanicTracer, and SetLogTracer functions. +// SetTracers a helper to set a [io.Writer] for error and panic stack tracing, the +// log tracer is set as well. More information see [SetErrorTracer], +// [SetPanicTracer], and [SetLogTracer] functions. // -// Remember that you can reset these with Flag package support. See +// Remember that you can reset these with [flag] package support. See // documentation of err2 package's flag section. func SetTracers(w io.Writer) { tracer.Error.SetTracer(w) diff --git a/try/out.go b/try/out.go index aebbe4e..d73472c 100644 --- a/try/out.go +++ b/try/out.go @@ -8,23 +8,23 @@ import ( ) type ( - // ErrFn is function type for try.OutX handlers. + // ErrFn is function type for [try.Out] handlers. ErrFn = handler.ErrorFn - // Result is the base of our error handling language for try.Out functions. + // Result is the base of our error handling language for [try.Out] functions. Result struct { // Err holds the error value returned from try.Out function result. Err error } - // Result1 is the base of our error handling DSL for try.Out1 functions. + // Result1 is the base of our error handling DSL for [try.Out1] functions. Result1[T any] struct { // Val1 holds the first value returned from try.Out1 function result. Val1 T Result } - // Result2 is the base of our error handling DSL for try.Out2 functions. + // Result2 is the base of our error handling DSL for [try.Out2] functions. Result2[T any, U any] struct { // Val2 holds the first value returned from try.Out2 function result. Val2 U @@ -32,8 +32,8 @@ type ( } ) -// Logf prints a log line to pre-set logging stream (err2.SetLogWriter) -// if the current Result.Err != nil. Logf follows Printf formatting logic. The +// Logf prints a log line to pre-set logging stream [err2.SetLogWriter] +// if the current [Result.Err] != nil. Logf follows Printf formatting logic. The // current error value will be added at the end of the logline with ": %v\n", // err. For example, the line: // @@ -46,8 +46,8 @@ func (o *Result) Logf(a ...any) *Result { return o.logf(logfFrameLvl, a) } -// Logf prints a log line to pre-set logging stream (err2.SetLogWriter) -// if the current Result.Err != nil. Logf follows Printf formatting logic. The +// Logf prints a log line to pre-set logging stream [err2.SetLogWriter] +// if the current [Result.Err] != nil. Logf follows Printf formatting logic. The // current error value will be added at the end of the logline with ": %v\n", // err. For example, the line: // @@ -61,8 +61,8 @@ func (o *Result1[T]) Logf(a ...any) *Result1[T] { return o } -// Logf prints a log line to pre-set logging stream (err2.SetLogWriter) -// if the current Result.Err != nil. Logf follows Printf formatting logic. The +// Logf prints a log line to pre-set logging stream [err2.SetLogWriter] +// if the current [Result.Err] != nil. Logf follows Printf formatting logic. The // current error value will be added at the end of the logline with ": %v\n", // err. For example, the line: // @@ -76,21 +76,21 @@ func (o *Result2[T, U]) Logf(a ...any) *Result2[T, U] { return o } -// Handle allows you to add an error handler to try.Out handler chain. Handle +// Handle allows you to add an error handler to [try.Out] handler chain. Handle // is a general purpose error handling function. It can handle several error // handling cases: // - if no argument is given and .Err != nil, it throws an error value immediately -// - if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called +// - if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) [ErrFn] is called // - if first argument is (string) and .Err != nil the error value is annotated and thrown -// - if first argument is (ErrFn) and .Err != nil, it calls ErrFn +// - if first argument is (ErrFn) and .Err != nil, it calls [ErrFn] // -// The handler function (ErrFn) can process and annotate the incoming error how +// The handler function [ErrFn] can process and annotate the incoming error how // it wants and returning error value decides if error is thrown. Handle // annotates and throws an error immediately i.e. terminates error handling DSL -// chain if Result.Err != nil. Handle supports error annotation similarly as -// fmt.Errorf. +// chain if [Result.Err] != nil. Handle supports error annotation similarly as +// [fmt.Errorf]. // -// For instance, to implement same as try.To(), you could do the following: +// For instance, to implement same as [try.To], you could do the following: // // d := try.Out(json.Unmarshal(b, &v)).Handle() func (o *Result) Handle(a ...any) *Result { @@ -124,21 +124,21 @@ func (o *Result) Handle(a ...any) *Result { return o } -// Handle allows you to add an error handler to try.Out handler chain. Handle +// Handle allows you to add an error handler to [try.Out] handler chain. Handle // is a general purpose error handling function. It can handle several error // handling cases: // - if no argument is given and .Err != nil, it throws an error value immediately -// - if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called +// - if two arguments (errTarget, [ErrFn]) and Is(.Err, errTarget) [ErrFn] is called // - if first argument is (string) and .Err != nil the error value is annotated and thrown -// - if first argument is (ErrFn) and .Err != nil, it calls ErrFn +// - if first argument is [ErrFn] and .Err != nil, it calls [ErrFn] // -// The handler function (ErrFn) can process and annotate the incoming error how +// The handler function [ErrFn] can process and annotate the incoming error how // it wants and returning error value decides if error is thrown. Handle // annotates and throws an error immediately i.e. terminates error handling DSL -// chain if Result.Err != nil. Handle supports error annotation similarly as -// fmt.Errorf. +// chain if [Result.Err] != nil. Handle supports error annotation similarly as +// [fmt.Errorf]. // -// For instance, to implement same as try.To(), you could do the following: +// For instance, to implement same as [try.To], you could do the following: // // d := try.Out(json.Unmarshal(b, &v)).Handle() func (o *Result1[T]) Handle(a ...any) *Result1[T] { @@ -146,21 +146,21 @@ func (o *Result1[T]) Handle(a ...any) *Result1[T] { return o } -// Handle allows you to add an error handler to try.Out handler chain. Handle +// Handle allows you to add an error handler to [try.Out] handler chain. Handle // is a general purpose error handling function. It can handle several error // handling cases: // - if no argument is given and .Err != nil, it throws an error value immediately -// - if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called +// - if two arguments (errTarget, [ErrFn]) and Is(.Err, errTarget) ErrFn is called // - if first argument is (string) and .Err != nil the error value is annotated and thrown -// - if first argument is (ErrFn) and .Err != nil, it calls ErrFn +// - if first argument is [ErrFn] and .Err != nil, it calls [ErrFn] // -// The handler function (ErrFn) can process and annotate the incoming error how +// The handler function [ErrFn] can process and annotate the incoming error how // it wants and returning error value decides if error is thrown. Handle // annotates and throws an error immediately i.e. terminates error handling DSL -// chain if Result.Err != nil. Handle supports error annotation similarly as -// fmt.Errorf. +// chain if [Result.Err] != nil. Handle supports error annotation similarly as +// [fmt.Errorf]. // -// For instance, to implement same as try.To(), you could do the following: +// For instance, to implement same as [try.To], you could do the following: // // d := try.Out(json.Unmarshal(b, &v)).Handle() func (o *Result2[T, U]) Handle(a ...any) *Result2[T, U] { @@ -168,8 +168,8 @@ func (o *Result2[T, U]) Handle(a ...any) *Result2[T, U] { return o } -// Catch catches the error and sets Result.Val1 if given. The value is used -// only in the case if Result.Err != nil. Catch returns the Val1 in all cases. +// Catch catches the error and sets [Result1.Val1] if given. The value is used +// only in the case if [Result1.Err] != nil. Catch returns the Val1 in all cases. func (o *Result1[T]) Catch(v ...T) T { if o.Err != nil && len(v) == 1 { o.Val1 = v[0] @@ -177,10 +177,10 @@ func (o *Result1[T]) Catch(v ...T) T { return o.Val1 } -// Catch catches the error and sets Result.Val1/Val2 if given. The value(s) is -// used in the case of Result.Err != nil. Catch returns the Val1 and Val2 in all -// cases. In case you want to set only Val2's default value, use Def2 before -// Catch call. +// Catch catches the error and sets [Result2.Val1] [Result2.Val2] if given. The +// value(s) is used in the case of [Result2.Err] != nil. Catch returns the [Val1] and +// [Val2] in all cases. In case you want to set only [Val2]'s default value, use +// [Def2] before Catch call. func (o *Result2[T, U]) Catch(a ...any) (T, U) { if o.Err != nil { switch len(a) { @@ -194,8 +194,8 @@ func (o *Result2[T, U]) Catch(a ...any) (T, U) { return o.Val1, o.Val2 } -// Def1 sets default value for Result.Val1. The value is returned in case of -// Result.Err != nil. +// Def1 sets default value for [Result.Val1.] The value is returned in case of +// [Result.Err] != nil. func (o *Result1[T]) Def1(v T) *Result1[T] { if o.Err == nil { return o @@ -204,8 +204,8 @@ func (o *Result1[T]) Def1(v T) *Result1[T] { return o } -// Def1 sets default value for Result.Val1. The value is returned in case of -// Result.Err != nil. +// Def1 sets default value for [Result.Val1]. The value is returned in case of +// [Result.Err] != nil. func (o *Result2[T, U]) Def1(v T) *Result2[T, U] { if o.Err == nil { return o @@ -214,8 +214,8 @@ func (o *Result2[T, U]) Def1(v T) *Result2[T, U] { return o } -// Def2 sets default value for Result.Val2. The value is returned in case of -// Result.Err != nil. +// Def2 sets default value for [Result.Val2]. The value is returned in case of +// [Result.Err] != nil. func (o *Result2[T, U]) Def2(v2 U) *Result2[T, U] { if o.Err == nil { return o @@ -225,7 +225,7 @@ func (o *Result2[T, U]) Def2(v2 U) *Result2[T, U] { } // Out is a helper function to call functions which returns (error) and start -// error handling with DSL. For instance, to implement same as try.To(), you +// error handling with DSL. For instance, to implement same as [try.To], you // could do the following: // // d := try.Out(json.Unmarshal(b, &v)).Handle() @@ -238,8 +238,8 @@ func Out(err error) *Result { } // Out1 is a helper function to call functions which returns (T, error). That -// allows you to use Result1, which makes possible to -// start error handling with DSL. For instance, instead of try.To1() you could +// allows you to use [Result1], which makes possible to +// start error handling with DSL. For instance, instead of [try.To1] you could // do the following: // // d := try.Out1(os.ReadFile(filename).Handle().Val1 @@ -253,8 +253,8 @@ func Out1[T any](v T, err error) *Result1[T] { } // Out2 is a helper function to call functions which returns (T, error). That -// allows you to use Result2, which makes possible to -// start error handling with DSL. For instance, instead of try.To2() you could +// allows you to use [Result2], which makes possible to +// start error handling with DSL. For instance, instead of [try.To2] you could // do the following: // // token := try.Out2(p.ParseUnverified(tokenStr, &customClaims{})).Handle().Val1 diff --git a/try/try.go b/try/try.go index 781d129..641853f 100644 --- a/try/try.go +++ b/try/try.go @@ -128,7 +128,7 @@ func To3[T, U, V any](v1 T, v2 U, v3 V, err error) (T, U, V) { } // Is function performs a filtered error check for the given argument. It's the -// same as To function, but it checks if the error matches the filter before +// same as [To] function, but it checks if the error matches the filter before // throwing an error. The false return value tells that there are no errors and // the true value that the error is the filter. func Is(err, filter error) bool { @@ -141,100 +141,101 @@ func Is(err, filter error) bool { return false } -// IsEOF1 function performs a filtered error check for the given argument. It's the -// same as To function, but it checks if the error matches the 'io.EOF' before -// throwing an error. The false return value tells that there are no errors and -// the true value that the error is the 'io.EOF'. +// IsEOF1 function performs a filtered error check for the given argument. It's +// the same as [To] function, but it checks if the error matches the [io.EOF] +// before throwing an error. The false return value tells that there are no +// errors and the true value that the error is the [io.EOF]. func IsEOF1[T any](v T, err error) (bool, T) { isFilter := Is(err, io.EOF) return isFilter, v } // IsEOF2 function performs a filtered error check for the given argument. It's the -// same as To function, but it checks if the error matches the 'io.EOF' before +// same as [To] function, but it checks if the error matches the [io.EOF] before // throwing an error. The false return value tells that there are no errors and -// the true value that the error is the 'io.EOF'. +// the true value that the error is the [io.EOF]. func IsEOF2[T, U any](v1 T, v2 U, err error) (bool, T, U) { isFilter := Is(err, io.EOF) return isFilter, v1, v2 } // IsEOF function performs a filtered error check for the given argument. It's the -// same as To function, but it checks if the error matches the 'io.EOF' before +// same as [To] function, but it checks if the error matches the [io.EOF] before // throwing an error. The false return value tells that there are no errors. -// The true tells that the err's chain includes 'io.EOF'. +// The true tells that the err's chain includes [io.EOF]. func IsEOF(err error) bool { return Is(err, io.EOF) } // IsNotFound function performs a filtered error check for the given argument. -// It's the same as To function, but it checks if the error matches the -// 'err2.NotFound' before throwing an error. The false return value tells that +// It's the same as [To] function, but it checks if the error matches the +// [err2.NotFound] before throwing an error. The false return value tells that // there are no errors. The true tells that the err's chain includes -// 'err2.NotFound'. +// [err2.NotFound]. func IsNotFound(err error) bool { return Is(err, err2.ErrNotFound) } // IsNotFound1 function performs a filtered error check for the given argument. -// It's the same as To function, but it checks if the error matches the -// 'err2.NotFound' before throwing an error. The false return value tells that +// It's the same as [To] function, but it checks if the error matches the +// [err2.NotFound] before throwing an error. The false return value tells that // there are no errors. The true tells that the err's chain includes -// 'err2.NotFound'. +// [err2.NotFound]. func IsNotFound1[T any](v T, err error) (bool, T) { isFilter := Is(err, err2.ErrNotFound) return isFilter, v } // IsNotExist function performs a filtered error check for the given argument. -// It's the same as To function, but it checks if the error matches the -// 'err2.NotExist' before throwing an error. The false return value tells that +// It's the same as [To] function, but it checks if the error matches the +// [err2.NotExist] before throwing an error. The false return value tells that // there are no errors. The true tells that the err's chain includes -// 'err2.NotExist'. +// [err2.NotExist]. func IsNotExist(err error) bool { return Is(err, err2.ErrNotExist) } // IsExist function performs a filtered error check for the given argument. It's -// the same as To function, but it checks if the error matches the 'err2.Exist' -// before throwing an error. The false return value tells that there are no -// errors. The true tells that the err's chain includes 'err2.Exist'. +// the same as [To] function, but it checks if the error matches the +// [err2.AlreadyExist] before throwing an error. The false return value tells +// that there are no errors. The true tells that the err's chain includes +// [err2.AlreadyExist]. func IsAlreadyExist(err error) bool { return Is(err, err2.ErrAlreadyExist) } // IsNotAccess function performs a filtered error check for the given argument. -// It's the same as To function, but it checks if the error matches the -// 'err2.NotAccess' before throwing an error. The false return value tells that +// It's the same as [To] function, but it checks if the error matches the +// [err2.NotAccess] before throwing an error. The false return value tells that // there are no errors. The true tells that the err's chain includes -// 'err2.NotAccess'. +// [err2.NotAccess]. func IsNotAccess(err error) bool { return Is(err, err2.ErrNotAccess) } // IsRecoverable function performs a filtered error check for the given -// argument. It's the same as To function, but it checks if the error matches -// the 'err2.ErrRecoverable' before throwing an error. The false return value +// argument. It's the same as [To] function, but it checks if the error matches +// the [err2.ErrRecoverable] before throwing an error. The false return value // tells that there are no errors. The true tells that the err's chain includes -// 'err2.ErrRecoverable'. +// [err2.ErrRecoverable]. func IsRecoverable(err error) bool { return Is(err, err2.ErrRecoverable) } // IsNotRecoverable function performs a filtered error check for the given -// argument. It's the same as To function, but it checks if the error matches -// the 'err2.ErrNotRecoverable' before throwing an error. The false return value +// argument. It's the same as [To] function, but it checks if the error matches +// the [err2.ErrNotRecoverable] before throwing an error. The false return value // tells that there are no errors. The true tells that the err's chain includes -// 'err2.ErrNotRecoverable'. +// [err2.ErrNotRecoverable]. func IsNotRecoverable(err error) bool { return Is(err, err2.ErrNotRecoverable) } // IsNotEnabled function performs a filtered error check for the given argument. -// It's the same as To function, but it checks if the error matches the -// 'err2.ErrNotEnabled' before throwing an error. The false return value tells +// It's the same as [To] function, but it checks if the error matches the +// [err2.ErrNotEnabled] before throwing an error. The false return value tells // that there are no errors. The true tells that the err's chain includes -// 'err2.ErrNotEnabled'. +// [err2.ErrNotEnabled]. func IsNotEnabled(err error) bool { return Is(err, err2.ErrNotEnabled) } From c086eb15ee05b2ef55195fe0e3d23c24b4a3d1c2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 11:20:31 +0200 Subject: [PATCH 69/88] real hyperlinks --- assert/assert.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index e803542..19a26f8 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -303,8 +303,10 @@ func Nil[T any](p *T, a ...any) { // (default Asserter) with the given message. // // Note, use this only for real interface types. Go's interface's has two values -// so this won't work e.g. slices! Read more information about interface type: -// https://go.dev/doc/faq#nil_error. +// so this won't work e.g. slices! +// Read more information about [the interface type]. +// +// [the interface type]: https://go.dev/doc/faq#nil_error func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" @@ -316,8 +318,10 @@ func INil(i any, a ...any) { // panics/errors (default Asserter) with the given message. // // Note, use this only for real interface types. Go's interface's has two values -// so this won't work e.g. slices! Read more information about interface type: -// https://go.dev/doc/faq#nil_error. +// so this won't work e.g. slices! +// Read more information about [the interface type]. +// +// [the interface type]: https://go.dev/doc/faq#nil_error func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" From 56741bc6063ca3177852ae39e474b8f43a7ba172 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 11:21:04 +0200 Subject: [PATCH 70/88] add missing links to docs --- err2.go | 2 +- formatter.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/err2.go b/err2.go index 3b43ca7..ef53250 100644 --- a/err2.go +++ b/err2.go @@ -157,7 +157,7 @@ func Handle(err *error, a ...any) { // // You can have unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one -// of them resets the error like Reset in the next sample: +// of them resets the error like [Reset] in the next sample: // // defer err2.Catch(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! // diff --git a/formatter.go b/formatter.go index f198f45..f6a663e 100644 --- a/formatter.go +++ b/formatter.go @@ -24,8 +24,8 @@ func SetFormatter(f formatter.Interface) { fmtstore.SetFormatter(f) } -// Returns the current formatter. See more information from SetFormatter and -// formatter package. +// Returns the current formatter. See more information from [SetFormatter] and +// [formatter] package. func Formatter() formatter.Interface { return fmtstore.Formatter() } From a1bb857335b180b42e053bb22f7728b366e6a03d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 11:21:33 +0200 Subject: [PATCH 71/88] CopyFile doc comments update, use long package names for sub-pkgs --- doc.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/doc.go b/doc.go index 7d260c3..6284a40 100644 --- a/doc.go +++ b/doc.go @@ -2,15 +2,9 @@ Package err2 provides three main functionality: 1. err2 package includes helper functions for error handling & automatic error stack tracing - 2. try package is for error checking - 3. assert package is for design-by-contract and preconditions both for normal - runtime and for testing - -The traditional error handling idiom in Go is roughly akin to - - if err != nil { return err } - -which applied recursively. + 2. [github.com/lainio/err2/try] sub-package is for error checking + 3. [github.com/lainio/err2/assert] sub-package is for design-by-contract and + preconditions both for normal runtime and for unit testing The err2 package drives programmers to focus on error handling rather than checking errors. We think that checks should be so easy that we never forget @@ -19,7 +13,7 @@ them. The CopyFile example shows how it works: // CopyFile copies source file to the given destination. If any error occurs it // returns error value describing the reason. func CopyFile(src, dst string) (err error) { - // Add first error handler just to annotate the error properly. + // Add first error handler is to catch and annotate the error properly. defer err2.Handle(&err) // Try to open the file. If error occurs now, err will be @@ -32,16 +26,18 @@ them. The CopyFile example shows how it works: // Try to create a file. If error occurs now, err will be annotated and // returned properly. w := try.To1(os.Create(dst)) - // Add error handler to clean up the destination file. Place it here that - // the next deferred close is called before our Remove call. + // Add error handler to clean up the destination file in case of + // error. Handler fn is called only if there has been an error at the + // following try.To check. We place it here that the next deferred + // close is called before our Remove a file call. defer err2.Handle(&err, err2.Err(func(error) { os.Remove(dst) })) defer w.Close() // Try to copy the file. If error occurs now, all previous error handlers - // will be called in the reversed order. And final return error is - // properly annotated in all the cases. + // will be called in the reversed order. And a final error value is + // properly annotated and returned in all the cases. try.To1(io.Copy(w, r)) // All OK, just return nil. @@ -62,8 +58,9 @@ we can write b := try.To1(io.ReadAll(r)) -Note that [try.To] functions are as fast as if err != nil statements. Please see -the try package documentation for more information about the error checks. +Note that try.To functions are as fast as if err != nil statements. Please see +the [github.com/lainio/err2/try] package documentation for more information +about the error checks. # Automatic Stack Tracing From b2d4af04fa72e9c7b3349525860b43cc53a36b8d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 15:43:05 +0200 Subject: [PATCH 72/88] export because of the doc ref link --- samples/main-play.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/main-play.go b/samples/main-play.go index 3f6e39d..035ee3e 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -3,7 +3,7 @@ // favorite editor and start to play with the main.go file. The comments on it // guide you. // -// We have only a few examples built over the CopyFile and callRecur functions, +// We have only a few examples built over the [CopyFile] and [CallRecur] functions, // but with them you can try all the important APIs from err2, try, and assert. // Just follow the comments and try suggested things :-) package main @@ -90,7 +90,7 @@ func OrgCopyFile(src, dst string) (err error) { return nil } -func callRecur(d int) (err error) { +func CallRecur(d int) (err error) { defer err2.Handle(&err) return doRecur(d) @@ -168,7 +168,7 @@ func doMain() (err error) { // Next fn demonstrates how error and panic traces work, comment out all // above CopyFile calls to play with: - try.To(callRecur(1)) + try.To(CallRecur(1)) println("=== you cannot see this ===") return nil From 2c48c35d4cad1d32b95542d595e870b666ee2907 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 15:43:26 +0200 Subject: [PATCH 73/88] better test case to use elsewhere --- internal/str/str_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/str/str_test.go b/internal/str/str_test.go index 7e16b35..45d02b1 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -65,7 +65,7 @@ func TestDecamel(t *testing.T) { {"acronym", args{"ARMCamelString"}, "armcamel string"}, {"acronym at end", args{"archIsARM"}, "arch is arm"}, {"simple method", args{"(*DIDAgent).AssertWallet"}, "didagent assert wallet"}, - {"package name and simple method", args{"ssi.(*DIDAgent).AssertWallet"}, "ssi: didagent assert wallet"}, + {"package name and simple method", args{"ssi.(*DIDAgent).CreateWallet"}, "ssi: didagent create wallet"}, {"simple method and anonym", args{"(*DIDAgent).AssertWallet.Func1"}, "didagent assert wallet: func1"}, {"complex method and anonym", args{"(**DIDAgent).AssertWallet.Func1"}, "didagent assert wallet: func1"}, {"unnatural method and anonym", args{"(**DIDAgent)...AssertWallet...Func1"}, "didagent assert wallet: func1"}, From c03ed81d7c1a17ab925f357c4813b366bb894113 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 15:46:39 +0200 Subject: [PATCH 74/88] sample code has wrong function --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e92cf8..f0f8541 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ func CopyFile(src, dst string) (err error) { return fmt.Errorf("mixing traditional error checking: %w", err) } defer err2.Handle(&err, err2.Err(func(error) { - try.Out1(os.Remove(dst)).Logf("cleaning error") + try.Out(os.Remove(dst)).Logf("cleaning error") })) defer w.Close() try.To1(io.Copy(w, r)) From 7a1a1ed7dcda4829f64dc5e61454591fe83bd2c5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 15:59:39 +0200 Subject: [PATCH 75/88] missing helper comment in func Log doc --- handlers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/handlers.go b/handlers.go index 864cbdf..9362344 100644 --- a/handlers.go +++ b/handlers.go @@ -73,7 +73,8 @@ func Err(f func(err error)) Handler { const lvl = 10 -// Log prints error string to the current log that is set by [SetLogTracer]. +// Log is a built-in helper to use with [Handle] and [Catch]. Log prints error +// string to the current log that is set by [SetLogTracer]. func Log(err error) error { if err == nil { return nil From bfd8dc5cc96c349e0535a0744ed9164faa23c9b4 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 16:00:12 +0200 Subject: [PATCH 76/88] continuing go doc format fixes, we are almost there --- assert/assert.go | 161 ++++++++++++++++++++++++++++++++++------- assert/doc.go | 12 +-- doc.go | 17 ++++- err2.go | 18 ++--- formatter.go | 8 +- formatter/formatter.go | 22 +++--- 6 files changed, 182 insertions(+), 56 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 19a26f8..5090f52 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -48,7 +48,7 @@ type defInd = uint32 // -------------------------------- // // [Test] minimalistic asserter for unit test use. More pragmatic is the -// TestFull asserter (test default). +// [TestFull] asserter (test default). // // Use this asserter if your IDE/editor doesn't support full file names and it // relies a relative path (Go standard). You can use this also if you need @@ -56,6 +56,7 @@ type defInd = uint32 // // [TestFull] asserter (test default). The TestFull asserter includes the caller // info and the call stack for unit testing, similarly like err2's error traces. +// Note, that [PushTester] set's TestFull if it's not yet set. // // The call stack produced by the test asserts can be used over Go module // boundaries. For example, if your app and it's sub packages both use @@ -65,9 +66,9 @@ type defInd = uint32 // // Note, that the cross-module assertions produce long file names (path // included), and some of the Go test result parsers cannot handle that. A -// proper test result parser like 'github.com/lainio/nvim-go' (fork) works very -// well. Also most of the make result parsers can process the output properly -// and allow traverse of locations of the error trace. +// proper test result parser like [nvim-go] (fork) works very well. Also most of +// the make result parsers can process the output properly and allow traverse of +// locations of the error trace. // // [Debug] asserter transforms assertion violations to panic calls where panic // object's type is string, i.e., err2 package treats it as a normal panic, not @@ -82,6 +83,8 @@ type defInd = uint32 // is equal to: // // assert.NotNil(p) +// +// [nvim-go]: https://github.com/lainio/nvim-go const ( Plain defInd = 0 + iota Production @@ -257,6 +260,9 @@ func tester() (t testing.TB) { } // NotImplemented always panics with 'not implemented' assertion message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func NotImplemented(a ...any) { current().reportAssertionFault("not implemented", a) } @@ -264,6 +270,9 @@ func NotImplemented(a ...any) { // ThatNot asserts that the term is NOT true. If is it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func ThatNot(term bool, a ...any) { if term { defMsg := assertionMsg @@ -274,6 +283,9 @@ func ThatNot(term bool, a ...any) { // That asserts that the term is true. If not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func That(term bool, a ...any) { if !term { defMsg := assertionMsg @@ -282,7 +294,10 @@ func That(term bool, a ...any) { } // NotNil asserts that the pointer IS NOT nil. If it is it panics/errors (default -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { defMsg := assertionMsg + ": pointer shouldn't be nil" @@ -291,7 +306,10 @@ func NotNil[P ~*T, T any](p P, a ...any) { } // Nil asserts that the pointer IS nil. If it is not it panics/errors (default -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func Nil[T any](p *T, a ...any) { if p != nil { defMsg := assertionMsg + ": pointer should be nil" @@ -300,7 +318,10 @@ func Nil[T any](p *T, a ...any) { } // INil asserts that the interface value IS nil. If it is it panics/errors -// (default Asserter) with the given message. +// (default Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. // // Note, use this only for real interface types. Go's interface's has two values // so this won't work e.g. slices! @@ -315,7 +336,10 @@ func INil(i any, a ...any) { } // INotNil asserts that the interface value is NOT nil. If it is it -// panics/errors (default Asserter) with the given message. +// panics/errors (default Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. // // Note, use this only for real interface types. Go's interface's has two values // so this won't work e.g. slices! @@ -330,7 +354,10 @@ func INotNil(i any, a ...any) { } // SNil asserts that the slice IS nil. If it is it panics/errors (default -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { defMsg := assertionMsg + ": slice should be nil" @@ -339,7 +366,10 @@ func SNil[S ~[]T, T any](s S, a ...any) { } // SNotNil asserts that the slice is not nil. If it is it panics/errors (default -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { defMsg := assertionMsg + ": slice shouldn't be nil" @@ -348,7 +378,10 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { } // CNotNil asserts that the channel is not nil. If it is it panics/errors -// (default Asserter) with the given message. +// (default Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { defMsg := assertionMsg + ": channel shouldn't be nil" @@ -357,7 +390,10 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { } // MNotNil asserts that the map is not nil. If it is it panics/errors (default -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { defMsg := assertionMsg + ": map shouldn't be nil" @@ -368,6 +404,12 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // NotEqual asserts that the values aren't equal. If they are it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// +// Note, when asserter is [Plain], optional arguments are used to build a new +// assert violation message. func NotEqual[T comparable](val, want T, a ...any) { if want == val { defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) @@ -378,6 +420,9 @@ func NotEqual[T comparable](val, want T, a ...any) { // Equal asserts that the values are equal. If not it panics/errors (according // the current Asserter) with the auto-generated message. You can append the // generated got-want message by using optional message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func Equal[T comparable](val, want T, a ...any) { if want != val { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) @@ -389,6 +434,9 @@ func Equal[T comparable](val, want T, a ...any) { // panics/errors (according the current Asserter) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) @@ -401,6 +449,9 @@ func DeepEqual(val, want any, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note, it uses reflect.DeepEqual which means that also the types must be the // same: // @@ -417,6 +468,9 @@ func NotDeepEqual(val, want any, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Len(obj string, length int, a ...any) { @@ -433,6 +487,9 @@ func Len(obj string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Longer(s string, length int, a ...any) { @@ -449,6 +506,9 @@ func Longer(s string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func Shorter(str string, length int, a ...any) { @@ -465,6 +525,9 @@ func Shorter(str string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SLen[S ~[]T, T any](obj S, length int, a ...any) { @@ -481,6 +544,9 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SLonger[S ~[]T, T any](obj S, length int, a ...any) { @@ -497,6 +563,9 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SShorter[S ~[]T, T any](obj S, length int, a ...any) { @@ -513,6 +582,9 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { @@ -529,6 +601,9 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { @@ -545,6 +620,9 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { @@ -561,6 +639,9 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CLen[C ~chan T, T any](obj C, length int, a ...any) { @@ -577,6 +658,9 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CLonger[C ~chan T, T any](obj C, length int, a ...any) { @@ -593,6 +677,9 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func CShorter[C ~chan T, T any](obj C, length int, a ...any) { @@ -605,7 +692,10 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { } // MKeyExists asserts that the map key exists. If not it panics/errors (current -// Asserter) with the given message. +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) { var ok bool val, ok = obj[key] @@ -620,6 +710,9 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) // NotEmpty asserts that the string is not empty. If it is, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func NotEmpty(obj string, a ...any) { if obj == "" { defMsg := assertionMsg + ": string shouldn't be empty" @@ -630,6 +723,9 @@ func NotEmpty(obj string, a ...any) { // Empty asserts that the string is empty. If it is NOT, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func Empty(obj string, a ...any) { if obj != "" { defMsg := assertionMsg + ": string should be empty" @@ -641,6 +737,9 @@ func Empty(obj string, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func SNotEmpty[S ~[]T, T any](obj S, a ...any) { @@ -658,6 +757,9 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { // You can append the generated got-want message by using optional message // arguments. // +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { @@ -673,12 +775,12 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note. We recommend that you prefer [try.To]. They work exactly the same during -// the test runs and you can use the same code for both: runtime and tests. -// However, there are cases that you want assert that there is no error in cases -// where fast fail and immediate stop of execution is wanted at runtime. With -// asserts ([Production], [Development], [Debug]) you get the file location as -// well. +// Note. We recommend that you prefer [github.com/lainio/err2/try.To]. They work +// exactly the same during the test runs and you can use the same code for both: +// runtime and tests. However, there are cases that you want assert that there +// is no error in cases where fast fail and immediate stop of execution is +// wanted at runtime. With asserts ([Production], [Development], [Debug]) you +// get the file location as well. func NoError(err error, a ...any) { if err != nil { defMsg := assertionMsg + conCatErrStr + err.Error() @@ -686,9 +788,12 @@ func NoError(err error, a ...any) { } } -// Error asserts that the err is not nil. If it is it panics with the given -// formatting string. Thanks to inlining, the performance penalty is equal to a +// Error asserts that the err is not nil. If it is it panics and builds a +// violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func Error(err error, a ...any) { if err == nil { defMsg := "Error:" + assertionMsg + ": missing error" @@ -696,9 +801,12 @@ func Error(err error, a ...any) { } } -// Zero asserts that the value is 0. If it is not it panics with the given -// formatting string. Thanks to inlining, the performance penalty is equal to a +// Zero asserts that the value is 0. If it is not it panics and builds a +// violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func Zero[T Number](val T, a ...any) { if val != 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) @@ -706,9 +814,12 @@ func Zero[T Number](val T, a ...any) { } } -// NotZero asserts that the value != 0. If it is not it panics with the given -// formatting string. Thanks to inlining, the performance penalty is equal to a +// NotZero asserts that the value != 0. If it is not it panics and builds a +// violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. func NotZero[T Number](val T, a ...any) { if val == 0 { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) diff --git a/assert/doc.go b/assert/doc.go index 0782af5..78fda5a 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -2,7 +2,7 @@ Package assert includes runtime assertion helpers both for normal execution as well as a assertion package for Go's testing. What makes solution unique is its capable to support both modes with the same API. Only thing you need to do is to -add a PushTester line at the beginning of your unit tests: +add a [PushTester] line at the beginning of your unit tests: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test @@ -59,8 +59,8 @@ Please see the code examples for more information. # Flag Package Support -The assert package supports Go's flags. All you need to do is to call flag.Parse. -And the following flags are supported (="default-value"): +The assert package supports Go's flags. All you need to do is to call +[flag.Parse]. And the following flags are supported (="default-value"): -asserter="Prod" A name of the asserter Plain, Prod, Dev, Debug @@ -70,15 +70,15 @@ And assert package's configuration flags are inserted. # Performance -assert.That's performance is equal to the if-statement thanks for inlining. And +[assert.That]'s performance is equal to the if-statement thanks for inlining. And the most of the generics-based versions are about the equally fast. Practice has -thought that we should prefer other than assert.That because by using detailed +thought that we should prefer other than [assert.That] because by using detailed version like [assert.Shorter] we get precise error messages automatically. Some also prefer readability of specific asserters. If your algorithm is performance-critical please run `make bench` in the err2 repo and decide case by case. Also you can make an issue or even PR if you would -like to have something similar like glog.V() function. +like to have something similar like [glog.V] function. # Naming diff --git a/doc.go b/doc.go index 6284a40..12a9441 100644 --- a/doc.go +++ b/doc.go @@ -31,7 +31,7 @@ them. The CopyFile example shows how it works: // following try.To check. We place it here that the next deferred // close is called before our Remove a file call. defer err2.Handle(&err, err2.Err(func(error) { - os.Remove(dst) + try.Out(os.Remove(dst)).Logf("cleanup failed") })) defer w.Close() @@ -75,12 +75,23 @@ programmatically (before [flag.Parse] if you are using that): or err2.SetPanicTracer(log.Writer()) // panic stack trace to std logger -Note. Since [Catch]'s default mode is to catch panics, the panic tracer's -default values is os.Stderr. The default error tracer is nil. +Note. Since [Catch]'s default mode is to recover from panics, it's a good +practice still print their stack trace. The panic tracer's default values is +[os.Stderr]. The default error tracer is nil. err2.SetPanicTracer(os.Stderr) // panic stack tracer's default is stderr err2.SetErrorTracer(nil) // error stack tracer's default is nil +Note, that both panic and error traces are optimized by err2 package. That means +that the head of the stack trace isn't the panic function, but an actual line +that caused it. It works for all three categories: + - normal error values + - [runtime.Error] values + - any types of the panics + +The last two types are handled as panics in the error handling functions given +to [Handle] and [Catch]. + # Automatic Logging Same err2 capablities support automatic logging like the [Catch] and diff --git a/err2.go b/err2.go index ef53250..c2ff4c2 100644 --- a/err2.go +++ b/err2.go @@ -14,8 +14,8 @@ type ( Handler = handler.ErrorFn ) -// Sentinel error value helpers. They are convenient thanks to [try.IsNotFound] -// and similar functions. +// Sentinel error value helpers. They are convenient thanks to +// [github.com/lainio/err2/try.IsNotFound] and similar functions. // // [ErrNotFound] ... [ErrNotEnabled] are similar no-error like [io.EOF] for // those who really want to use error return values to transport non errors. @@ -38,7 +38,7 @@ var ( ) // Stdnull implements [io.Writer] that writes nothing, e.g., -// [SetLogTracer] in cases you don't want to use automatic log writer, +// [SetLogTracer] in cases you don't want to use automatic log writer (=nil), // i.e., [LogTracer] == /dev/null. It can be used to change how the [Catch] // works, e.g., in CLI apps. var Stdnull = &nullDev{} @@ -145,7 +145,7 @@ func Handle(err *error, a ...any) { // cleanups, etc. The error handler function has same signature as Handle's // error handling function [Handler]. By returning nil resets the // error, which allows e.g. prevent automatic error logs to happening. -// Otherwise, the output results depends on the current [Tracer] and assert +// Otherwise, the output results depends on the current trace and assert // settings. The default trace setting prints call stacks for panics but not for // errors: // @@ -155,15 +155,15 @@ func Handle(err *error, a ...any) { // // defer err2.Catch(err2.Noop) // -// You can have unlimited amount of error handlers. They are called if error +// You can give unlimited amount of error handlers. They are called if error // happens and they are called in the same order as they are given or until one // of them resets the error like [Reset] in the next sample: // // defer err2.Catch(err2.Noop, err2.Reset, err2.Log) // err2.Log not called! // -// The last one calls your error handler, and you have an explicit panic handler -// as well, where you can e.g. continue panicking to propagate it for above -// callers or stop it like below: +// The next sample calls your error handler, and you have an explicit panic +// handler as well, where you can e.g. continue panicking to propagate it for +// above callers or stop it like below: // // defer err2.Catch(func(err error) error { return err }, func(p any) {}) func Catch(a ...any) { @@ -183,7 +183,7 @@ func Catch(a ...any) { doTrace(err) } -// Throwf builds and throws (panics) an error. For creation it's similar to +// Throwf builds and throws an error (panic). For creation it's similar to // [fmt.Errorf]. Because panic is used to transport the error instead of error // return value, it's called only if you want to non-local control structure for // error handling, i.e. your current function doesn't have error return value. diff --git a/formatter.go b/formatter.go index f6a663e..994a2b5 100644 --- a/formatter.go +++ b/formatter.go @@ -10,8 +10,12 @@ func init() { } // SetFormatter sets the current formatter for the err2 package. The default -// formatter.Decamel tries to process function names to human readable and the -// idiomatic Go format, i.e. all lowercase, space delimiter, etc. +// [formatter.Decamel] processes function names to human readable and the +// idiomatic Go format, i.e. all lowercase, space delimiter, package names colon +// separated. The example how a quite complex method name gives a proper error +// message prefix: +// +// "ssi.(*DIDAgent).CreateWallet" -> "ssi: didagent create wallet" // // Following line sets a noop formatter where errors are taken as function names // are in the call stack. diff --git a/formatter/formatter.go b/formatter/formatter.go index 0946ea2..73ae476 100644 --- a/formatter/formatter.go +++ b/formatter/formatter.go @@ -24,18 +24,18 @@ type Formatter struct { DoFmt } -var ( - // Decamel is preimplemented and default formatter to produce human - // readable error strings from function names. - // func CopyFile(..) -> "copy file: file not exists" - // ^-------^ -> generated from CopyFile - Decamel = &Formatter{DoFmt: str.Decamel} +// Decamel is preimplemented and default formatter to produce human +// readable error strings from function names. +// +// func CopyFile(..) -> "copy file: file not exists" +// ^-------^ -> generated from 'func CopyFile' +var Decamel = &Formatter{DoFmt: str.Decamel} - // Noop is preimplemented formatter that does nothing to function name. - // func CopyFile(..) -> "CopyFile: file not exists" - // ^------^ -> function name as it is: CopyFile - Noop = &Formatter{DoFmt: func(i string) string { return i }} -) +// Noop is preimplemented formatter that does nothing to function name. +// +// func CopyFile(..) -> "CopyFile: file not exists" +// ^------^ -> function name as it is: CopyFile +var Noop = &Formatter{DoFmt: func(i string) string { return i }} // Format just calls function set in the DoFmt field. func (f *Formatter) Format(input string) string { From fd2d889694f9afcdc7cebe083787220ded31588e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 16:04:36 +0200 Subject: [PATCH 77/88] update 1.0.0 release comment --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f0f8541..a325295 100644 --- a/README.md +++ b/README.md @@ -530,12 +530,13 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release ##### 1.0.0 -- Finally! We are very happy, and thanks to all. -- Lots of documentation updates and cleanups for version 1 +- Finally! We are very happy, and thanks to all who have helped! +- Lots of documentation updates and cleanups for version 1.0.0 - `Catch/Handle` take unlimited amount error handler functions - allows building e.g. error handling middlewares + - this is major feature because it allows building helpers/add-ons - automatic outputs aren't overwritten by given args, only with `assert.Plain` -- Minor API fixes to simplify it: +- Minor API fixes to still simplify it: - remove exported vars, obsolete types and funcs from `assert` pkg - `Result2.Def2()` sets only Val2 - technical refactorings: variadic function calls only in API level From 9b5e41795b277bed424cb170d2e4b6b372adecd7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 17 Mar 2024 18:43:21 +0200 Subject: [PATCH 78/88] explain error return traces and add link to Zig --- tracer.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tracer.go b/tracer.go index 3c14e55..954768f 100644 --- a/tracer.go +++ b/tracer.go @@ -27,14 +27,23 @@ func LogTracer() io.Writer { } // SetErrorTracer sets a [io.Writer] for automatic error stack tracing. The err2 -// default is nil. Note that the current function is capable to print error -// stack trace when the function has at least one deferred error handler, e.g: +// default is nil. Note that any function that has deferred [Handle] or [Catch] +// is capable to print error stack trace: // // func CopyFile(src, dst string) (err error) { -// defer err2.Handle(&err) // <- error trace print decision is done here +// defer err2.Handle(&err) // <- makes error trace printing decision +// +// Error trace is almost the same format as Go's standard call stack but it may +// have multiple sections because every [Handle] and [Catch] prints it. If an +// error happens in a deep call stack, the error trace includes various parts. +// The principle is similar to [Zig Error Return Traces], where you see how +// error bubbles up. However, our error trace is a combination of error return +// traces and stack traces because we get all the needed information at once. // // Remember that you can reset these with [flag] package support. See // documentation of err2 package's flag section. +// +// [Zig Error Return Traces]: https://ziglang.org/documentation/master/#Error-Return-Traces func SetErrorTracer(w io.Writer) { tracer.Error.SetTracer(w) } From 08a533d58fe8166d9086639a7e4b54eae423e194 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 18 Mar 2024 09:46:28 +0200 Subject: [PATCH 79/88] Note that with out comma --- doc.go | 4 ++-- handlers.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc.go b/doc.go index 12a9441..af37f26 100644 --- a/doc.go +++ b/doc.go @@ -82,7 +82,7 @@ practice still print their stack trace. The panic tracer's default values is err2.SetPanicTracer(os.Stderr) // panic stack tracer's default is stderr err2.SetErrorTracer(nil) // error stack tracer's default is nil -Note, that both panic and error traces are optimized by err2 package. That means +Note that both panic and error traces are optimized by err2 package. That means that the head of the stack trace isn't the panic function, but an actual line that caused it. It works for all three categories: - normal error values @@ -112,7 +112,7 @@ And the following flags are supported (="default-value"): -err2-trace="nil" A name of the stream currently supported stderr, stdout or nil -Note, that you have called [SetErrorTracer] and others, before you call +Note that you have called [SetErrorTracer] and others, before you call [flag.Parse]. This allows you set the defaults according your app's need and allow end-user change them during the runtime. diff --git a/handlers.go b/handlers.go index 9362344..7f98a09 100644 --- a/handlers.go +++ b/handlers.go @@ -60,7 +60,7 @@ func Reset(error) error { return nil } // fmt.Println("ERROR:", err) // })) // -// Note, that since Err helper we have other helpers like [Stdout] that allows +// Note that since Err helper we have other helpers like [Stdout] that allows // previous block be written as simple as: // // defer err2.Catch(err2.Stdout) From a9218f2185d828a6ae75143f76c2f9ed0ac174ed Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 18 Mar 2024 09:46:49 +0200 Subject: [PATCH 80/88] Note that without comma, and updated docs --- assert/assert.go | 35 +++++++++++++++++++++-------------- err2.go | 6 +++--- internal/str/str.go | 6 +++--- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 5090f52..00e1b22 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -25,7 +25,7 @@ type defInd = uint32 // // assert.NotEmpty(c.PoolName, "pool name cannot be empty") // -// Note, that Plain is only asserter that override auto-generated assertion +// Note that Plain is only asserter that override auto-generated assertion // messages with given arguments like 'pool name cannot be empty'. Others add // given arguments at the end of the auto-generated assert message. // @@ -56,7 +56,7 @@ type defInd = uint32 // // [TestFull] asserter (test default). The TestFull asserter includes the caller // info and the call stack for unit testing, similarly like err2's error traces. -// Note, that [PushTester] set's TestFull if it's not yet set. +// Note that [PushTester] set's TestFull if it's not yet set. // // The call stack produced by the test asserts can be used over Go module // boundaries. For example, if your app and it's sub packages both use @@ -64,7 +64,7 @@ type defInd = uint32 // be automatically converted to test asserts. If any of the runtime asserts of // the sub packages fails during the app tests, the app test fails as well. // -// Note, that the cross-module assertions produce long file names (path +// Note that the cross-module assertions produce long file names (path // included), and some of the Go test result parsers cannot handle that. A // proper test result parser like [nvim-go] (fork) works very well. Also most of // the make result parsers can process the output properly and allow traverse of @@ -179,7 +179,7 @@ const ( // PushTester allows you to change the current default asserter by accepting it // as a second argument. // -// Note, that the second argument, if given, changes the default asserter for +// Note that the second argument, if given, changes the default asserter for // whole package. The argument is mainly for temporary development use and isn't // not preferred API usage. func PushTester(t testing.TB, a ...defInd) function { @@ -241,7 +241,7 @@ func PopTester() { msg = "test panic catch" } - // First, print the call stack. Note, that we aren't support full error + // First, print the call stack. Note that we aren't support full error // tracing with unit test logging. However, using it has proved the top // level error stack as more enough. Even so that we could consider using // it for normal error stack traces if it would be possible. @@ -249,7 +249,7 @@ func PopTester() { debug.PrintStackForTest(os.Stderr, stackLvl) // Now that call stack errors are printed, if any. Let's print the actual - // line that caused the error, i.e., was throwing the error. Note, that we + // line that caused the error, i.e., was throwing the error. Note that we // are here in the 'catch-function'. const framesToSkip = 4 // how many fn calls there is before FuncName call fatal("assertion catching: "+msg, framesToSkip) @@ -837,15 +837,16 @@ func current() asserter { return defAsserter[def] } -// SetDefault set the current default asserter for assert pkg. +// SetDefault sets the current default asserter for assert pkg. It also returns +// the previous asserter. // -// Note, that you should use this in TestMain function, and use Flag package to +// Note that you should use this in TestMain function, and use [flag] package to // set it for the app. For the tests you can set it to panic about every // assertion fault, or to throw an error, or/and print the call stack // immediately when assert occurs. The err2 package helps you to catch and // report all types of the asserts. // -// Note, that if you are using tracers you might get two call stacks, so test +// Note that if you are using tracers you might get two call stacks, so test // what's best for your case. // // Tip. If our own packages (client packages for assert) have lots of parallel @@ -854,15 +855,21 @@ func current() asserter { // // func TestMain(m *testing.M) { // SetDefault(assert.TestFull) -func SetDefault(i defInd) defInd { - // pkg lvl lock to allow only one pkg client call this at the time +func SetDefault(i defInd) (old defInd) { + // pkg lvl lock to allow only one pkg client call this at one of the time + // together with the indexing, i.e we don't need to switch asserter + // variable or pointer to it but just index to array they are stored. + // All of this make race detector happy at the client pkgs. mu.Lock() defer mu.Unlock() - // to make this fully thread safe the def var should be atomic, BUT it - // would be owerkill. We need only defaults to be set at once. + old = def + // theoretically, to make this fully thread safe the def var should be + // atomic, BUT it would be overkill. We need only defaults to be set at + // once. AND because we use indexing to actual asserter the thread-safety + // and performance are guaranteed, def = i - return def + return } // mapDefInd runtime asserters, that's why test asserts are removed for now. diff --git a/err2.go b/err2.go index c2ff4c2..c41c9f9 100644 --- a/err2.go +++ b/err2.go @@ -114,11 +114,11 @@ func Handle(err *error, a ...any) { } // Catch is a convenient helper to those functions that doesn't return errors. -// Note, that Catch always catch the panics. If you don't want to stop them +// Note that Catch always catch the panics. If you don't want to stop them // (i.e., use of [recover]) you should add panic handler and continue panicking // there. There can be only one deferred Catch function per non error returning -// function like main(). There is several ways to use the Catch function. And -// always remember the [defer]. +// functions, i.e. goroutine functions like main(). There is several ways to use +// the Catch function. And always remember the [defer]. // // The deferred Catch is very convenient, because it makes your current // goroutine panic and error-safe. You can fine tune its 'global' behavior with diff --git a/internal/str/str.go b/internal/str/str.go index 183b904..c9d9d13 100644 --- a/internal/str/str.go +++ b/internal/str/str.go @@ -15,8 +15,8 @@ var ( clean = regexp.MustCompile(`[^\w]`) ) -// DecamelRegexp return the given string as space delimeted. Note! it's slow. Use -// Decamel instead. +// DecamelRegexp return the given string as space delimeted. Note! It's slow. +// Use [Decamel] instead. It's left here for learning purposes. func DecamelRegexp(str string) string { str = clean.ReplaceAllString(str, " ") str = uncamel.ReplaceAllString(str, ` $1`) @@ -78,7 +78,7 @@ func Decamel(s string) string { // separated filename, and line number. If frame cannot be found ok is false. // // See more information from runtime.Caller. The skip tells how many stack -// frames are skipped. Note, that FuncName calculates itself to skip frames. +// frames are skipped. Note that FuncName calculates itself the skip frames. func FuncName(skip int, long bool) (n, fname string, ln int, ok bool) { pc, file, ln, yes := runtime.Caller(skip + 1) // +1 skip ourself if yes { From bcca59e5a9679d5a7c2ab4a920b2b725093e0cf0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 14:12:45 +0200 Subject: [PATCH 81/88] assert pkg docs: sub-goroutines in unit tests, better English --- assert/doc.go | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 78fda5a..210ed4d 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -7,35 +7,39 @@ add a [PushTester] line at the beginning of your unit tests: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test - // v-----v Invite's control flow includes assertions + // v-----v Invite's control flow includes assertions alice.Node = root1.Invite(alice.Node, root1.Key, alice.PubKey, 1) assert.Equal(alice.Len(), 1) // assert anything normally + ... + go func() { + defer assert.PushTester(t)() // <-- Needs to do again for new goroutine # Merge Runtime And Unit Test Assertions -If some assertion violation happens in the deep call stack, they are still -reported as a test failure. See the above example. If assertion failure happens -somewhere inside the Invite() functions call stack, it's still reported -correctly as a test failure of the TestInvite. It doesn't matter how deep the -recursion is, or if parallel test runs are performed. The failure report -includes all the locations of the meaningful call stack steps. See the chapter -Call Stack Traveral During Tests. - -This is the actual Invite function implementation's first two lines. Even if the -assertion line is written more for runtime detection and active comment, it -catches all unit test errors as well: +The next block is the actual Invite function's first two lines. Even if the +assertion line is written more from a runtime detection point of view, it catches +all assert violations in the unit tests as well: func (c Chain) Invite(...) { assert.That(c.isLeaf(invitersKey), "only leaf can invite") +If some assertion violation occurs in the deep call stack, they are still +reported as a test failure. See the above code blocks. If assertion failure +happens somewhere inside the Invite function's call stack, it's still reported +correctly as a test failure of the TestInvite unit test. It doesn't matter how +deep the recursion is or if parallel test runs are performed. The failure report +includes all the locations of the meaningful call stack steps. See the next +chapter. + # Call Stack Traversal During Tests -Assert package allows us track assertion violations over package and even module -boundaries. When an assertion fails during the unit testing, the whole call -stack is brought to unit test logs. And some help with your IDE, that output can -be tranferred to a location list, for examplem in Neovim/Vim. +The Assert package allows us to track assertion violations over the package and +even module boundaries. When an assertion fails during the unit testing, the +whole call stack is brought to unit test logs. And some help with your IDE, such +as transferring output to a location list, for example, in Neovim/Vim. For +example, you can find a proper test result parser like [nvim-go] (fork) -With large multi repo environment this has proven to be valuable. +With a sizeable multi-repo environment, this has proven to be valuable. # Why Runtime Asserts Are So Important? @@ -85,5 +89,7 @@ like to have something similar like [glog.V] function. Because performance has been number one requirement and Go's generics cannot discrete slices, maps and channels we have used naming prefixes accordingly: S = slice, M = map, C = channel. No prefix is (currently) for the string type. + +[nvim-go]: https://github.com/lainio/nvim-go */ package assert From b189b5bd06f746d576d9d26b65b83f0e4a071020 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 14:51:01 +0200 Subject: [PATCH 82/88] Push and Pop works for sub-goroutines + docs --- assert/assert.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 00e1b22..d906a8d 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -179,6 +179,14 @@ const ( // PushTester allows you to change the current default asserter by accepting it // as a second argument. // +// Note that you MUST call PushTester for sub-goroutines: +// +// defer assert.PushTester(t)() // does the cleanup +// ... +// go func() { +// assert.PushTester(t) // left cleanup out! Leave it for the test, see ^ +// ... +// // Note that the second argument, if given, changes the default asserter for // whole package. The argument is mainly for temporary development use and isn't // not preferred API usage. @@ -190,13 +198,7 @@ func PushTester(t testing.TB, a ...defInd) function { // it's good to keep the API as simple as possible SetDefault(TestFull) } - testers.Tx(func(m testersMap) { - rid := goid() - if _, ok := m[rid]; ok { - panic("PushTester is already called") - } - m[rid] = t - }) + testers.Set(goid(), t) return PopTester } @@ -219,10 +221,7 @@ func PushTester(t testing.TB, a ...defInd) function { // // defer assert.PushTester(t)() func PopTester() { - defer testers.Tx(func(m testersMap) { - goid := goid() - delete(m, goid) - }) + defer testers.Del(goid()) r := recover() if r == nil { From c2f1416b310cb42102332a7fe0213d4ae1ba9e65 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 15:00:36 +0200 Subject: [PATCH 83/88] code block intention --- err2.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/err2.go b/err2.go index c41c9f9..ece30df 100644 --- a/err2.go +++ b/err2.go @@ -72,10 +72,10 @@ var Stdnull = &nullDev{} // a second argument: // // defer err2.Handle(&err, func(err error) error { -// if rmErr := os.Remove(dst); rmErr != nil { -// return fmt.Errorf("%w: cleanup error: %w", err, rmErr) -// } -// return err +// if rmErr := os.Remove(dst); rmErr != nil { +// return fmt.Errorf("%w: cleanup error: %w", err, rmErr) +// } +// return err // }) // // You can have unlimited amount of error handlers. They are called if error From 8073fd2ffc1338c31e68d8bf3a14c38a7b2149cc Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 15:02:44 +0200 Subject: [PATCH 84/88] package lvl PushTester info for goroutines --- assert/doc.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 210ed4d..614c9dc 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -2,7 +2,8 @@ Package assert includes runtime assertion helpers both for normal execution as well as a assertion package for Go's testing. What makes solution unique is its capable to support both modes with the same API. Only thing you need to do is to -add a [PushTester] line at the beginning of your unit tests: +add a [PushTester] line at the beginning of your unit tests and its +sub-gouroutines: func TestInvite(t *testing.T) { defer assert.PushTester(t)() // push testing variable t beginning of any test @@ -12,7 +13,7 @@ add a [PushTester] line at the beginning of your unit tests: assert.Equal(alice.Len(), 1) // assert anything normally ... go func() { - defer assert.PushTester(t)() // <-- Needs to do again for new goroutine + assert.PushTester(t) // <-- Needs to do again for a new goroutine # Merge Runtime And Unit Test Assertions From 07c4a764208234920c51ca6a293bac7c0f1388b6 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 18:58:20 +0200 Subject: [PATCH 85/88] clean spaces --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f6dd9..662568b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ handlers ##### 0.9.40 -- Significant performance boost for: `defer err2.Handle/Catch()` +- Significant performance boost for: `defer err2.Handle/Catch()` - **3x faster happy path than the previous version, which is now equal to simplest `defer` function in the `err`-returning function** . (Please see the `defer` benchmarks in the `err2_test.go` and run `make bench_reca`) @@ -73,7 +73,7 @@ especially performance ##### 0.9.0 -- **Clean and simple API** +- **Clean and simple API** - Removing deprecated functions: - Only `err2.Handle` for error returning functions - Only `err2.Catch` for function that doesn't return error From 15dcc6101785e2377d2801ccdda8a427c88a541a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 19 Mar 2024 18:59:28 +0200 Subject: [PATCH 86/88] keep sample short, clean spaces --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a325295..03ab5de 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,19 @@ and propagation** like other modern programming languages: **Zig**, Rust, Swift, etc. `err2` isn't an exception handling library, but an entirely orthogonal package with Go's existing error handling mechanism. -```go +```go func CopyFile(src, dst string) (err error) { defer err2.Handle(&err) - assert.NotEmpty(src) - assert.NotEmpty(dst) - r := try.To1(os.Open(src)) defer r.Close() - w, err := os.Create(dst) - if err != nil { - return fmt.Errorf("mixing traditional error checking: %w", err) - } + w := try.To1(os.Create(dst)) defer err2.Handle(&err, err2.Err(func(error) { try.Out(os.Remove(dst)).Logf("cleaning error") })) defer w.Close() + try.To1(io.Copy(w, r)) return nil } @@ -88,7 +83,7 @@ little error handling. But most importantly, it doesn't help developers with > resilience. -- Gregor Hohpe Automatic error propagation is crucial because it makes your code change -tolerant. And, of course, it helps to make your code error-safe: +tolerant. And, of course, it helps to make your code error-safe: ![Never send a human to do a machine's job](https://www.magicalquote.com/wp-content/uploads/2013/10/Never-send-a-human-to-do-a-machines-job.jpg) @@ -99,7 +94,7 @@ The err2 package is your automation buddy: line exactly similar as [Zig's `errdefer`](https://ziglang.org/documentation/master/#errdefer). 2. It helps to check and transport errors to the nearest (the defer-stack) error - handler. + handler. 3. It helps us use design-by-contract type preconditions. 4. It offers automatic stack tracing for every error, runtime error, or panic. If you are familiar with Zig, the `err2` error traces are same as Zig's. @@ -124,7 +119,7 @@ This is the simplest form of `err2` automatic error handler: ```go func doSomething() (err error) { // below: if err != nil { return ftm.Errorf("%s: %w", CUR_FUNC_NAME, err) } - defer err2.Handle(&err) + defer err2.Handle(&err) ``` See more information from `err2.Handle`'s documentation. It supports several @@ -250,7 +245,7 @@ notExist := try.Is(r2.err, plugin.ErrNotExist) happens: nearest `err2.Handle` gets it first. These `try.Is` functions help cleanup mesh idiomatic Go, i.e. mixing happy and -error path, leads to. +error path, leads to. For more information see the examples in the documentation of both functions. @@ -330,7 +325,7 @@ func TestWebOfTrustInfo(t *testing.T) { assert.SLen(common, 2) wot := dave.WebOfTrustInfo(eve.Node) //<- this includes asserts as well!! - // And if there's violations during the test run they are reported as + // And if there's violations during the test run they are reported as // test failures for this TestWebOfTrustInfo -test. assert.Equal(0, wot.CommonInvider) @@ -366,7 +361,7 @@ stack.** When you are using `err2` or `assert` packages, i.e., just importing them, you have an option to automatically support for err2 configuration flags through Go's standard `flag` package. See more information about err2 settings from -[Error Stack Tracing](#error-stack-tracing) and [Asserters](#asserters). +[Error Stack Tracing](#error-stack-tracing) and [Asserters](#asserters). Now you can always deploy your applications and services with the simple end-user friendly error messages and no stack traces, **but you can switch them @@ -423,10 +418,10 @@ support packages like `err2` and `glog` and their flags. ```go PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { defer err2.Handle(&err) - + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 goflag.Parse() - + try.To(goflag.Set("logtostderr", "true")) handleViperFlags(cmd) // local helper with envs glog.CopyStandardLogTo("ERROR") // for err2 @@ -472,7 +467,7 @@ part of the algorithm itself.** **there are no performance penalty at all**. However, the mandatory use of the `defer` might prevent some code optimisations like function inlining. And still, we have cases where using the `err2` and `try` package simplify the algorithm so -that it's faster than the return value if err != nil version. (**See the +that it's faster than the return value if err != nil version. (**See the benchmarks for `io.Copy` in the repo.**) If you have a performance-critical use case, we always recommend you to write @@ -536,7 +531,7 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). - allows building e.g. error handling middlewares - this is major feature because it allows building helpers/add-ons - automatic outputs aren't overwritten by given args, only with `assert.Plain` -- Minor API fixes to still simplify it: +- Minor API fixes to still simplify it: - remove exported vars, obsolete types and funcs from `assert` pkg - `Result2.Def2()` sets only Val2 - technical refactorings: variadic function calls only in API level From a8d6e307d8e3df338391957c42de71b61d9950dc Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 20 Mar 2024 17:13:53 +0200 Subject: [PATCH 87/88] fix some code block intentions --- assert/assert.go | 40 ++++++++++++++++++++-------------------- assert/doc.go | 2 +- err2.go | 6 +++--- handlers.go | 10 +++++----- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index d906a8d..28a1600 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -159,17 +159,17 @@ const ( // be called at the beginning of every test. There is two way of doing it: // // for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { // Shorter way, litle magic -// defer assert.PushTester(t)() // <- IMPORTANT! NOTE! (t)() -// ... -// assert.That(something, "test won't work") -// }) -// t.Run(tt.name, func(t *testing.T) { // Longer, explicit way, 2 lines -// assert.PushTester(t) // <- IMPORTANT! -// defer assert.PopTester() -// ... -// assert.That(something, "test won't work") -// }) +// t.Run(tt.name, func(t *testing.T) { // Shorter way, litle magic +// defer assert.PushTester(t)() // <- IMPORTANT! NOTE! (t)() +// ... +// assert.That(something, "test won't work") +// }) +// t.Run(tt.name, func(t *testing.T) { // Longer, explicit way, 2 lines +// assert.PushTester(t) // <- IMPORTANT! +// defer assert.PopTester() +// ... +// assert.That(something, "test won't work") +// }) // } // // Because PushTester returns [PopTester] it allows us to merge these two calls @@ -184,8 +184,8 @@ const ( // defer assert.PushTester(t)() // does the cleanup // ... // go func() { -// assert.PushTester(t) // left cleanup out! Leave it for the test, see ^ -// ... +// assert.PushTester(t) // left cleanup out! Leave it for the test, see ^ +// ... // // Note that the second argument, if given, changes the default asserter for // whole package. The argument is mainly for temporary development use and isn't @@ -209,12 +209,12 @@ func PushTester(t testing.TB, a ...defInd) function { // You have two ways to call [PopTester]. With defer right after [PushTester]: // // for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// assert.PushTester(t) // <- important! -// defer assert.PopTester() // <- for good girls and not so bad boys -// ... -// assert.That(something, "test won't work") -// }) +// t.Run(tt.name, func(t *testing.T) { +// assert.PushTester(t) // <- important! +// defer assert.PopTester() // <- for good girls and not so bad boys +// ... +// assert.That(something, "test won't work") +// }) // } // // If you want, you can combine [PushTester] and PopTester to one-liner: @@ -853,7 +853,7 @@ func current() asserter { // and set asserter only one in TestMain, or in init. // // func TestMain(m *testing.M) { -// SetDefault(assert.TestFull) +// SetDefault(assert.TestFull) func SetDefault(i defInd) (old defInd) { // pkg lvl lock to allow only one pkg client call this at one of the time // together with the indexing, i.e we don't need to switch asserter diff --git a/assert/doc.go b/assert/doc.go index 614c9dc..d6a246e 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -22,7 +22,7 @@ assertion line is written more from a runtime detection point of view, it catche all assert violations in the unit tests as well: func (c Chain) Invite(...) { - assert.That(c.isLeaf(invitersKey), "only leaf can invite") + assert.That(c.isLeaf(invitersKey), "only leaf can invite") If some assertion violation occurs in the deep call stack, they are still reported as a test failure. See the above code blocks. If assertion failure diff --git a/err2.go b/err2.go index ece30df..f26f6da 100644 --- a/err2.go +++ b/err2.go @@ -91,9 +91,9 @@ var Stdnull = &nullDev{} // a panic handler. See the second handler below: // // defer err2.Handle(&err, -// err2.Err( func(error) { os.Remove(dst) }), // err2.Err() keeps it short -// // below handler catches panics, but you can re-throw if needed -// func(p any) {} +// err2.Err( func(error) { os.Remove(dst) }), // err2.Err() keeps it short +// // below handler catches panics, but you can re-throw if needed +// func(p any) {} // ) func Handle(err *error, a ...any) { // This and others are similar but we need to call `recover` here because diff --git a/handlers.go b/handlers.go index 7f98a09..af3e9c6 100644 --- a/handlers.go +++ b/handlers.go @@ -14,7 +14,7 @@ import ( // You can use it like this: // // func main() { -// defer err2.Catch(err2.Stderr) +// defer err2.Catch(err2.Stderr) func Stderr(err error) error { if err == nil { return nil @@ -30,7 +30,7 @@ func Stderr(err error) error { // You can use it like this: // // func main() { -// defer err2.Catch(err2.Stdout) +// defer err2.Catch(err2.Stdout) func Stdout(err error) error { if err == nil { return nil @@ -57,7 +57,7 @@ func Reset(error) error { return nil } // and don't want to use [SetLogTracer] and keep it to write to your logs. // // defer err2.Catch(err2.Err(func(err error) { -// fmt.Println("ERROR:", err) +// fmt.Println("ERROR:", err) // })) // // Note that since Err helper we have other helpers like [Stdout] that allows @@ -89,7 +89,7 @@ func Log(err error) error { // You can use it like this: // // func myFunction() { -// defer err2.Handle(err2.Noop, err2.StderrNoReset) +// defer err2.Handle(err2.Noop, err2.StderrNoReset) func StderrNoReset(err error) error { if err == nil { return nil @@ -104,7 +104,7 @@ func StderrNoReset(err error) error { // You can use it like this: // // func main() { -// defer err2.Catch(err2.StdoutNoReset) +// defer err2.Catch(err2.StdoutNoReset) func StdoutNoReset(err error) error { if err == nil { return nil From ed98df4c825e12ddbc2a5a3da656c193429920e9 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 20 Mar 2024 17:26:03 +0200 Subject: [PATCH 88/88] finalize release 1.0.0 message --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03ab5de..9473df2 100644 --- a/README.md +++ b/README.md @@ -525,7 +525,7 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release ##### 1.0.0 -- Finally! We are very happy, and thanks to all who have helped! +- **Finally! We are very happy, and thanks to all who have helped!** - Lots of documentation updates and cleanups for version 1.0.0 - `Catch/Handle` take unlimited amount error handler functions - allows building e.g. error handling middlewares @@ -533,5 +533,5 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). - automatic outputs aren't overwritten by given args, only with `assert.Plain` - Minor API fixes to still simplify it: - remove exported vars, obsolete types and funcs from `assert` pkg - - `Result2.Def2()` sets only Val2 + - `Result2.Def2()` sets only `Val2` - technical refactorings: variadic function calls only in API level