fix(config): reject ambiguous checklist comma format and propagate encode errors
ParseChecklistValue now accepts JSON arrays or single bare values and rejects legacy comma-separated input. Empty JSON segments are validated consistently, and FormatChecklistValue returns marshal errors instead of an empty string
This commit is contained in:
parent
a58e2fe0c3
commit
42f2572616
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
// ParseChecklistValue parses a checklist argument wire value.
|
||||
// New values are JSON arrays; legacy comma-separated values are still accepted.
|
||||
// Values must be JSON arrays, or a single choice without commas.
|
||||
func ParseChecklistValue(value string) ([]string, error) {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
|
|
@ -18,7 +18,11 @@ func ParseChecklistValue(value string) ([]string, error) {
|
|||
return parseJSONChecklistValue(trimmed)
|
||||
}
|
||||
|
||||
return parseLegacyChecklistValue(value)
|
||||
if strings.Contains(trimmed, ",") {
|
||||
return nil, fmt.Errorf("checklist value uses legacy comma-separated format; use a JSON array instead")
|
||||
}
|
||||
|
||||
return []string{trimmed}, nil
|
||||
}
|
||||
|
||||
func parseJSONChecklistValue(value string) ([]string, error) {
|
||||
|
|
@ -27,34 +31,25 @@ func parseJSONChecklistValue(value string) ([]string, error) {
|
|||
return nil, fmt.Errorf("invalid checklist JSON value: %w", err)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func parseLegacyChecklistValue(value string) ([]string, error) {
|
||||
segments := strings.Split(value, ",")
|
||||
values := make([]string, 0, len(segments))
|
||||
for _, segment := range segments {
|
||||
trimmedSegment := strings.TrimSpace(segment)
|
||||
if trimmedSegment == "" {
|
||||
for _, segment := range values {
|
||||
if strings.TrimSpace(segment) == "" {
|
||||
return nil, fmt.Errorf("checklist value contains an empty segment")
|
||||
}
|
||||
|
||||
values = append(values, trimmedSegment)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// FormatChecklistValue serializes selected checklist values for API transport.
|
||||
func FormatChecklistValue(values []string) string {
|
||||
func FormatChecklistValue(values []string) (string, error) {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return ""
|
||||
return "", fmt.Errorf("encoding checklist value: %w", err)
|
||||
}
|
||||
|
||||
return string(encoded)
|
||||
return string(encoded), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,25 +19,40 @@ func TestParseChecklistValueJSON(t *testing.T) {
|
|||
assert.Equal(t, []string{"kitchen,bedroom", "hallway"}, values)
|
||||
}
|
||||
|
||||
func TestParseChecklistValueLegacyCommaSeparated(t *testing.T) {
|
||||
func TestParseChecklistValueSingleValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
values, err := ParseChecklistValue("documents, photos")
|
||||
values, err := ParseChecklistValue("documents")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"documents", "photos"}, values)
|
||||
assert.Equal(t, []string{"documents"}, values)
|
||||
}
|
||||
|
||||
func TestParseChecklistValueRejectsEmptyLegacySegment(t *testing.T) {
|
||||
func TestParseChecklistValueRejectsLegacyCommaSeparated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ParseChecklistValue("documents,,photos")
|
||||
_, err := ParseChecklistValue("documents, photos")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestParseChecklistValueRejectsEmptyJSONSegment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ParseChecklistValue(`["documents","","photos"]`)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFormatChecklistValueJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, `["documents","photos"]`, FormatChecklistValue([]string{"documents", "photos"}))
|
||||
assert.Equal(t, `["kitchen,bedroom"]`, FormatChecklistValue([]string{"kitchen,bedroom"}))
|
||||
assert.Empty(t, FormatChecklistValue(nil))
|
||||
encoded, err := FormatChecklistValue([]string{"documents", "photos"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `["documents","photos"]`, encoded)
|
||||
|
||||
encoded, err = FormatChecklistValue([]string{"kitchen,bedroom"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `["kitchen,bedroom"]`, encoded)
|
||||
|
||||
encoded, err = FormatChecklistValue(nil)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, encoded)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,16 +499,21 @@ func mangleChecklistValue(arg *config.ActionArgument, value string, actionTitle
|
|||
return value
|
||||
}
|
||||
|
||||
return mangleChecklistSegments(arg, segments, actionTitle)
|
||||
return mangleChecklistSegments(arg, segments, value, actionTitle)
|
||||
}
|
||||
|
||||
func mangleChecklistSegments(arg *config.ActionArgument, segments []string, actionTitle string) string {
|
||||
func mangleChecklistSegments(arg *config.ActionArgument, segments []string, fallback string, actionTitle string) string {
|
||||
mangled := make([]string, len(segments))
|
||||
for i, segment := range segments {
|
||||
mangled[i] = mangleChecklistSegment(arg, segment, actionTitle)
|
||||
}
|
||||
|
||||
return config.FormatChecklistValue(mangled)
|
||||
formatted, err := config.FormatChecklistValue(mangled)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
func mangleChecklistSegment(arg *config.ActionArgument, segment string, actionTitle string) string {
|
||||
|
|
|
|||
|
|
@ -136,10 +136,10 @@ func TestValidateArgumentChecklistSelections(t *testing.T) {
|
|||
err := ValidateArgument(&arg, "documents", &action)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ValidateArgument(&arg, "documents,photos", &action)
|
||||
err = ValidateArgument(&arg, `["documents","photos"]`, &action)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ValidateArgument(&arg, "documents,unknown", &action)
|
||||
err = ValidateArgument(&arg, `["documents","unknown"]`, &action)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ func TestValidateArgumentChecklistTitleMangling(t *testing.T) {
|
|||
arg := checklistTestArg()
|
||||
action := config.Action{Title: "Test checklist title mangling"}
|
||||
|
||||
err := ValidateArgument(&arg, "Documents,Photos", &action)
|
||||
err := ValidateArgument(&arg, `["Documents","Photos"]`, &action)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ func TestValidateArgumentChecklistRejectsEmptySegment(t *testing.T) {
|
|||
arg := checklistTestArg()
|
||||
action := config.Action{Title: "Test checklist empty segment"}
|
||||
|
||||
err := ValidateArgument(&arg, "documents,,photos", &action)
|
||||
err := ValidateArgument(&arg, `["documents","","photos"]`, &action)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -195,10 +195,10 @@ func TestMangleArgumentValueChecklist(t *testing.T) {
|
|||
|
||||
arg := checklistTestArg()
|
||||
|
||||
out := MangleArgumentValue(&arg, "Documents,Music", "Test action")
|
||||
out := MangleArgumentValue(&arg, `["Documents","Music"]`, "Test action")
|
||||
assert.Equal(t, `["documents","music"]`, out)
|
||||
|
||||
out = MangleArgumentValue(&arg, "documents,photos", "Test action")
|
||||
out = MangleArgumentValue(&arg, `["documents","photos"]`, "Test action")
|
||||
assert.Equal(t, `["documents","photos"]`, out)
|
||||
}
|
||||
|
||||
|
|
@ -225,10 +225,10 @@ func TestValidateArgumentChecklistEntitySelections(t *testing.T) {
|
|||
err := ValidateArgument(&arg, "attic", &action)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ValidateArgument(&arg, "attic,basement", &action)
|
||||
err = ValidateArgument(&arg, `["attic","basement"]`, &action)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ValidateArgument(&arg, "attic,unknown", &action)
|
||||
err = ValidateArgument(&arg, `["attic","unknown"]`, &action)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +247,7 @@ func TestMangleArgumentValueChecklistEntityTitles(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
out := MangleArgumentValue(&arg, "attic room,basement room", "Test checklist entity titles")
|
||||
out := MangleArgumentValue(&arg, `["attic room","basement room"]`, "Test checklist entity titles")
|
||||
assert.Equal(t, `["attic","basement"]`, out)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func TestStorableArgumentsFromRequestStoresMangledChecklistValue(t *testing.T) {
|
|||
},
|
||||
}
|
||||
req.Arguments = map[string]string{
|
||||
"directories": "Documents,Photos",
|
||||
"directories": `["Documents","Photos"]`,
|
||||
}
|
||||
|
||||
mangleInvalidArgumentValues(req)
|
||||
|
|
|
|||
Loading…
Reference in New Issue