mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(config): support Chinese comma separator in allow_from environment variables (#1301)
Add UnmarshalText method to FlexibleStringSlice to support both English (,) and Chinese (,) comma separators in environment variables. Includes comprehensive unit tests covering: - English commas, Chinese commas, mixed commas - Single values, whitespace trimming - Empty strings, edge cases Fixes #1280
This commit is contained in:
@@ -17,6 +17,8 @@ var rrCounter atomic.Uint64
|
||||
|
||||
// FlexibleStringSlice is a []string that also accepts JSON numbers,
|
||||
// so allow_from can contain both "123" and 123.
|
||||
// It also supports parsing comma-separated strings from environment variables,
|
||||
// including both English (,) and Chinese (,) commas.
|
||||
type FlexibleStringSlice []string
|
||||
|
||||
func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||
@@ -48,6 +50,30 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler to support env variable parsing.
|
||||
// It handles comma-separated values with both English (,) and Chinese (,) commas.
|
||||
func (f *FlexibleStringSlice) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*f = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
s := string(text)
|
||||
// Replace Chinese comma with English comma, then split
|
||||
s = strings.ReplaceAll(s, ",", ",")
|
||||
parts := strings.Split(s, ",")
|
||||
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
result = append(result, part)
|
||||
}
|
||||
}
|
||||
*f = result
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Agents AgentsConfig `json:"agents"`
|
||||
Bindings []AgentBinding `json:"bindings,omitempty"`
|
||||
|
||||
@@ -505,3 +505,119 @@ func TestDefaultConfig_WorkspacePath_WithPicoclawHome(t *testing.T) {
|
||||
t.Errorf("Workspace path with PICOCLAW_HOME = %q, want %q", cfg.Agents.Defaults.Workspace, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFlexibleStringSlice_UnmarshalText tests UnmarshalText with various comma separators
|
||||
func TestFlexibleStringSlice_UnmarshalText(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "English commas only",
|
||||
input: "123,456,789",
|
||||
expected: []string{"123", "456", "789"},
|
||||
},
|
||||
{
|
||||
name: "Chinese commas only",
|
||||
input: "123,456,789",
|
||||
expected: []string{"123", "456", "789"},
|
||||
},
|
||||
{
|
||||
name: "Mixed English and Chinese commas",
|
||||
input: "123,456,789",
|
||||
expected: []string{"123", "456", "789"},
|
||||
},
|
||||
{
|
||||
name: "Single value",
|
||||
input: "123",
|
||||
expected: []string{"123"},
|
||||
},
|
||||
{
|
||||
name: "Values with whitespace",
|
||||
input: " 123 , 456 , 789 ",
|
||||
expected: []string{"123", "456", "789"},
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
input: "",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Only commas - English",
|
||||
input: ",,",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "Only commas - Chinese",
|
||||
input: ",,",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "Mixed commas with empty parts",
|
||||
input: "123,,456,,789",
|
||||
expected: []string{"123", "456", "789"},
|
||||
},
|
||||
{
|
||||
name: "Complex mixed values",
|
||||
input: "user1@example.com,user2@test.com, admin@domain.org",
|
||||
expected: []string{"user1@example.com", "user2@test.com", "admin@domain.org"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var f FlexibleStringSlice
|
||||
err := f.UnmarshalText([]byte(tt.input))
|
||||
if err != nil {
|
||||
t.Fatalf("UnmarshalText(%q) error = %v", tt.input, err)
|
||||
}
|
||||
|
||||
if tt.expected == nil {
|
||||
if f != nil {
|
||||
t.Errorf("UnmarshalText(%q) = %v, want nil", tt.input, f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(f) != len(tt.expected) {
|
||||
t.Errorf("UnmarshalText(%q) length = %d, want %d", tt.input, len(f), len(tt.expected))
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range tt.expected {
|
||||
if f[i] != v {
|
||||
t.Errorf("UnmarshalText(%q)[%d] = %q, want %q", tt.input, i, f[i], v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFlexibleStringSlice_UnmarshalText_EmptySliceConsistency tests nil vs empty slice behavior
|
||||
func TestFlexibleStringSlice_UnmarshalText_EmptySliceConsistency(t *testing.T) {
|
||||
t.Run("Empty string returns nil", func(t *testing.T) {
|
||||
var f FlexibleStringSlice
|
||||
err := f.UnmarshalText([]byte(""))
|
||||
if err != nil {
|
||||
t.Fatalf("UnmarshalText error = %v", err)
|
||||
}
|
||||
if f != nil {
|
||||
t.Errorf("Empty string should return nil, got %v", f)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Commas only returns empty slice", func(t *testing.T) {
|
||||
var f FlexibleStringSlice
|
||||
err := f.UnmarshalText([]byte(",,,"))
|
||||
if err != nil {
|
||||
t.Fatalf("UnmarshalText error = %v", err)
|
||||
}
|
||||
if f == nil {
|
||||
t.Error("Commas only should return empty slice, not nil")
|
||||
}
|
||||
if len(f) != 0 {
|
||||
t.Errorf("Expected empty slice, got %v", f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user