|
| 1 | +package must |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "reflect" |
| 6 | +) |
| 7 | + |
| 8 | +type allowAll struct{} |
| 9 | + |
| 10 | +// Any implements a Checker that always returns true. |
| 11 | +var Any = allowAll{} |
| 12 | + |
| 13 | +const anyDebugMessage = "Any allows all values" |
| 14 | + |
| 15 | +func (allowAll) MustBe(v any) bool { |
| 16 | + return true |
| 17 | +} |
| 18 | + |
| 19 | +func (allowAll) Debug(v any) any { |
| 20 | + return anyDebugMessage |
| 21 | +} |
| 22 | + |
| 23 | +// Checker can be used by B# functions to check if value is allowed. |
| 24 | +type Checker interface { |
| 25 | + // MustBe returns true iff value is allowed. |
| 26 | + MustBe(v any) bool |
| 27 | + // Debug is used to explain why the value is not allowed. |
| 28 | + Debug(v any) any |
| 29 | +} |
| 30 | + |
| 31 | +// B1 asserts on one value. |
| 32 | +// |
| 33 | +// v is the value to be checked. If check is passed v is returned, otherwise panics. |
| 34 | +// |
| 35 | +// b checks v, there are a few cases: |
| 36 | +// |
| 37 | +// - A Checker (such as must.Any for all values); or |
| 38 | +// - a literal value to test for equality using `==`, |
| 39 | +// if types are the same and literal is comparable; or |
| 40 | +// - a nil value, which will match nil of any type. |
| 41 | +// - All other cases panic, room for future expansion. |
| 42 | +// |
| 43 | +// v and b are separated to two function calls for clarity, |
| 44 | +// v has to go first so the compiler can auto type it. |
| 45 | +// |
| 46 | +// debug does not participate in functionality. If the assert need a line comment, |
| 47 | +// then use it as the debug message is suggested. |
| 48 | +func B1[V any](v V) func(b any, debug ...any) V { |
| 49 | + return func(b any, debug ...any) V { |
| 50 | + checker, ok := b.(Checker) |
| 51 | + if ok { |
| 52 | + if !checker.MustBe(v) { |
| 53 | + panicWith(debug, checker.Debug(v)) |
| 54 | + } |
| 55 | + return v |
| 56 | + } |
| 57 | + if b == nil { |
| 58 | + value := reflect.ValueOf(v) |
| 59 | + if value.IsValid() && !value.IsNil() { |
| 60 | + panicWith(append(debug, v), "value must be nil") |
| 61 | + } |
| 62 | + return v |
| 63 | + } |
| 64 | + bAsV, ok := b.(V) |
| 65 | + if ok { |
| 66 | + var equal bool |
| 67 | + r := Recover(func() { equal = any(bAsV) == any(v) }) |
| 68 | + if r != nil { |
| 69 | + debug = append(debug, fmt.Sprintf("type(%T) is not comparable", bAsV), r) |
| 70 | + } |
| 71 | + if !equal { |
| 72 | + panicWith(append(debug, v, b), "values must equal") |
| 73 | + } |
| 74 | + return v |
| 75 | + } |
| 76 | + panicWith(debug, fmt.Sprintf("must use Checker interface or the same comparable type as input. expected %T got %T", v, b)) |
| 77 | + return v // unreachable |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +// B2 is 2 valued version of B1. To only Check one value, use must.Any on the other value to skip it. |
| 82 | +func B2[V1 any, V2 any](v1 V1, v2 V2) func(b1, b2 any, debug ...any) (V1, V2) { |
| 83 | + return func(b1, b2 any, debug ...any) (V1, V2) { |
| 84 | + B1(v1)(b1, append(debug, "#1")...) |
| 85 | + B1(v2)(b2, append(debug, "#2")...) |
| 86 | + return v1, v2 |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +// B3 is 3 valued version of B1. To only Check some value, use must.Any on the other values to skip it. |
| 91 | +func B3[V1 any, V2 any, V3 any](v1 V1, v2 V2, v3 V3) func(b1, b2, b3 any, debug ...any) (V1, V2, V3) { |
| 92 | + return func(b1, b2, b3 any, debug ...any) (V1, V2, V3) { |
| 93 | + B1(v1)(b1, append(debug, "#1")...) |
| 94 | + B1(v2)(b2, append(debug, "#2")...) |
| 95 | + B1(v3)(b3, append(debug, "#3")...) |
| 96 | + return v1, v2, v3 |
| 97 | + } |
| 98 | +} |
0 commit comments