Skip to content

Commit a052e73

Browse files
committed
[must] add must.True assertion
Also: - Improve documentation - Better tests Change-Id: Ibbac5427bbb745ac6822adf1e7dc8d62332c1fca
1 parent e6bb308 commit a052e73

7 files changed

Lines changed: 115 additions & 56 deletions

File tree

must/doc.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Package must provides assertion functions that panic on failure.
2+
//
3+
// # When to Use
4+
//
5+
// Use must functions when you are certain that operations should not fail
6+
// and want to eliminate error handling boilerplate in situations where
7+
// errors indicate programming bugs rather than runtime conditions.
8+
//
9+
// Common scenarios:
10+
// - Parsing static/hardcoded data (URLs, JSON, regex patterns)
11+
// - Operations that should always succeed in your specific context
12+
// - Configuration parsing where failure means misconfiguration
13+
//
14+
// # When NOT to Use
15+
//
16+
// Avoid must functions for:
17+
// - User input validation
18+
// - Network operations
19+
// - File I/O operations
20+
// - Any operation that can legitimately fail at runtime
21+
//
22+
// # Functions
23+
//
24+
// - must.OK[T](value T, err error) T - returns value if err is nil, panics otherwise
25+
// - must.True[T](value T, condition bool) T - returns value if condition is true, panics otherwise
26+
// - must.NoErr(err error) - panics if err is not nil
27+
//
28+
// # Examples
29+
//
30+
// // Static data parsing
31+
// baseURL := must.OK(url.Parse("https://api.example.com"))
32+
// pattern := must.OK(regexp.Compile(`^[a-z]+$`))
33+
//
34+
// // Configuration validation
35+
// config := must.True(loadConfig(), config.IsValid())
36+
//
37+
// // JSON unmarshaling of known-good data
38+
// var data Config
39+
// must.NoErr(json.Unmarshal(embeddedConfig, &data))
40+
package must

must/no-err.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,11 @@ import (
44
"dev.gaijin.team/go/golib/e"
55
)
66

7-
// NoErr is a wrapper function to simplify code, in situations where developer is sure
8-
// that the function being called cannot return an error. E.g.
7+
// NoErr panics if error is not nil.
98
//
10-
// err := json.Unmarshal([]byte(`["totally", "valid", "data"]`), &data)
11-
// if err != nil {
12-
// panic(err)
13-
// }
14-
//
15-
// developer doesn't expect error here because the parsed data is static and correct
16-
//
17-
// must.OK(json.Unmarshal([]byte(`["totally", "valid", "data"]`), &data))
9+
// must.NoErr(json.Unmarshal(staticJSON, &data))
1810
func NoErr(err error) {
1911
if err != nil {
20-
panic(e.NewFrom("NoErr assurance failed", err))
12+
panic(e.NewFrom("must.NoErr assertion failed", err))
2113
}
2214
}

must/no-error_test.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,25 @@ import (
66

77
"github.com/stretchr/testify/assert"
88

9+
"dev.gaijin.team/go/golib/e"
910
"dev.gaijin.team/go/golib/must"
1011
)
1112

1213
func TestNoErr(t *testing.T) {
1314
t.Parallel()
1415

1516
assert.NotPanics(t, func() {
16-
var v []string
17+
var data []string
1718

18-
must.NoErr(json.Unmarshal([]byte(`["foo"]`), &v))
19+
must.NoErr(json.Unmarshal([]byte(`["valid", "json"]`), &data))
20+
assert.Equal(t, []string{"valid", "json"}, data)
1921
})
2022

21-
assert.Panics(t, func() {
22-
var v []string
23+
imminentError := func() error { return e.New("imminent error") }
2324

24-
must.NoErr(json.Unmarshal([]byte(`["foo]`), &v))
25+
err := catchPanicError(t, func() {
26+
must.NoErr(imminentError())
2527
})
2628

27-
err := catchPanic(t, func() {
28-
must.NoErr(errorFn1())
29-
})
30-
31-
assert.ErrorIs(t, err, errTestError)
32-
assert.Equal(t, "NoErr assurance failed: test error", err.Error())
33-
}
34-
35-
func errorFn1() error {
36-
return errTestError
29+
assert.ErrorContains(t, err, "must.NoErr assertion failed: imminent error")
3730
}

must/ok.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,12 @@ import (
44
"dev.gaijin.team/go/golib/e"
55
)
66

7-
// OK is a wrapper function to simplify code, in situations where developer is sure
8-
// that the function being called cannot return an error. E.g.
7+
// OK returns the value if error is nil, otherwise panics.
98
//
10-
// u, err := url.Parse("example.com")
11-
// if err != nil {
12-
// panic(err)
13-
// }
14-
//
15-
// developer doesn't expect error here because the parsed path is a static and correct
16-
//
17-
// u = must.OK(url.Parse("example.com"))
9+
// u := must.OK(url.Parse("https://example.com"))
1810
func OK[T any](v T, err error) T { //nolint:ireturn
1911
if err != nil {
20-
panic(e.NewFrom("OK assurance failed", err))
12+
panic(e.NewFrom("must.OK assertion failed", err))
2113
}
2214

2315
return v

must/ok_test.go

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,47 @@
11
package must_test
22

33
import (
4-
"errors"
5-
"regexp"
4+
"net/url"
65
"testing"
76

87
"github.com/stretchr/testify/assert"
98

9+
"dev.gaijin.team/go/golib/e"
1010
"dev.gaijin.team/go/golib/must"
1111
)
1212

1313
func TestOK(t *testing.T) {
1414
t.Parallel()
1515

1616
assert.NotPanics(t, func() {
17-
_ = must.OK(regexp.Compile("[a-z]")) //nolint:gocritic
17+
uri := must.OK(url.Parse("https://example.com"))
18+
assert.Equal(t, "https://example.com", uri.String())
1819
})
1920

20-
assert.Panics(t, func() {
21-
_ = must.OK(regexp.Compile("[a-z")) //nolint:staticcheck,gocritic
22-
})
21+
imminentError := func() (bool, error) { return false, e.New("imminent error") }
2322

24-
err := catchPanic(t, func() {
25-
_ = must.OK(errorFn())
23+
err := catchPanicError(t, func() {
24+
_ = must.OK(imminentError())
2625
})
2726

28-
assert.ErrorIs(t, err, errTestError)
29-
assert.Equal(t, "OK assurance failed: test error", err.Error())
30-
}
31-
32-
var errTestError = errors.New("test error")
33-
34-
func errorFn() (bool, error) {
35-
return false, errTestError
27+
assert.ErrorContains(t, err, "must.OK assertion failed: imminent error")
3628
}
3729

38-
func catchPanic(t *testing.T, panickingFn func()) (err error) {
30+
func catchPanicError(t *testing.T, fn func()) (err error) {
3931
t.Helper()
4032

4133
defer func() {
42-
err = recover().(error) //nolint:forcetypeassert
34+
if rv := recover(); rv != nil {
35+
re, ok := rv.(error)
36+
if !ok {
37+
t.Fatal("recovered panic value is not an error")
38+
}
39+
40+
err = re
41+
}
4342
}()
4443

45-
panickingFn()
44+
fn()
4645

4746
return nil
4847
}

must/true.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package must
2+
3+
import (
4+
"dev.gaijin.team/go/golib/e"
5+
)
6+
7+
// True returns the value if condition is true, otherwise panics.
8+
//
9+
// result := must.True(logger.FromCtx(ctx))
10+
func True[T any](v T, condition bool) T { //nolint:ireturn
11+
if !condition {
12+
panic(e.New("must.True assertion failed"))
13+
}
14+
15+
return v
16+
}

must/true_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package must_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"dev.gaijin.team/go/golib/logger"
9+
"dev.gaijin.team/go/golib/must"
10+
)
11+
12+
func TestTrue(t *testing.T) {
13+
t.Parallel()
14+
15+
assert.NotPanics(t, func() {
16+
lgr := logger.NewNop()
17+
18+
result := must.True(logger.FromCtx(logger.ToCtx(t.Context(), lgr)))
19+
assert.Equal(t, lgr, result)
20+
})
21+
22+
err := catchPanicError(t, func() {
23+
_ = must.True(logger.FromCtx(t.Context()))
24+
})
25+
26+
assert.ErrorContains(t, err, "must.True assertion failed")
27+
}

0 commit comments

Comments
 (0)