Skip to content

Commit 2ff6644

Browse files
feat(yaflags): Add minimal YaFlags
1 parent 6aee0b8 commit 2ff6644

3 files changed

Lines changed: 163 additions & 0 deletions

File tree

yaflags/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package yaflags
2+
3+
import "errors"
4+
5+
var (
6+
ErrTooManyBits = errors.New("too many bits for target type")
7+
ErrBitIndexOutOfRange = errors.New("bit index out of range")
8+
)

yaflags/yaflags.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package yaflags
2+
3+
import (
4+
"fmt"
5+
"math/bits"
6+
"net/http"
7+
8+
"github.com/YaCodeDev/GoYaCodeDevUtils/yaerrors"
9+
)
10+
11+
type uints interface {
12+
~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint
13+
}
14+
15+
func typeBitSize[T uints]() uint8 {
16+
var zero T
17+
18+
return uint8(bits.Len64(uint64(^zero)))
19+
}
20+
21+
// PackBitIndexes packs a list of bit positions into an unsigned integer of type T.
22+
// The function returns an error if the number of bits exceeds the size of T or if a bit index
23+
// is outside of the allowable range for T.
24+
func PackBitIndexes[T uints](bits []uint8) (T, yaerrors.Error) {
25+
var flags T
26+
27+
maxBits := typeBitSize[T]()
28+
29+
if len(bits) > int(maxBits) {
30+
return 0, yaerrors.FromError(
31+
http.StatusBadRequest,
32+
ErrTooManyBits,
33+
fmt.Sprintf(
34+
"pack bits: received %d bits but %T supports at most %d",
35+
len(bits),
36+
flags,
37+
maxBits,
38+
),
39+
)
40+
}
41+
42+
for _, bit := range bits {
43+
if bit >= maxBits {
44+
return 0, yaerrors.FromError(
45+
http.StatusBadRequest,
46+
ErrBitIndexOutOfRange,
47+
fmt.Sprintf(
48+
"pack bits: index %d out of range for %T (%d bits)",
49+
bit,
50+
flags,
51+
maxBits,
52+
),
53+
)
54+
}
55+
56+
flags |= T(1) << bit
57+
}
58+
59+
return flags, nil
60+
}
61+
62+
// UnpackBitIndexes unpacks a list of bit positions from an unsigned integer of type T.
63+
func UnpackBitIndexes[T uints](flags T) []uint8 {
64+
if flags == 0 {
65+
return nil
66+
}
67+
68+
maxBits := typeBitSize[T]()
69+
70+
result := make([]uint8, 0, bits.OnesCount64(uint64(flags)))
71+
72+
for bit := range maxBits {
73+
if flags&(T(1)<<bit) != 0 {
74+
result = append(result, bit)
75+
}
76+
}
77+
78+
return result
79+
}

yaflags/yaflags_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package yaflags
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"reflect"
7+
"testing"
8+
)
9+
10+
type customFlags uint16
11+
12+
func TestPackBitIndexesSuccess(t *testing.T) {
13+
t.Parallel()
14+
15+
flags, err := PackBitIndexes[uint16]([]uint8{0, 3, 7})
16+
if err != nil {
17+
t.Fatalf("unexpected error: %v", err)
18+
}
19+
20+
const want uint16 = (1 << 0) | (1 << 3) | (1 << 7)
21+
if flags != want {
22+
t.Fatalf("wrong flags: want %016b, got %016b", want, flags)
23+
}
24+
}
25+
26+
func TestPackBitIndexesTooManyBits(t *testing.T) {
27+
t.Parallel()
28+
29+
_, err := PackBitIndexes[uint8]([]uint8{0, 1, 2, 3, 4, 5, 6, 7, 0})
30+
if err == nil {
31+
t.Fatal("expected error, got nil")
32+
}
33+
34+
if !errors.Is(err, ErrTooManyBits) {
35+
t.Fatalf("expected ErrTooManyBits, got %v", err)
36+
}
37+
38+
if err.Code() != http.StatusBadRequest {
39+
t.Fatalf("expected HTTP status %d in error code, got %d", http.StatusBadRequest, err.Code())
40+
}
41+
}
42+
43+
func TestPackBitIndexesBitIndexOutOfRange(t *testing.T) {
44+
t.Parallel()
45+
46+
_, err := PackBitIndexes[uint8]([]uint8{0, 8})
47+
if err == nil {
48+
t.Fatal("expected error, got nil")
49+
}
50+
51+
if !errors.Is(err, ErrBitIndexOutOfRange) {
52+
t.Fatalf("expected ErrBitIndexOutOfRange, got %v", err)
53+
}
54+
}
55+
56+
func TestUnpackBitIndexesReturnsSortedIndexes(t *testing.T) {
57+
t.Parallel()
58+
59+
const value customFlags = (1 << 0) | (1 << 4) | (1 << 9)
60+
61+
got := UnpackBitIndexes(value)
62+
want := []uint8{0, 4, 9}
63+
64+
if !reflect.DeepEqual(got, want) {
65+
t.Fatalf("expected %v, got %v", want, got)
66+
}
67+
}
68+
69+
func TestUnpackBitIndexesZeroValue(t *testing.T) {
70+
t.Parallel()
71+
72+
got := UnpackBitIndexes[uint32](0)
73+
if got != nil {
74+
t.Fatalf("expected nil slice, got %v", got)
75+
}
76+
}

0 commit comments

Comments
 (0)