mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-11 02:58:49 +00:00
feat(internal): add ListOr[T any] type
This is a utility type that lets you decode a JSON T or list of T as a single value. This will be used with Redis Sentinel config so that you can specify multiple sentinel addresses. Ref TecharoHQ/botstopper#24 Assisted-by: GLM 4.6 via Claude Code Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
39
internal/listor.go
Normal file
39
internal/listor.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ListOr[T any] is a slice that can contain either a single T or multiple T values.
|
||||
// During JSON unmarshaling, it checks if the first character is '[' to determine
|
||||
// whether to treat the JSON as an array or a single value.
|
||||
type ListOr[T any] []T
|
||||
|
||||
func (lo *ListOr[T]) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if first non-whitespace character is '['
|
||||
firstChar := data[0]
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||
firstChar = data[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if firstChar == '[' {
|
||||
// It's an array, unmarshal directly
|
||||
return json.Unmarshal(data, (*[]T)(lo))
|
||||
} else {
|
||||
// It's a single value, unmarshal as a single item in a slice
|
||||
var single T
|
||||
if err := json.Unmarshal(data, &single); err != nil {
|
||||
return err
|
||||
}
|
||||
*lo = ListOr[T]{single}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
79
internal/listor_test.go
Normal file
79
internal/listor_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListOr_UnmarshalJSON(t *testing.T) {
|
||||
t.Run("single value should be unmarshaled as single item", func(t *testing.T) {
|
||||
var lo ListOr[string]
|
||||
|
||||
err := json.Unmarshal([]byte(`"hello"`), &lo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal single string: %v", err)
|
||||
}
|
||||
|
||||
if len(lo) != 1 {
|
||||
t.Fatalf("Expected 1 item, got %d", len(lo))
|
||||
}
|
||||
|
||||
if lo[0] != "hello" {
|
||||
t.Errorf("Expected 'hello', got %q", lo[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("array should be unmarshaled as multiple items", func(t *testing.T) {
|
||||
var lo ListOr[string]
|
||||
|
||||
err := json.Unmarshal([]byte(`["hello", "world"]`), &lo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal array: %v", err)
|
||||
}
|
||||
|
||||
if len(lo) != 2 {
|
||||
t.Fatalf("Expected 2 items, got %d", len(lo))
|
||||
}
|
||||
|
||||
if lo[0] != "hello" {
|
||||
t.Errorf("Expected 'hello', got %q", lo[0])
|
||||
}
|
||||
if lo[1] != "world" {
|
||||
t.Errorf("Expected 'world', got %q", lo[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("single number should be unmarshaled as single item", func(t *testing.T) {
|
||||
var lo ListOr[int]
|
||||
|
||||
err := json.Unmarshal([]byte(`42`), &lo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal single number: %v", err)
|
||||
}
|
||||
|
||||
if len(lo) != 1 {
|
||||
t.Fatalf("Expected 1 item, got %d", len(lo))
|
||||
}
|
||||
|
||||
if lo[0] != 42 {
|
||||
t.Errorf("Expected 42, got %d", lo[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("array of numbers should be unmarshaled as multiple items", func(t *testing.T) {
|
||||
var lo ListOr[int]
|
||||
|
||||
err := json.Unmarshal([]byte(`[1, 2, 3]`), &lo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal number array: %v", err)
|
||||
}
|
||||
|
||||
if len(lo) != 3 {
|
||||
t.Fatalf("Expected 3 items, got %d", len(lo))
|
||||
}
|
||||
|
||||
if lo[0] != 1 || lo[1] != 2 || lo[2] != 3 {
|
||||
t.Errorf("Expected [1, 2, 3], got %v", lo)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user