Open intercepted16 opened 1 week ago
Agreed, would be great to have the ability to add custom Marshal
/Unmarshal
functions and a ValueAny
that can be used instead of Value
so that the conversion to/from a string is usable for virtually any type. Would also need a ValidateAny
that would take interface{}
or any
on the callback function ... or it would be fine with validation still happening at the string level before Unmarshal
is called.
I took the existing Text
and Input
types and made them generic with marshal/unmarshal functions. You can drop these in your own project somewhere rather than waiting for something like this in huh.
Then, if I want support for int
field to collect a port number, I can do:
func marshalInt(input int) string {
return strconv.FormatInt(int64(input), 10)
}
func unmarshalInt(input string) (int, error) {
return strconv.Atoi(input)
}
func validatePort(port int) error {
if port < 0 || port > 65535 {
return fmt.Errorf("Invalid port")
}
return nil
}
.
.
.
NewInputAny(marshalInt, unmarshalInt).
Title("Port").
Value(&portNumber).
Validate(validatePort)
If I want to collect a single IPv4 address into a netip.Addr
, I can do:
func marshalAddr(input netip.Addr) string {
return input.String()
}
func unmarshalAddr(input string) (netip.Addr, error) {
return netip.ParseAddr(input)
}
func validateAddrv4(addr netip.Addr) error {
if !addr.Is4() {
return fmt.Errorf("Address must be IPv4")
}
return nil
}
.
.
.
NewInputAny(marshalAddr, unmarshalAddr).
Title("IP").
Value(&m.balenaConfig.Proxy.IP).
Validate(validateAddrv4),
If I want to collect a set of IP/Prefix, one per line, into a netip.Prefix
and ensure they are valid and don't overlap, I can do:
func marshalIPPrefixArray(input []netip.Prefix) string {
// Convert to a string with one CIDR per line
var s string
for _, prefix := range input {
s += prefix.String() + "\n"
}
return s
}
var netipParseCleanup = regexp.MustCompile(`netip.ParsePrefix\(\"(.*)\"\): (.*)`)
func unmarshalIPPrefixArray(input string) ([]netip.Prefix, error) {
// Split the string into lines
lines := strings.Split(input, "\n")
// Parse each line as a CIDR
var prefixes []netip.Prefix
for _, line := range lines {
if line == "" {
continue
}
prefix, err := netip.ParsePrefix(line)
if err != nil {
// Clean up the error message
errStr := netipParseCleanup.ReplaceAllString(err.Error(), "'$1': $2")
return nil, fmt.Errorf(errStr)
}
prefixes = append(prefixes, prefix)
}
return prefixes, nil
}
func validateIPPrefixArray(prefixes []netip.Prefix) error {
// Check for empty array
if len(prefixes) == 0 {
return fmt.Errorf("IP Prefix array cannot be empty")
}
// Check for invalid prefixes
for _, prefix := range prefixes {
if !prefix.IsValid() {
return fmt.Errorf("Invalid IP Prefix: %s", prefix)
}
}
// Check for overlapping prefixes
for i, prefix := range prefixes {
for j, otherPrefix := range prefixes {
if i != j && prefix.Overlaps(otherPrefix) {
return fmt.Errorf("Overlapping IP Prefixes: %s and %s", prefix, otherPrefix)
}
}
}
return nil
}
.
.
.
NewTextAny(marshalIPPrefixArray, unmarshalIPPrefixArray).
Title("CIDR(s)").
Description("List of IPs in CIDR (1.2.3.4/24) format, one per line").
Value(&m.balenaConfig.Ethernet[selectedInterface].IPv4.Addresses).
Validate(validateIPPrefixArray),
@shaunco Thanks a lot for that temporary solution!
Is your feature request related to a problem? Please describe. Yes; I am trying to get an integer from the user through the
NewInput
function, and it seems that I can't because the type expected inValue
is a pointer to a string, not an integer.Describe the solution you'd like An option to specify the type as a generic type argument.
Describe alternatives you've considered An alternative is to provide a function that takes a function as an argument which converts the specified value to an integer.
Additional context Add any other context or screenshots about the feature request here.