|
11 | 11 | // - validateMountStringFormat() - Parses and validates a "source:dest:mode" mount string |
12 | 12 | // - containsTrigger() - Reports whether an 'on:' section includes a named trigger |
13 | 13 | // |
| 14 | +// # Type Conversion Helpers (any → []string) |
| 15 | +// |
| 16 | +// - parseStringSliceAny() - Canonical coercion of []string/[]any to []string; skips non-strings |
| 17 | +// - toStringSlice() - Strict variant: returns error on non-string elements; also accepts bare string |
| 18 | +// - extractStringSliceField() - Accepts string/[]string/[]any; skips empty strings; wraps bare string |
| 19 | +// |
14 | 20 | // # Design Rationale |
15 | 21 | // |
16 | 22 | // These helpers consolidate 76+ duplicate validation patterns identified in the |
@@ -189,6 +195,67 @@ func parseStringSliceAny(raw any, log *logger.Logger) []string { |
189 | 195 | } |
190 | 196 | } |
191 | 197 |
|
| 198 | +// toStringSlice converts an any value to a []string, supporting []string, []any, and string. |
| 199 | +// Unlike parseStringSliceAny, this function returns an error when a []any element is not a string, |
| 200 | +// and also accepts a bare string value (wrapping it in a single-element slice). |
| 201 | +func toStringSlice(val any) ([]string, error) { |
| 202 | + switch v := val.(type) { |
| 203 | + case []string: |
| 204 | + return v, nil |
| 205 | + case []any: |
| 206 | + result := make([]string, 0, len(v)) |
| 207 | + for _, item := range v { |
| 208 | + s, ok := item.(string) |
| 209 | + if !ok { |
| 210 | + return nil, errors.New("non-string item in list") |
| 211 | + } |
| 212 | + result = append(result, s) |
| 213 | + } |
| 214 | + return result, nil |
| 215 | + case string: |
| 216 | + return []string{v}, nil |
| 217 | + default: |
| 218 | + return nil, fmt.Errorf("unsupported type %T", val) |
| 219 | + } |
| 220 | +} |
| 221 | + |
| 222 | +// extractStringSliceField extracts a string slice from various input formats. |
| 223 | +// Handles: string, []string, []any (with string elements). |
| 224 | +// Returns nil if the input is empty or invalid. |
| 225 | +func extractStringSliceField(value any, fieldName string) []string { |
| 226 | + switch v := value.(type) { |
| 227 | + case string: |
| 228 | + // Single string |
| 229 | + if v == "" { |
| 230 | + return nil |
| 231 | + } |
| 232 | + validationHelpersLog.Printf("Extracted single %s: %s", fieldName, v) |
| 233 | + return []string{v} |
| 234 | + case []string: |
| 235 | + // Already a string slice |
| 236 | + if len(v) == 0 { |
| 237 | + return nil |
| 238 | + } |
| 239 | + validationHelpersLog.Printf("Extracted %d %s: %v", len(v), fieldName, v) |
| 240 | + return v |
| 241 | + case []any: |
| 242 | + // Array of any - extract strings |
| 243 | + var result []string |
| 244 | + for _, item := range v { |
| 245 | + if str, ok := item.(string); ok && str != "" { |
| 246 | + result = append(result, str) |
| 247 | + } |
| 248 | + } |
| 249 | + if len(result) == 0 { |
| 250 | + return nil |
| 251 | + } |
| 252 | + validationHelpersLog.Printf("Extracted %d %s from array: %v", len(result), fieldName, result) |
| 253 | + return result |
| 254 | + } |
| 255 | + validationHelpersLog.Printf("No valid %s found or unsupported type: %T", fieldName, value) |
| 256 | + return nil |
| 257 | +} |
| 258 | + |
192 | 259 | // validateNoDuplicateIDs checks that all items have unique IDs extracted by idFunc. |
193 | 260 | // The onDuplicate callback creates the error to return when a duplicate is found. |
194 | 261 | func validateNoDuplicateIDs[T any](items []T, idFunc func(T) string, onDuplicate func(string) error) error { |
|
0 commit comments